From 4acb7f41e1196115fec60be27ec64d2a82d46ec3 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Thu, 16 Nov 2023 12:34:23 +0100 Subject: [PATCH] Fix/company picker v2 (#2535) Fixed company picker V2 Fixed picker search hook filters / where clause Fixed OrderBy / SortOrder type Fixed set relation to null --- .../companies/components/CompanyPicker.tsx | 34 ++++++++- .../useMapFieldMetadataToGraphQLQuery.ts | 14 +++- .../useGenerateFindManyCustomObjectsQuery.ts | 4 +- .../hooks/useFilteredSearchEntityQueryV2.ts | 76 +++++++++++-------- .../components/SingleEntitySelectBase.tsx | 4 - .../ui/object/field/hooks/usePersistField.ts | 7 +- .../utils/getEntityChipFromFieldMetadata.ts | 6 ++ .../input/components/RelationFieldInput.tsx | 3 +- .../modules/users/components/UserPicker.tsx | 10 +-- 9 files changed, 98 insertions(+), 60 deletions(-) diff --git a/front/src/modules/companies/components/CompanyPicker.tsx b/front/src/modules/companies/components/CompanyPicker.tsx index 9ecc7b509..bf523fcdb 100644 --- a/front/src/modules/companies/components/CompanyPicker.tsx +++ b/front/src/modules/companies/components/CompanyPicker.tsx @@ -1,11 +1,14 @@ import { useEffect } from 'react'; +import { useQuery } from '@apollo/client'; +import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; +import { useFilteredSearchEntityQueryV2 } from '@/search/hooks/useFilteredSearchEntityQueryV2'; import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect'; import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; +import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; - -import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery'; +import { getLogoUrlFromDomainName } from '~/utils'; export type CompanyPickerProps = { companyId: string | null; @@ -29,9 +32,32 @@ export const CompanyPicker = ({ } }, [initialSearchFilter, setRelationPickerSearchFilter]); - const companies = useFilteredSearchCompanyQuery({ - searchFilter: relationPickerSearchFilter, + const { findManyQuery } = useFindOneObjectMetadataItem({ + objectNamePlural: 'companiesV2', + }); + + const useFindManyCompanies = (options: any) => + useQuery(findManyQuery, options); + + const companies = useFilteredSearchEntityQueryV2({ + queryHook: useFindManyCompanies, + filters: [ + { + fieldNames: ['name'], + filter: relationPickerSearchFilter, + }, + ], + orderByField: 'name', + mappingFunction: (company) => ({ + entityType: Entity.Company, + id: company.id, + name: company.name, + avatarType: 'squared', + avatarUrl: getLogoUrlFromDomainName(company.domainName), + originalEntity: company, + }), selectedIds: companyId ? [companyId] : [], + objectNamePlural: 'companiesV2', }); const handleEntitySelected = async ( diff --git a/front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts b/front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts index 5ef2e9a90..be5970f70 100644 --- a/front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts +++ b/front/src/modules/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery.ts @@ -42,7 +42,7 @@ export const useMapFieldMetadataToGraphQLQuery = () => { return `${field.name} { id - ${relationMetadataItem?.fields + ${(relationMetadataItem?.fields ?? []) .filter((field) => field.type !== 'RELATION') .map((field) => field.name) .join('\n')} @@ -51,11 +51,21 @@ export const useMapFieldMetadataToGraphQLQuery = () => { fieldType === 'RELATION' && field.fromRelationMetadata?.relationType === 'ONE_TO_MANY' ) { + const relationMetadataItem = objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.id === + (field.fromRelationMetadata as any)?.toObjectMetadata?.id, + ); + return `${field.name} { edges { node { - id + id + ${(relationMetadataItem?.fields ?? []) + .filter((field) => field.type !== 'RELATION') + .map((field) => field.name) + .join('\n')} } } }`; diff --git a/front/src/modules/object-record/utils/useGenerateFindManyCustomObjectsQuery.ts b/front/src/modules/object-record/utils/useGenerateFindManyCustomObjectsQuery.ts index 62dde1aa9..924c7bce8 100644 --- a/front/src/modules/object-record/utils/useGenerateFindManyCustomObjectsQuery.ts +++ b/front/src/modules/object-record/utils/useGenerateFindManyCustomObjectsQuery.ts @@ -23,10 +23,10 @@ export const useGenerateFindManyCustomObjectsQuery = ({ objectMetadataItem.nameSingular, )}FilterInput, $orderBy: ${capitalize( objectMetadataItem.nameSingular, - )}OrderByInput, $lastCursor: String) { + )}OrderByInput, $lastCursor: String, $limit: Float = 30) { ${ objectMetadataItem.namePlural - }(filter: $filter, orderBy: $orderBy, first: 30, after: $lastCursor){ + }(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){ edges { node { id diff --git a/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts b/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts index bc5995253..89f8dd5f1 100644 --- a/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts +++ b/front/src/modules/search/hooks/useFilteredSearchEntityQueryV2.ts @@ -1,9 +1,10 @@ import { QueryHookOptions, QueryResult } from '@apollo/client'; +import { isNonEmptyString } from '@sniptt/guards'; import { mapPaginatedObjectsToObjects } from '@/object-record/utils/mapPaginatedObjectsToObjects'; import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; -import { QueryMode, SortOrder } from '~/generated/graphql'; +import { isDefined } from '~/utils/isDefined'; type SelectStringKeys = NonNullable< { @@ -23,7 +24,13 @@ type ExtractEntityTypeFromQueryResponse = T extends { type SearchFilter = { fieldNames: string[]; filter: string | number }; -const DEFAULT_SEARCH_REQUEST_LIMIT = 10; +export type OrderBy = + | 'AscNullsLast' + | 'DescNullsLast' + | 'AscNullsFirst' + | 'DescNullsFirst'; + +const DEFAULT_SEARCH_REQUEST_LIMIT = 30; // TODO: use this for all search queries, because we need selectedEntities and entitiesToSelect each time we want to search // Filtered entities to select are @@ -31,7 +38,7 @@ export const useFilteredSearchEntityQueryV2 = ({ queryHook, orderByField, filters, - sortOrder = SortOrder.Asc, + sortOrder = 'AscNullsLast', selectedIds, mappingFunction, limit, @@ -43,7 +50,7 @@ export const useFilteredSearchEntityQueryV2 = ({ ) => QueryResult; orderByField: string; filters: SearchFilter[]; - sortOrder?: SortOrder; + sortOrder?: OrderBy; selectedIds: string[]; mappingFunction: (entity: any) => EntityForSelect; limit?: number; @@ -53,7 +60,7 @@ export const useFilteredSearchEntityQueryV2 = ({ const { loading: selectedEntitiesLoading, data: selectedEntitiesData } = queryHook({ variables: { - where: { + filter: { id: { in: selectedIds, }, @@ -64,30 +71,39 @@ export const useFilteredSearchEntityQueryV2 = ({ } as any, }); - const searchFilter = filters.map(({ fieldNames, filter }) => { - return { - OR: fieldNames.map((fieldName) => ({ - [fieldName]: { - startsWith: `%${filter}%`, - mode: QueryMode.Insensitive, - }, - })), - }; - }); + const searchFilter = filters + .map(({ fieldNames, filter }) => { + if (!isNonEmptyString(filter)) { + return undefined; + } + + return { + or: fieldNames.map((fieldName) => ({ + [fieldName]: { + like: `%${filter}%`, + // TODO: fix mode + // mode: QueryMode.Insensitive, + }, + })), + }; + }) + .filter(isDefined); const { loading: filteredSelectedEntitiesLoading, data: filteredSelectedEntitiesData, } = queryHook({ variables: { - where: { - AND: [ + filter: { + and: [ { - AND: searchFilter, + and: searchFilter, }, { - id: { - in: selectedIds, + not: { + id: { + in: selectedIds, + }, }, }, ], @@ -101,14 +117,16 @@ export const useFilteredSearchEntityQueryV2 = ({ const { loading: entitiesToSelectLoading, data: entitiesToSelectData } = queryHook({ variables: { - where: { - AND: [ + filter: { + and: [ { - AND: searchFilter, + and: searchFilter, }, { - id: { - notIn: [...selectedIds, ...excludeEntityIds], + not: { + id: { + in: [...selectedIds, ...excludeEntityIds], + }, }, }, ], @@ -120,14 +138,6 @@ export const useFilteredSearchEntityQueryV2 = ({ } as any, }); - console.log({ - selectedEntitiesData, - test: mapPaginatedObjectsToObjects({ - objectNamePlural: objectNamePlural, - pagedObjects: selectedEntitiesData, - }), - }); - return { selectedEntities: mapPaginatedObjectsToObjects({ objectNamePlural: objectNamePlural, diff --git a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx index 94aaaa470..a4310090b 100644 --- a/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx +++ b/front/src/modules/ui/input/relation-picker/components/SingleEntitySelectBase.tsx @@ -65,10 +65,6 @@ export const SingleEntitySelectBase = < assertNotNull(entity) && isNonEmptyString(entity.name), ); - console.log({ - entitiesInDropdown, - }); - const { preselectedOptionId, resetScroll } = useEntitySelectScroll({ selectableOptionIds: [ EmptyButtonId, diff --git a/front/src/modules/ui/object/field/hooks/usePersistField.ts b/front/src/modules/ui/object/field/hooks/usePersistField.ts index 853f2a70b..90b797e7e 100644 --- a/front/src/modules/ui/object/field/hooks/usePersistField.ts +++ b/front/src/modules/ui/object/field/hooks/usePersistField.ts @@ -105,16 +105,11 @@ export const usePersistField = () => { valueToPersist, ); - console.log({ - fieldName, - valueToPersist, - }); - updateEntity?.({ variables: { where: { id: entityId }, data: { - [fieldName]: valueToPersist?.id, + [fieldName]: valueToPersist?.id ?? null, }, }, }); diff --git a/front/src/modules/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata.ts b/front/src/modules/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata.ts index 0255b9fb0..91d2c2991 100644 --- a/front/src/modules/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata.ts +++ b/front/src/modules/ui/object/field/meta-types/display/utils/getEntityChipFromFieldMetadata.ts @@ -1,6 +1,7 @@ import { EntityChipProps } from '@/ui/display/chip/components/EntityChip'; import { FieldDefinition } from '@/ui/object/field/types/FieldDefinition'; import { FieldRelationMetadata } from '@/ui/object/field/types/FieldMetadata'; +import { getLogoUrlFromDomainName } from '~/utils'; export const getEntityChipFromFieldMetadata = ( fieldDefinition: FieldDefinition, @@ -21,6 +22,11 @@ export const getEntityChipFromFieldMetadata = ( // TODO: use every if (fieldName === 'accountOwner' && fieldValue) { chipValue.name = fieldValue.firstName + ' ' + fieldValue.lastName; + } else if (fieldName === 'company' && fieldValue) { + chipValue.name = fieldValue.name; + chipValue.pictureUrl = getLogoUrlFromDomainName(fieldValue.domainName); + } else if (fieldName === 'person' && fieldValue) { + chipValue.name = fieldValue.firstName + ' ' + fieldValue.lastName; } return chipValue; diff --git a/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx b/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx index 6add485cd..dff16905a 100644 --- a/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx +++ b/front/src/modules/ui/object/field/meta-types/input/components/RelationFieldInput.tsx @@ -4,7 +4,6 @@ import styled from '@emotion/styled'; import { CompanyPicker } from '@/companies/components/CompanyPicker'; import { PeoplePicker } from '@/people/components/PeoplePicker'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; -import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { UserPicker } from '@/users/components/UserPicker'; import { usePersistField } from '../../../hooks/usePersistField'; @@ -55,7 +54,7 @@ export const RelationFieldInput = ({ onCancel={onCancel} initialSearchFilter={initialSearchValue} /> - ) : fieldDefinition.metadata.relationType === Entity.Company ? ( + ) : fieldDefinition.metadata.fieldName === 'company' ? ( useQuery(findManyQuery, {}); + const useFindManyWorkspaceMembers = (options: any) => + useQuery(findManyQuery, options); - // TODO: put workspace member const users = useFilteredSearchEntityQueryV2({ queryHook: useFindManyWorkspaceMembers, filters: [ @@ -51,7 +51,7 @@ export const UserPicker = ({ filter: relationPickerSearchFilter, }, ], - orderByField: '', + orderByField: 'firstName', mappingFunction: (workspaceMember) => ({ entityType: Entity.WorkspaceMember, id: workspaceMember.id, @@ -64,10 +64,6 @@ export const UserPicker = ({ objectNamePlural: 'workspaceMembersV2', }); - console.log({ - users, - }); - const handleEntitySelected = async (selectedUser: any | null | undefined) => { onSubmit(selectedUser ?? null); };