From c095808aef5c4e627b8399e5cb0224a9cc3cd9e3 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 17 Oct 2024 11:54:32 +0200 Subject: [PATCH 01/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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 8f1544d30f57782af58c8201d58e40c05efe96c0 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 23 Oct 2024 15:36:50 +0200 Subject: [PATCH 10/15] wip --- ...ctMatchesFilterRecordsIdsComponentState.ts | 8 ++ ...tInlineCellEditModeMultiRecords2Effect.tsx | 123 ++++++++++++++++++ ...ltiObjectSearchMatchesSearchFilterQuery.ts | 87 +++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts create mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecords2Effect.tsx create mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts new file mode 100644 index 000000000000..9c083c71eaab --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts @@ -0,0 +1,8 @@ +import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; +import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; + +export const objectRecordMultiSelectMatchesFilterRecordsIdsComponentState = + createComponentState({ + key: 'objectRecordMultiSelectMatchesFilterRecordsIdsComponentState', + defaultValue: [], + }); diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecords2Effect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecords2Effect.tsx new file mode 100644 index 000000000000..abf79001da37 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecords2Effect.tsx @@ -0,0 +1,123 @@ +import { useEffect } from 'react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; + +import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; +import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates'; +import { useMultiObjectSearchMatchesSearchFilterQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery'; +import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; +import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; + +export const ActivityTargetInlineCellEditModeMultiRecords2Effect = () => { + console.log('rendering ActivityTargetInlineCellEditModeMultiRecords2Effect'); + const scopeId = useAvailableScopeIdOrThrow( + RelationPickerScopeInternalContext, + ); + const { recordMultiSelectIsLoadingState } = + useObjectRecordMultiSelectScopedStates(scopeId); + // const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] = + // useRecoilState(objectRecordsIdsMultiSelectState); + + const setRecordMultiSelectIsLoading = useSetRecoilState( + recordMultiSelectIsLoadingState, + ); + + const setRecordMultiSelectMatchesFilterRecords = useSetRecoilState( + objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({ + scopeId, + }), + ); + + const relationPickerScopedId = useAvailableScopeIdOrThrow( + RelationPickerScopeInternalContext, + ); + + const { relationPickerSearchFilterState } = useRelationPickerScopedStates({ + relationPickerScopedId, + }); + const relationPickerSearchFilter = useRecoilValue( + relationPickerSearchFilterState, + ); + + const { + matchesSearchFilterObjectRecords, + matchesSearchFilterObjectRecordsLoading: loading, + } = useMultiObjectSearchMatchesSearchFilterQuery({ + excludedObjects: [CoreObjectNameSingular.Task, CoreObjectNameSingular.Note], + searchFilterValue: relationPickerSearchFilter, + limit: 10, + }); + + setRecordMultiSelectMatchesFilterRecords(matchesSearchFilterObjectRecords); + + // const [ + // objectRecordMultiSelectCheckedRecordsIds, + // setObjectRecordMultiSelectCheckedRecordsIds, + // ] = useRecoilState(objectRecordMultiSelectCheckedRecordsIdsState); + + // const updateRecords = useRecoilCallback( + // ({ snapshot, set }) => + // (newRecords: ObjectRecordForSelect[]) => { + // for (const newRecord of newRecords) { + // const currentRecord = snapshot + // .getLoadable( + // objectRecordMultiSelectComponentFamilyState({ + // scopeId: scopeId, + // familyKey: newRecord.record.id, + // }), + // ) + // .getValue(); + + // const newRecordWithSelected = { + // ...newRecord, + // selected: objectRecordMultiSelectCheckedRecordsIds.some( + // (checkedRecordId) => checkedRecordId === newRecord.record.id, + // ), + // }; + + // if ( + // !isDeeplyEqual( + // newRecordWithSelected.selected, + // currentRecord?.selected, + // ) + // ) { + // set( + // objectRecordMultiSelectComponentFamilyState({ + // scopeId: scopeId, + // familyKey: newRecordWithSelected.record.id, + // }), + // newRecordWithSelected, + // ); + // } + // } + // }, + // [objectRecordMultiSelectCheckedRecordsIds, scopeId], + // ); + + // useEffect(() => { + // const allRecords = matchesSearchFilterObjectRecords ?? []; + // updateRecords(allRecords); + // const allRecordsIds = allRecords.map((record) => record.record.id); + // if (!isDeeplyEqual(allRecordsIds, objectRecordsIdsMultiSelect)) { + // setObjectRecordsIdsMultiSelect(allRecordsIds); + // } + // }, [ + // matchesSearchFilterObjectRecords, + // objectRecordsIdsMultiSelect, + // setObjectRecordsIdsMultiSelect, + // updateRecords, + // ]); + + // useEffect(() => { + // setObjectRecordMultiSelectCheckedRecordsIds( + // selectedObjectRecordIds.map((rec) => rec.id), + // ); + // }, [selectedObjectRecordIds, setObjectRecordMultiSelectCheckedRecordsIds]); + + useEffect(() => { + setRecordMultiSelectIsLoading(loading); + }, [loading, setRecordMultiSelectIsLoading]); + + return <>; +}; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts new file mode 100644 index 000000000000..038f1f22c5ad --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts @@ -0,0 +1,87 @@ +import { useQuery } from '@apollo/client'; +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 { 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 { isDefined } from '~/utils/isDefined'; + +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 useMultiObjectSearchMatchesSearchFilterQuery = ({ + searchFilterValue, + limit, + excludedObjects, +}: { + searchFilterValue: string; + limit?: number; + excludedObjects?: CoreObjectNameSingular[]; +}) => { + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + const selectableObjectMetadataItems = objectMetadataItems + .filter(({ isSystem, isRemote }) => !isSystem && !isRemote) + .filter(({ nameSingular }) => { + return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular); + }); + + const { limitPerMetadataItem } = useLimitPerMetadataItem({ + objectMetadataItems, + limit, + }); + + const multiSelectSearchQueryForSelectedIds = + useGenerateCombinedSearchRecordsQuery({ + operationSignatures: selectableObjectMetadataItems.map( + (objectMetadataItem) => ({ + objectNameSingular: objectMetadataItem.nameSingular, + variables: {}, + }), + ), + }); + + const { + loading: matchesSearchFilterObjectRecordsLoading, + data: matchesSearchFilterObjectRecordsQueryResult, + } = useQuery( + multiSelectSearchQueryForSelectedIds ?? EMPTY_QUERY, + { + variables: { + search: searchFilterValue, + ...limitPerMetadataItem, + }, + skip: !isDefined(multiSelectSearchQueryForSelectedIds), + }, + ); + + const { objectRecordForSelectArray: matchesSearchFilterObjectRecords } = + useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ + multiObjectRecordsQueryResult: formatSearchResults( + matchesSearchFilterObjectRecordsQueryResult, + ), + }); + + return { + matchesSearchFilterObjectRecordsLoading, + matchesSearchFilterObjectRecords, + }; +}; From d4e63b1048a87728de67853d82d08391a550a6cd Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Wed, 23 Oct 2024 18:55:44 +0200 Subject: [PATCH 11/15] work in progress --- .../ActivityTargetInlineCellEditMode.tsx | 3 + ...onFromManyFieldInputMultiRecordsEffect.tsx | 10 +- ...tInlineCellEditModeMultiRecords2Effect.tsx | 123 --------------- ...etInlineCellEditModeMultiRecordsEffect.tsx | 71 +++------ ...EditModeMultiRecordsSearchFilterEffect.tsx | 67 +++++++++ .../__tests__/useMultiObjectSearch.test.tsx | 140 ------------------ .../hooks/useMultiObjectSearch.ts | 76 ---------- ...atchesSearchFilterAndSelectedItemsQuery.ts | 120 --------------- ...archMatchesSearchFilterAndToSelectQuery.ts | 106 ------------- 9 files changed, 101 insertions(+), 615 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecords2Effect.tsx create mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearch.ts delete mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts delete mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts diff --git a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx index 4ed47174a550..8dd159f9df36 100644 --- a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx +++ b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx @@ -27,6 +27,7 @@ import { import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ActivityTargetInlineCellEditModeMultiRecordsEffect } from '@/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect'; +import { ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect } from '@/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect'; import { MultiRecordSelect } from '@/object-record/relation-picker/components/MultiRecordSelect'; import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope'; import { prefillRecord } from '@/object-record/utils/prefillRecord'; @@ -236,6 +237,7 @@ export const ActivityTargetInlineCellEditMode = ({ (activityTarget) => activityTarget.id !== activityTargetToDeleteId, ); + if (isActivityInCreateMode) { upsertActivity({ activity, @@ -279,6 +281,7 @@ export const ActivityTargetInlineCellEditMode = ({ + diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx index 6f42a7fbad2e..a527fa057adf 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx @@ -3,15 +3,23 @@ import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil'; import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField'; import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState'; -import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; import { useRelationPickerEntitiesOptions } from '@/object-record/relation-picker/hooks/useRelationPickerEntitiesOptions'; import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; +export type ObjectRecordForSelect = { + objectMetadataItem: ObjectMetadataItem; + record: ObjectRecord; + recordIdentifier: ObjectRecordIdentifier; +}; + export const RelationFromManyFieldInputMultiRecordsEffect = () => { const { fieldValue, fieldDefinition } = useRelationField(); const scopeId = useAvailableScopeIdOrThrow( diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecords2Effect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecords2Effect.tsx deleted file mode 100644 index abf79001da37..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecords2Effect.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { useEffect } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; - -import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; -import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates'; -import { useMultiObjectSearchMatchesSearchFilterQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery'; -import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; -import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; - -export const ActivityTargetInlineCellEditModeMultiRecords2Effect = () => { - console.log('rendering ActivityTargetInlineCellEditModeMultiRecords2Effect'); - const scopeId = useAvailableScopeIdOrThrow( - RelationPickerScopeInternalContext, - ); - const { recordMultiSelectIsLoadingState } = - useObjectRecordMultiSelectScopedStates(scopeId); - // const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] = - // useRecoilState(objectRecordsIdsMultiSelectState); - - const setRecordMultiSelectIsLoading = useSetRecoilState( - recordMultiSelectIsLoadingState, - ); - - const setRecordMultiSelectMatchesFilterRecords = useSetRecoilState( - objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({ - scopeId, - }), - ); - - const relationPickerScopedId = useAvailableScopeIdOrThrow( - RelationPickerScopeInternalContext, - ); - - const { relationPickerSearchFilterState } = useRelationPickerScopedStates({ - relationPickerScopedId, - }); - const relationPickerSearchFilter = useRecoilValue( - relationPickerSearchFilterState, - ); - - const { - matchesSearchFilterObjectRecords, - matchesSearchFilterObjectRecordsLoading: loading, - } = useMultiObjectSearchMatchesSearchFilterQuery({ - excludedObjects: [CoreObjectNameSingular.Task, CoreObjectNameSingular.Note], - searchFilterValue: relationPickerSearchFilter, - limit: 10, - }); - - setRecordMultiSelectMatchesFilterRecords(matchesSearchFilterObjectRecords); - - // const [ - // objectRecordMultiSelectCheckedRecordsIds, - // setObjectRecordMultiSelectCheckedRecordsIds, - // ] = useRecoilState(objectRecordMultiSelectCheckedRecordsIdsState); - - // const updateRecords = useRecoilCallback( - // ({ snapshot, set }) => - // (newRecords: ObjectRecordForSelect[]) => { - // for (const newRecord of newRecords) { - // const currentRecord = snapshot - // .getLoadable( - // objectRecordMultiSelectComponentFamilyState({ - // scopeId: scopeId, - // familyKey: newRecord.record.id, - // }), - // ) - // .getValue(); - - // const newRecordWithSelected = { - // ...newRecord, - // selected: objectRecordMultiSelectCheckedRecordsIds.some( - // (checkedRecordId) => checkedRecordId === newRecord.record.id, - // ), - // }; - - // if ( - // !isDeeplyEqual( - // newRecordWithSelected.selected, - // currentRecord?.selected, - // ) - // ) { - // set( - // objectRecordMultiSelectComponentFamilyState({ - // scopeId: scopeId, - // familyKey: newRecordWithSelected.record.id, - // }), - // newRecordWithSelected, - // ); - // } - // } - // }, - // [objectRecordMultiSelectCheckedRecordsIds, scopeId], - // ); - - // useEffect(() => { - // const allRecords = matchesSearchFilterObjectRecords ?? []; - // updateRecords(allRecords); - // const allRecordsIds = allRecords.map((record) => record.record.id); - // if (!isDeeplyEqual(allRecordsIds, objectRecordsIdsMultiSelect)) { - // setObjectRecordsIdsMultiSelect(allRecordsIds); - // } - // }, [ - // matchesSearchFilterObjectRecords, - // objectRecordsIdsMultiSelect, - // setObjectRecordsIdsMultiSelect, - // updateRecords, - // ]); - - // useEffect(() => { - // setObjectRecordMultiSelectCheckedRecordsIds( - // selectedObjectRecordIds.map((rec) => rec.id), - // ); - // }, [selectedObjectRecordIds, setObjectRecordMultiSelectCheckedRecordsIds]); - - useEffect(() => { - setRecordMultiSelectIsLoading(loading); - }, [loading, setRecordMultiSelectIsLoading]); - - return <>; -}; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx index 425877c20e15..152f78ecf7d9 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx @@ -7,18 +7,18 @@ import { } from 'recoil'; import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { ObjectRecordForSelect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState'; -import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates'; -import { - ObjectRecordForSelect, - SelectedObjectRecordId, - useMultiObjectSearch, -} from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; +import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; +export type SelectedObjectRecordId = { + objectNameSingular: string; + id: string; +}; + export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ selectedObjectRecordIds, }: { @@ -30,43 +30,14 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ const { objectRecordsIdsMultiSelectState, objectRecordMultiSelectCheckedRecordsIdsState, - recordMultiSelectIsLoadingState, } = useObjectRecordMultiSelectScopedStates(scopeId); const [objectRecordsIdsMultiSelect, setObjectRecordsIdsMultiSelect] = useRecoilState(objectRecordsIdsMultiSelectState); - const setRecordMultiSelectIsLoading = useSetRecoilState( - recordMultiSelectIsLoadingState, - ); - - const relationPickerScopedId = useAvailableScopeIdOrThrow( - RelationPickerScopeInternalContext, - ); - - const { relationPickerSearchFilterState } = useRelationPickerScopedStates({ - relationPickerScopedId, - }); - const relationPickerSearchFilter = useRecoilValue( - relationPickerSearchFilterState, + const setObjectRecordMultiSelectCheckedRecordsIds = useSetRecoilState( + objectRecordMultiSelectCheckedRecordsIdsState, ); - const { filteredSelectedObjectRecords, loading, objectRecordsToSelect } = - useMultiObjectSearch({ - searchFilterValue: relationPickerSearchFilter, - excludedObjects: [ - CoreObjectNameSingular.Task, - CoreObjectNameSingular.Note, - ], - selectedObjectRecordIds, - excludedObjectRecordIds: [], - limit: 10, - }); - - const [ - objectRecordMultiSelectCheckedRecordsIds, - setObjectRecordMultiSelectCheckedRecordsIds, - ] = useRecoilState(objectRecordMultiSelectCheckedRecordsIdsState); - const updateRecords = useRecoilCallback( ({ snapshot, set }) => (newRecords: ObjectRecordForSelect[]) => { @@ -80,6 +51,10 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ ) .getValue(); + const objectRecordMultiSelectCheckedRecordsIds = snapshot + .getLoadable(objectRecordMultiSelectCheckedRecordsIdsState) + .getValue(); + const newRecordWithSelected = { ...newRecord, selected: objectRecordMultiSelectCheckedRecordsIds.some( @@ -103,23 +78,25 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ } } }, - [objectRecordMultiSelectCheckedRecordsIds, scopeId], + [objectRecordMultiSelectCheckedRecordsIdsState, scopeId], + ); + + const matchesSearchFilterObjectRecords = useRecoilValue( + objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({ + scopeId, + }), ); useEffect(() => { - const allRecords = [ - ...(filteredSelectedObjectRecords ?? []), - ...(objectRecordsToSelect ?? []), - ]; + const allRecords = matchesSearchFilterObjectRecords ?? []; updateRecords(allRecords); const allRecordsIds = allRecords.map((record) => record.record.id); if (!isDeeplyEqual(allRecordsIds, objectRecordsIdsMultiSelect)) { setObjectRecordsIdsMultiSelect(allRecordsIds); } }, [ - filteredSelectedObjectRecords, + matchesSearchFilterObjectRecords, objectRecordsIdsMultiSelect, - objectRecordsToSelect, setObjectRecordsIdsMultiSelect, updateRecords, ]); @@ -130,9 +107,5 @@ export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ ); }, [selectedObjectRecordIds, setObjectRecordMultiSelectCheckedRecordsIds]); - useEffect(() => { - setRecordMultiSelectIsLoading(loading); - }, [loading, setRecordMultiSelectIsLoading]); - return <>; }; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx new file mode 100644 index 000000000000..dc55185d60c5 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx @@ -0,0 +1,67 @@ +import { useEffect } from 'react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; + +import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; +import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates'; +import { useMultiObjectSearchMatchesSearchFilterQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery'; +import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; +import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; + +export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect = + () => { + const scopeId = useAvailableScopeIdOrThrow( + RelationPickerScopeInternalContext, + ); + const { recordMultiSelectIsLoadingState } = + useObjectRecordMultiSelectScopedStates(scopeId); + + const setRecordMultiSelectIsLoading = useSetRecoilState( + recordMultiSelectIsLoadingState, + ); + + const setRecordMultiSelectMatchesFilterRecords = useSetRecoilState( + objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({ + scopeId, + }), + ); + + const relationPickerScopedId = useAvailableScopeIdOrThrow( + RelationPickerScopeInternalContext, + ); + + const { relationPickerSearchFilterState } = useRelationPickerScopedStates({ + relationPickerScopedId, + }); + const relationPickerSearchFilter = useRecoilValue( + relationPickerSearchFilterState, + ); + + const { + matchesSearchFilterObjectRecords, + matchesSearchFilterObjectRecordsLoading: loading, + } = useMultiObjectSearchMatchesSearchFilterQuery({ + excludedObjects: [ + CoreObjectNameSingular.Task, + CoreObjectNameSingular.Note, + ], + searchFilterValue: relationPickerSearchFilter, + limit: 10, + }); + + useEffect(() => { + setRecordMultiSelectMatchesFilterRecords( + matchesSearchFilterObjectRecords, + ); + }, [ + setRecordMultiSelectMatchesFilterRecords, + matchesSearchFilterObjectRecords, + ]); + + useEffect(() => { + setRecordMultiSelectIsLoading(loading); + }, [loading, setRecordMultiSelectIsLoading]); + + return <>; + }; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx deleted file mode 100644 index 73d1715f5503..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { - MultiObjectSearch, - ObjectRecordForSelect, - SelectedObjectRecordId, - useMultiObjectSearch, -} from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; -import { useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery'; -import { useMultiObjectSearchMatchesSearchFilterAndToSelectQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery'; -import { useMultiObjectSearchSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery'; -import { renderHook } from '@testing-library/react'; -import { FieldMetadataType } from '~/generated/graphql'; - -jest.mock( - '@/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery', -); -jest.mock( - '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery', -); -jest.mock( - '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery', -); - -const objectData: ObjectMetadataItem[] = [ - { - createdAt: 'createdAt', - id: 'id', - isActive: true, - isCustom: true, - isSystem: false, - isRemote: false, - labelPlural: 'labelPlural', - labelSingular: 'labelSingular', - namePlural: 'namePlural', - nameSingular: 'nameSingular', - updatedAt: 'updatedAt', - fields: [ - { - id: 'f6a0a73a-5ee6-442e-b764-39b682471240', - name: 'id', - label: 'id', - type: FieldMetadataType.Uuid, - createdAt: '2024-01-01T00:00:00.000Z', - updatedAt: '2024-01-01T00:00:00.000Z', - isActive: true, - }, - ], - indexMetadatas: [], - }, -]; - -describe('useMultiObjectSearch', () => { - const selectedObjectRecordIds: SelectedObjectRecordId[] = [ - { objectNameSingular: 'object1', id: '1' }, - { objectNameSingular: 'object2', id: '2' }, - ]; - const searchFilterValue = 'searchValue'; - const limit = 5; - const excludedObjectRecordIds: SelectedObjectRecordId[] = [ - { objectNameSingular: 'object3', id: '3' }, - { objectNameSingular: 'object4', id: '4' }, - ]; - const excludedObjects: CoreObjectNameSingular[] = []; - - const selectedObjectRecords: ObjectRecordForSelect[] = [ - { - objectMetadataItem: objectData[0], - record: { - __typename: 'ObjectRecord', - id: '1', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - }, - recordIdentifier: { - id: '1', - name: 'name', - }, - }, - ]; - const selectedObjectRecordsLoading = false; - - const selectedAndMatchesSearchFilterObjectRecords: ObjectRecordForSelect[] = - []; - const selectedAndMatchesSearchFilterObjectRecordsLoading = false; - - const toSelectAndMatchesSearchFilterObjectRecords: ObjectRecordForSelect[] = - []; - const toSelectAndMatchesSearchFilterObjectRecordsLoading = false; - - beforeEach(() => { - (useMultiObjectSearchSelectedItemsQuery as jest.Mock).mockReturnValue({ - selectedObjectRecords, - selectedObjectRecordsLoading, - }); - - ( - useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery as jest.Mock - ).mockReturnValue({ - selectedAndMatchesSearchFilterObjectRecords, - selectedAndMatchesSearchFilterObjectRecordsLoading, - }); - - ( - useMultiObjectSearchMatchesSearchFilterAndToSelectQuery as jest.Mock - ).mockReturnValue({ - toSelectAndMatchesSearchFilterObjectRecords, - toSelectAndMatchesSearchFilterObjectRecordsLoading, - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should return the correct object records and loading state', () => { - const { result } = renderHook(() => - useMultiObjectSearch({ - searchFilterValue, - selectedObjectRecordIds, - limit, - excludedObjectRecordIds, - excludedObjects, - }), - ); - - const expected: MultiObjectSearch = { - selectedObjectRecords, - filteredSelectedObjectRecords: - selectedAndMatchesSearchFilterObjectRecords, - objectRecordsToSelect: toSelectAndMatchesSearchFilterObjectRecords, - loading: - selectedAndMatchesSearchFilterObjectRecordsLoading || - toSelectAndMatchesSearchFilterObjectRecordsLoading || - selectedObjectRecordsLoading, - }; - - expect(result.current).toEqual(expected); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearch.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearch.ts deleted file mode 100644 index 8651e7f428f8..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearch.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery'; -import { useMultiObjectSearchMatchesSearchFilterAndToSelectQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery'; -import { useMultiObjectSearchSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery'; -import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; - -export const MULTI_OBJECT_SEARCH_REQUEST_LIMIT = 5; - -export type ObjectRecordForSelect = { - objectMetadataItem: ObjectMetadataItem; - record: ObjectRecord; - recordIdentifier: ObjectRecordIdentifier; -}; - -export type SelectedObjectRecordId = { - objectNameSingular: string; - id: string; -}; - -export type MultiObjectSearch = { - selectedObjectRecords: ObjectRecordForSelect[]; - filteredSelectedObjectRecords: ObjectRecordForSelect[]; - objectRecordsToSelect: ObjectRecordForSelect[]; - loading: boolean; -}; - -export const useMultiObjectSearch = ({ - searchFilterValue, - selectedObjectRecordIds, - limit, - excludedObjectRecordIds = [], - excludedObjects, -}: { - searchFilterValue: string; - selectedObjectRecordIds: SelectedObjectRecordId[]; - limit?: number; - excludedObjectRecordIds?: SelectedObjectRecordId[]; - excludedObjects?: CoreObjectNameSingular[]; -}): MultiObjectSearch => { - const { selectedObjectRecords, selectedObjectRecordsLoading } = - useMultiObjectSearchSelectedItemsQuery({ - selectedObjectRecordIds, - }); - - const { - selectedAndMatchesSearchFilterObjectRecords, - selectedAndMatchesSearchFilterObjectRecordsLoading, - } = useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery({ - searchFilterValue, - selectedObjectRecordIds, - limit, - }); - - const { - toSelectAndMatchesSearchFilterObjectRecords, - toSelectAndMatchesSearchFilterObjectRecordsLoading, - } = useMultiObjectSearchMatchesSearchFilterAndToSelectQuery({ - excludedObjects, - excludedObjectRecordIds, - searchFilterValue, - selectedObjectRecordIds, - limit, - }); - - return { - selectedObjectRecords, - filteredSelectedObjectRecords: selectedAndMatchesSearchFilterObjectRecords, - objectRecordsToSelect: toSelectAndMatchesSearchFilterObjectRecords, - loading: - selectedAndMatchesSearchFilterObjectRecordsLoading || - toSelectAndMatchesSearchFilterObjectRecordsLoading || - selectedObjectRecordsLoading, - }; -}; 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 deleted file mode 100644 index b69ef1f40c6d..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { useQuery } from '@apollo/client'; -import { isNonEmptyArray } from '@sniptt/guards'; -import { useRecoilValue } from 'recoil'; - -import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; -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 { 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, - limit, -}: { - selectedObjectRecordIds: SelectedObjectRecordId[]; - searchFilterValue: string; - limit?: number; -}) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - - const objectMetadataItemsUsedInSelectedIdsQuery = useMemo( - () => - objectMetadataItems.filter(({ nameSingular }) => { - return selectedObjectRecordIds.some(({ objectNameSingular }) => { - return objectNameSingular === nameSingular; - }); - }), - [objectMetadataItems, selectedObjectRecordIds], - ); - - const selectedAndMatchesSearchFilterTextFilterPerMetadataItem = - Object.fromEntries( - objectMetadataItems - .map(({ nameSingular }) => { - const selectedIds = selectedObjectRecordIds - .filter( - ({ objectNameSingular }) => objectNameSingular === nameSingular, - ) - .map(({ id }) => id); - - if (!isNonEmptyArray(selectedIds)) return null; - - return [ - `filter${capitalize(nameSingular)}`, - { - id: { - in: selectedIds, - }, - }, - ]; - }) - .filter(isDefined), - ); - - const { limitPerMetadataItem } = useLimitPerMetadataItem({ - objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery, - limit, - }); - - const multiSelectSearchQueryForSelectedIds = - useGenerateCombinedSearchRecordsQuery({ - operationSignatures: objectMetadataItemsUsedInSelectedIdsQuery.map( - (objectMetadataItem) => ({ - objectNameSingular: objectMetadataItem.nameSingular, - variables: {}, - }), - ), - }); - - const { - loading: selectedAndMatchesSearchFilterObjectRecordsLoading, - data: selectedAndMatchesSearchFilterObjectRecordsQueryResult, - } = useQuery( - multiSelectSearchQueryForSelectedIds ?? EMPTY_QUERY, - { - variables: { - search: searchFilterValue, - ...selectedAndMatchesSearchFilterTextFilterPerMetadataItem, - ...limitPerMetadataItem, - }, - skip: !isDefined(multiSelectSearchQueryForSelectedIds), - }, - ); - - const { - objectRecordForSelectArray: selectedAndMatchesSearchFilterObjectRecords, - } = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ - multiObjectRecordsQueryResult: formatSearchResults( - selectedAndMatchesSearchFilterObjectRecordsQueryResult, - ), - }); - - return { - selectedAndMatchesSearchFilterObjectRecordsLoading, - selectedAndMatchesSearchFilterObjectRecords, - }; -}; 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 deleted file mode 100644 index b94fb0f99c1b..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { useQuery } from '@apollo/client'; -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 { 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 { 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'; - -export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ - selectedObjectRecordIds, - excludedObjectRecordIds, - searchFilterValue, - limit, - excludedObjects, -}: { - selectedObjectRecordIds: SelectedObjectRecordId[]; - excludedObjectRecordIds: SelectedObjectRecordId[]; - searchFilterValue: string; - limit?: number; - excludedObjects?: CoreObjectNameSingular[]; -}) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - - const selectableObjectMetadataItems = objectMetadataItems - .filter(({ isSystem, isRemote }) => !isSystem && !isRemote) - .filter(({ nameSingular }) => { - return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular); - }); - - const objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem = - Object.fromEntries( - selectableObjectMetadataItems - .map(({ nameSingular }) => { - const selectedIds = selectedObjectRecordIds - .filter( - ({ objectNameSingular }) => objectNameSingular === nameSingular, - ) - .map(({ id }) => id); - - const excludedIds = excludedObjectRecordIds - .filter( - ({ objectNameSingular }) => objectNameSingular === nameSingular, - ) - .map(({ id }) => id); - - const excludedIdsUnion = [...selectedIds, ...excludedIds]; - const excludedIdsFilter = excludedIdsUnion.length - ? { not: { id: { in: excludedIdsUnion } } } - : undefined; - - return [ - `filter${capitalize(nameSingular)}`, - makeAndFilterVariables([excludedIdsFilter]), - ]; - }) - .filter(isDefined), - ); - const { limitPerMetadataItem } = useLimitPerMetadataItem({ - objectMetadataItems: selectableObjectMetadataItems, - limit, - }); - - const multiSelectQuery = useGenerateCombinedSearchRecordsQuery({ - operationSignatures: selectableObjectMetadataItems.map( - (objectMetadataItem) => ({ - objectNameSingular: objectMetadataItem.nameSingular, - variables: {}, - }), - ), - }); - - const { - loading: toSelectAndMatchesSearchFilterObjectRecordsLoading, - data: toSelectAndMatchesSearchFilterObjectRecordsQueryResult, - } = useQuery(multiSelectQuery ?? EMPTY_QUERY, { - variables: { - search: searchFilterValue, - ...objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem, - ...limitPerMetadataItem, - }, - skip: !isDefined(multiSelectQuery), - }); - - const { - objectRecordForSelectArray: toSelectAndMatchesSearchFilterObjectRecords, - } = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ - multiObjectRecordsQueryResult: formatSearchResults( - toSelectAndMatchesSearchFilterObjectRecordsQueryResult, - ), - }); - - return { - toSelectAndMatchesSearchFilterObjectRecordsLoading, - toSelectAndMatchesSearchFilterObjectRecords, - }; -}; From d5f9967b7c60b1e6bd6cf391e8ce429be8130e1d Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 24 Oct 2024 14:09:49 +0200 Subject: [PATCH 12/15] Fix after rebase --- ...ctRecordMultiSelectComponentFamilyState.ts | 2 +- ...ctMatchesFilterRecordsIdsComponentState.ts | 2 +- ...ltFormattedAsObjectRecordForSelectArray.ts | 2 +- ...archMatchesSearchFilterAndToSelectQuery.ts | 110 ------------------ ...ltiObjectSearchMatchesSearchFilterQuery.ts | 6 +- .../useMultiObjectSearchSelectedItemsQuery.ts | 2 +- 6 files changed, 9 insertions(+), 115 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts index 0e15c962e11b..9918c7fa8414 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts @@ -1,4 +1,4 @@ -import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; +import { ObjectRecordForSelect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; export type ObjectRecordAndSelected = ObjectRecordForSelect & { diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts index 9c083c71eaab..141f9c47eea5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts @@ -1,4 +1,4 @@ -import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; +import { ObjectRecordForSelect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; export const objectRecordMultiSelectMatchesFilterRecordsIdsComponentState = diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts index a1047478a7ba..e6e0780b4397 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts @@ -4,7 +4,7 @@ import { useRecoilValue } from 'recoil'; import { objectMetadataItemsByNamePluralMapSelector } from '@/object-metadata/states/objectMetadataItemsByNamePluralMapSelector'; import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier'; import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection'; -import { ObjectRecordForSelect } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; +import { ObjectRecordForSelect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; import { isDefined } from '~/utils/isDefined'; export type MultiObjectRecordQueryResult = { 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 deleted file mode 100644 index c3150cd44ca8..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { useQuery } from '@apollo/client'; -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 { 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 { 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'; - -export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ - selectedObjectRecordIds, - excludedObjectRecordIds, - searchFilterValue, - limit, - excludedObjects, -}: { - selectedObjectRecordIds: SelectedObjectRecordId[]; - excludedObjectRecordIds: SelectedObjectRecordId[]; - searchFilterValue: string; - limit?: number; - excludedObjects?: CoreObjectNameSingular[]; -}) => { - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - - const selectableObjectMetadataItems = objectMetadataItems - .filter(({ isSystem, isRemote }) => !isSystem && !isRemote) - .filter(({ nameSingular }) => { - return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular); - }) - .filter((object) => - isObjectMetadataItemSearchableInCombinedRequest(object), - ); - - const objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem = - Object.fromEntries( - selectableObjectMetadataItems - .map(({ nameSingular }) => { - const selectedIds = selectedObjectRecordIds - .filter( - ({ objectNameSingular }) => objectNameSingular === nameSingular, - ) - .map(({ id }) => id); - - const excludedIds = excludedObjectRecordIds - .filter( - ({ objectNameSingular }) => objectNameSingular === nameSingular, - ) - .map(({ id }) => id); - - const excludedIdsUnion = [...selectedIds, ...excludedIds]; - const excludedIdsFilter = excludedIdsUnion.length - ? { not: { id: { in: excludedIdsUnion } } } - : undefined; - - return [ - `filter${capitalize(nameSingular)}`, - makeAndFilterVariables([excludedIdsFilter]), - ]; - }) - .filter(isDefined), - ); - const { limitPerMetadataItem } = useLimitPerMetadataItem({ - objectMetadataItems: selectableObjectMetadataItems, - limit, - }); - - const multiSelectQuery = useGenerateCombinedSearchRecordsQuery({ - operationSignatures: selectableObjectMetadataItems.map( - (objectMetadataItem) => ({ - objectNameSingular: objectMetadataItem.nameSingular, - variables: {}, - }), - ), - }); - - const { - loading: toSelectAndMatchesSearchFilterObjectRecordsLoading, - data: toSelectAndMatchesSearchFilterObjectRecordsQueryResult, - } = useQuery(multiSelectQuery ?? EMPTY_QUERY, { - variables: { - search: searchFilterValue, - ...objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem, - ...limitPerMetadataItem, - }, - skip: !isDefined(multiSelectQuery), - }); - - const { - objectRecordForSelectArray: toSelectAndMatchesSearchFilterObjectRecords, - } = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ - multiObjectRecordsQueryResult: formatSearchResults( - toSelectAndMatchesSearchFilterObjectRecordsQueryResult, - ), - }); - - return { - toSelectAndMatchesSearchFilterObjectRecordsLoading, - toSelectAndMatchesSearchFilterObjectRecords, - }; -}; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts index 038f1f22c5ad..1e15e0613433 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts @@ -10,6 +10,7 @@ import { MultiObjectRecordQueryResult, useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray, } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; +import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest'; import { isDefined } from '~/utils/isDefined'; export const formatSearchResults = ( @@ -42,7 +43,10 @@ export const useMultiObjectSearchMatchesSearchFilterQuery = ({ .filter(({ isSystem, isRemote }) => !isSystem && !isRemote) .filter(({ nameSingular }) => { return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular); - }); + }) + .filter((objectMetadataItem) => + isObjectMetadataItemSearchableInCombinedRequest(objectMetadataItem), + ); const { limitPerMetadataItem } = useLimitPerMetadataItem({ objectMetadataItems, diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts index 5656cb77f5b9..38d5be2d03d3 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts @@ -4,12 +4,12 @@ import { useRecoilValue } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; +import { SelectedObjectRecordId } from '@/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect'; 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 { isDefined } from '~/utils/isDefined'; import { capitalize } from '~/utils/string/capitalize'; From 10d42e34a2b6e764dbee816da69aa2b798e20e57 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 24 Oct 2024 14:13:48 +0200 Subject: [PATCH 13/15] remove useless loading state --- ...EditModeMultiRecordsSearchFilterEffect.tsx | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx index dc55185d60c5..c216122c148c 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect.tsx @@ -1,7 +1,6 @@ import { useEffect } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates'; @@ -14,12 +13,6 @@ export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect = const scopeId = useAvailableScopeIdOrThrow( RelationPickerScopeInternalContext, ); - const { recordMultiSelectIsLoadingState } = - useObjectRecordMultiSelectScopedStates(scopeId); - - const setRecordMultiSelectIsLoading = useSetRecoilState( - recordMultiSelectIsLoadingState, - ); const setRecordMultiSelectMatchesFilterRecords = useSetRecoilState( objectRecordMultiSelectMatchesFilterRecordsIdsComponentState({ @@ -38,17 +31,15 @@ export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect = relationPickerSearchFilterState, ); - const { - matchesSearchFilterObjectRecords, - matchesSearchFilterObjectRecordsLoading: loading, - } = useMultiObjectSearchMatchesSearchFilterQuery({ - excludedObjects: [ - CoreObjectNameSingular.Task, - CoreObjectNameSingular.Note, - ], - searchFilterValue: relationPickerSearchFilter, - limit: 10, - }); + const { matchesSearchFilterObjectRecords } = + useMultiObjectSearchMatchesSearchFilterQuery({ + excludedObjects: [ + CoreObjectNameSingular.Task, + CoreObjectNameSingular.Note, + ], + searchFilterValue: relationPickerSearchFilter, + limit: 10, + }); useEffect(() => { setRecordMultiSelectMatchesFilterRecords( @@ -59,9 +50,5 @@ export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect = matchesSearchFilterObjectRecords, ]); - useEffect(() => { - setRecordMultiSelectIsLoading(loading); - }, [loading, setRecordMultiSelectIsLoading]); - return <>; }; From 75d5425f4c9259147ec713b778e4af87936648cc Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 7 Nov 2024 17:59:08 +0400 Subject: [PATCH 14/15] Refactos --- .../ActivityTargetInlineCellEditMode.tsx | 1 - ...onFromManyFieldInputMultiRecordsEffect.tsx | 10 +- ...ctRecordMultiSelectComponentFamilyState.ts | 2 +- ...ctMatchesFilterRecordsIdsComponentState.ts | 2 +- ...etInlineCellEditModeMultiRecordsEffect.tsx | 8 +- .../__tests__/useMultiObjectSearch.test.tsx | 141 ------------------ ...ltFormattedAsObjectRecordForSelectArray.ts | 40 +++-- ...ltiObjectSearchMatchesSearchFilterQuery.ts | 18 +-- .../useMultiObjectSearchSelectedItemsQuery.ts | 2 +- .../formatMultiObjectRecordSearchResults.ts | 16 ++ .../types/ObjectRecordForSelect.ts | 9 ++ .../types/SelectedObjectRecordId.ts | 4 + 12 files changed, 61 insertions(+), 192 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx create mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/utils/formatMultiObjectRecordSearchResults.ts create mode 100644 packages/twenty-front/src/modules/object-record/types/ObjectRecordForSelect.ts create mode 100644 packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts diff --git a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx index aa8fef82ba8e..7719f4576bed 100644 --- a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx +++ b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx @@ -245,7 +245,6 @@ export const ActivityTargetInlineCellEditMode = ({ (activityTarget) => activityTarget.id !== activityTargetToDeleteId, ); - if (isActivityInCreateMode) { upsertActivity({ activity, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx index a527fa057adf..866edfdada99 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect.tsx @@ -3,23 +3,15 @@ import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil'; import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField'; import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState'; import { useRelationPickerEntitiesOptions } from '@/object-record/relation-picker/hooks/useRelationPickerEntitiesOptions'; import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; -import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; +import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -export type ObjectRecordForSelect = { - objectMetadataItem: ObjectMetadataItem; - record: ObjectRecord; - recordIdentifier: ObjectRecordIdentifier; -}; - export const RelationFromManyFieldInputMultiRecordsEffect = () => { const { fieldValue, fieldDefinition } = useRelationField(); const scopeId = useAvailableScopeIdOrThrow( diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts index 9918c7fa8414..e4150ae029c6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState.ts @@ -1,4 +1,4 @@ -import { ObjectRecordForSelect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; +import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { createComponentFamilyState } from '@/ui/utilities/state/component-state/utils/createComponentFamilyState'; export type ObjectRecordAndSelected = ObjectRecordForSelect & { diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts index 141f9c47eea5..bfaebeaa86fa 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState.ts @@ -1,4 +1,4 @@ -import { ObjectRecordForSelect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; +import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; export const objectRecordMultiSelectMatchesFilterRecordsIdsComponentState = diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx index 152f78ecf7d9..26981f3c0396 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect.tsx @@ -7,18 +7,14 @@ import { } from 'recoil'; import { useObjectRecordMultiSelectScopedStates } from '@/activities/hooks/useObjectRecordMultiSelectScopedStates'; -import { ObjectRecordForSelect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; import { objectRecordMultiSelectComponentFamilyState } from '@/object-record/record-field/states/objectRecordMultiSelectComponentFamilyState'; import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; +import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; +import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; -export type SelectedObjectRecordId = { - objectNameSingular: string; - id: string; -}; - export const ActivityTargetInlineCellEditModeMultiRecordsEffect = ({ selectedObjectRecordIds, }: { diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx deleted file mode 100644 index 0f27bd796e54..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectSearch.test.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { - MultiObjectSearch, - ObjectRecordForSelect, - SelectedObjectRecordId, - useMultiObjectSearch, -} from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; -import { useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery'; -import { useMultiObjectSearchMatchesSearchFilterAndToSelectQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery'; -import { useMultiObjectSearchSelectedItemsQuery } from '@/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery'; -import { renderHook } from '@testing-library/react'; -import { FieldMetadataType } from '~/generated/graphql'; - -jest.mock( - '@/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery', -); -jest.mock( - '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery', -); -jest.mock( - '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery', -); - -const objectData: ObjectMetadataItem[] = [ - { - createdAt: 'createdAt', - id: 'id', - isActive: true, - isCustom: true, - isSystem: false, - isRemote: false, - labelPlural: 'labelPlural', - labelSingular: 'labelSingular', - namePlural: 'namePlural', - nameSingular: 'nameSingular', - isLabelSyncedWithName: false, - updatedAt: 'updatedAt', - fields: [ - { - id: 'f6a0a73a-5ee6-442e-b764-39b682471240', - name: 'id', - label: 'id', - type: FieldMetadataType.Uuid, - createdAt: '2024-01-01T00:00:00.000Z', - updatedAt: '2024-01-01T00:00:00.000Z', - isActive: true, - }, - ], - indexMetadatas: [], - }, -]; - -describe('useMultiObjectSearch', () => { - const selectedObjectRecordIds: SelectedObjectRecordId[] = [ - { objectNameSingular: 'object1', id: '1' }, - { objectNameSingular: 'object2', id: '2' }, - ]; - const searchFilterValue = 'searchValue'; - const limit = 5; - const excludedObjectRecordIds: SelectedObjectRecordId[] = [ - { objectNameSingular: 'object3', id: '3' }, - { objectNameSingular: 'object4', id: '4' }, - ]; - const excludedObjects: CoreObjectNameSingular[] = []; - - const selectedObjectRecords: ObjectRecordForSelect[] = [ - { - objectMetadataItem: objectData[0], - record: { - __typename: 'ObjectRecord', - id: '1', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - }, - recordIdentifier: { - id: '1', - name: 'name', - }, - }, - ]; - const selectedObjectRecordsLoading = false; - - const selectedAndMatchesSearchFilterObjectRecords: ObjectRecordForSelect[] = - []; - const selectedAndMatchesSearchFilterObjectRecordsLoading = false; - - const toSelectAndMatchesSearchFilterObjectRecords: ObjectRecordForSelect[] = - []; - const toSelectAndMatchesSearchFilterObjectRecordsLoading = false; - - beforeEach(() => { - (useMultiObjectSearchSelectedItemsQuery as jest.Mock).mockReturnValue({ - selectedObjectRecords, - selectedObjectRecordsLoading, - }); - - ( - useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery as jest.Mock - ).mockReturnValue({ - selectedAndMatchesSearchFilterObjectRecords, - selectedAndMatchesSearchFilterObjectRecordsLoading, - }); - - ( - useMultiObjectSearchMatchesSearchFilterAndToSelectQuery as jest.Mock - ).mockReturnValue({ - toSelectAndMatchesSearchFilterObjectRecords, - toSelectAndMatchesSearchFilterObjectRecordsLoading, - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should return the correct object records and loading state', () => { - const { result } = renderHook(() => - useMultiObjectSearch({ - searchFilterValue, - selectedObjectRecordIds, - limit, - excludedObjectRecordIds, - excludedObjects, - }), - ); - - const expected: MultiObjectSearch = { - selectedObjectRecords, - filteredSelectedObjectRecords: - selectedAndMatchesSearchFilterObjectRecords, - objectRecordsToSelect: toSelectAndMatchesSearchFilterObjectRecords, - loading: - selectedAndMatchesSearchFilterObjectRecordsLoading || - toSelectAndMatchesSearchFilterObjectRecordsLoading || - selectedObjectRecordsLoading, - }; - - expect(result.current).toEqual(expected); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts index e6e0780b4397..ebac778c44f9 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.ts @@ -4,7 +4,8 @@ import { useRecoilValue } from 'recoil'; import { objectMetadataItemsByNamePluralMapSelector } from '@/object-metadata/states/objectMetadataItemsByNamePluralMapSelector'; import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier'; import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection'; -import { ObjectRecordForSelect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; +import { formatMultiObjectRecordSearchResults } from '@/object-record/relation-picker/utils/formatMultiObjectRecordSearchResults'; +import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect'; import { isDefined } from '~/utils/isDefined'; export type MultiObjectRecordQueryResult = { @@ -24,25 +25,34 @@ export const useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArr objectMetadataItemsByNamePluralMapSelector, ); + const formattedMultiObjectRecordsQueryResult = useMemo(() => { + return formatMultiObjectRecordSearchResults( + multiObjectRecordsQueryResult, + ); + }, [multiObjectRecordsQueryResult]); + const objectRecordForSelectArray = useMemo(() => { - return Object.entries(multiObjectRecordsQueryResult ?? {}).flatMap( - ([namePlural, objectRecordConnection]) => { - const objectMetadataItem = - objectMetadataItemsByNamePluralMap.get(namePlural); + return Object.entries( + formattedMultiObjectRecordsQueryResult ?? {}, + ).flatMap(([namePlural, objectRecordConnection]) => { + const objectMetadataItem = + objectMetadataItemsByNamePluralMap.get(namePlural); - if (!isDefined(objectMetadataItem)) return []; + if (!isDefined(objectMetadataItem)) return []; - return objectRecordConnection.edges.map(({ node }) => ({ + return objectRecordConnection.edges.map(({ node }) => ({ + objectMetadataItem, + record: node, + recordIdentifier: getObjectRecordIdentifier({ objectMetadataItem, record: node, - recordIdentifier: getObjectRecordIdentifier({ - objectMetadataItem, - record: node, - }), - })) as ObjectRecordForSelect[]; - }, - ); - }, [multiObjectRecordsQueryResult, objectMetadataItemsByNamePluralMap]); + }), + })) as ObjectRecordForSelect[]; + }); + }, [ + formattedMultiObjectRecordsQueryResult, + objectMetadataItemsByNamePluralMap, + ]); return { objectRecordForSelectArray, diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts index 1e15e0613433..a8bdfec35419 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterQuery.ts @@ -13,21 +13,6 @@ import { import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest'; import { isDefined } from '~/utils/isDefined'; -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 useMultiObjectSearchMatchesSearchFilterQuery = ({ searchFilterValue, limit, @@ -79,9 +64,8 @@ export const useMultiObjectSearchMatchesSearchFilterQuery = ({ const { objectRecordForSelectArray: matchesSearchFilterObjectRecords } = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ - multiObjectRecordsQueryResult: formatSearchResults( + multiObjectRecordsQueryResult: matchesSearchFilterObjectRecordsQueryResult, - ), }); return { diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts index 38d5be2d03d3..a4fd29ddcbb5 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchSelectedItemsQuery.ts @@ -4,13 +4,13 @@ import { useRecoilValue } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; -import { SelectedObjectRecordId } from '@/object-record/relation-picker/components/ActivityTargetInlineCellEditModeMultiRecordsEffect'; import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem'; import { MultiObjectRecordQueryResult, useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray, } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; import { useOrderByFieldPerMetadataItem } from '@/object-record/relation-picker/hooks/useOrderByFieldPerMetadataItem'; +import { SelectedObjectRecordId } from '@/object-record/types/SelectedObjectRecordId'; import { isDefined } from '~/utils/isDefined'; import { capitalize } from '~/utils/string/capitalize'; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/utils/formatMultiObjectRecordSearchResults.ts b/packages/twenty-front/src/modules/object-record/relation-picker/utils/formatMultiObjectRecordSearchResults.ts new file mode 100644 index 000000000000..318f12ab7548 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/relation-picker/utils/formatMultiObjectRecordSearchResults.ts @@ -0,0 +1,16 @@ +import { MultiObjectRecordQueryResult } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; + +export const formatMultiObjectRecordSearchResults = ( + searchResults: MultiObjectRecordQueryResult | undefined | null, +): 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); +}; diff --git a/packages/twenty-front/src/modules/object-record/types/ObjectRecordForSelect.ts b/packages/twenty-front/src/modules/object-record/types/ObjectRecordForSelect.ts new file mode 100644 index 000000000000..a1266fb6f702 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/types/ObjectRecordForSelect.ts @@ -0,0 +1,9 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; + +export type ObjectRecordForSelect = { + objectMetadataItem: ObjectMetadataItem; + record: ObjectRecord; + recordIdentifier: ObjectRecordIdentifier; +}; diff --git a/packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts b/packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts new file mode 100644 index 000000000000..f2cac82a9515 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts @@ -0,0 +1,4 @@ +export type SelectedObjectRecordId = { + objectNameSingular: string; + id: string; + }; \ No newline at end of file From ccfc39e085b3e30a8de9e10c9879d1a4f247f9e6 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 7 Nov 2024 18:25:58 +0400 Subject: [PATCH 15/15] fix lint --- .../modules/object-record/types/SelectedObjectRecordId.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts b/packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts index f2cac82a9515..2c9bb2353892 100644 --- a/packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts +++ b/packages/twenty-front/src/modules/object-record/types/SelectedObjectRecordId.ts @@ -1,4 +1,4 @@ export type SelectedObjectRecordId = { - objectNameSingular: string; - id: string; - }; \ No newline at end of file + objectNameSingular: string; + id: string; +};