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
This commit is contained in:
Lucas Bordeau
2023-11-16 12:34:23 +01:00
committed by GitHub
parent e026b2b6e9
commit 4acb7f41e1
9 changed files with 98 additions and 60 deletions

View File

@ -1,11 +1,14 @@
import { useEffect } from 'react'; 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 { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState'; import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; 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 { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { getLogoUrlFromDomainName } from '~/utils';
import { useFilteredSearchCompanyQuery } from '../hooks/useFilteredSearchCompanyQuery';
export type CompanyPickerProps = { export type CompanyPickerProps = {
companyId: string | null; companyId: string | null;
@ -29,9 +32,32 @@ export const CompanyPicker = ({
} }
}, [initialSearchFilter, setRelationPickerSearchFilter]); }, [initialSearchFilter, setRelationPickerSearchFilter]);
const companies = useFilteredSearchCompanyQuery({ const { findManyQuery } = useFindOneObjectMetadataItem({
searchFilter: relationPickerSearchFilter, 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] : [], selectedIds: companyId ? [companyId] : [],
objectNamePlural: 'companiesV2',
}); });
const handleEntitySelected = async ( const handleEntitySelected = async (

View File

@ -42,7 +42,7 @@ export const useMapFieldMetadataToGraphQLQuery = () => {
return `${field.name} return `${field.name}
{ {
id id
${relationMetadataItem?.fields ${(relationMetadataItem?.fields ?? [])
.filter((field) => field.type !== 'RELATION') .filter((field) => field.type !== 'RELATION')
.map((field) => field.name) .map((field) => field.name)
.join('\n')} .join('\n')}
@ -51,11 +51,21 @@ export const useMapFieldMetadataToGraphQLQuery = () => {
fieldType === 'RELATION' && fieldType === 'RELATION' &&
field.fromRelationMetadata?.relationType === 'ONE_TO_MANY' field.fromRelationMetadata?.relationType === 'ONE_TO_MANY'
) { ) {
const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
(field.fromRelationMetadata as any)?.toObjectMetadata?.id,
);
return `${field.name} return `${field.name}
{ {
edges { edges {
node { node {
id id
${(relationMetadataItem?.fields ?? [])
.filter((field) => field.type !== 'RELATION')
.map((field) => field.name)
.join('\n')}
} }
} }
}`; }`;

View File

@ -23,10 +23,10 @@ export const useGenerateFindManyCustomObjectsQuery = ({
objectMetadataItem.nameSingular, objectMetadataItem.nameSingular,
)}FilterInput, $orderBy: ${capitalize( )}FilterInput, $orderBy: ${capitalize(
objectMetadataItem.nameSingular, objectMetadataItem.nameSingular,
)}OrderByInput, $lastCursor: String) { )}OrderByInput, $lastCursor: String, $limit: Float = 30) {
${ ${
objectMetadataItem.namePlural objectMetadataItem.namePlural
}(filter: $filter, orderBy: $orderBy, first: 30, after: $lastCursor){ }(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
edges { edges {
node { node {
id id

View File

@ -1,9 +1,10 @@
import { QueryHookOptions, QueryResult } from '@apollo/client'; import { QueryHookOptions, QueryResult } from '@apollo/client';
import { isNonEmptyString } from '@sniptt/guards';
import { mapPaginatedObjectsToObjects } from '@/object-record/utils/mapPaginatedObjectsToObjects'; import { mapPaginatedObjectsToObjects } from '@/object-record/utils/mapPaginatedObjectsToObjects';
import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect'; import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { QueryMode, SortOrder } from '~/generated/graphql'; import { isDefined } from '~/utils/isDefined';
type SelectStringKeys<T> = NonNullable< type SelectStringKeys<T> = NonNullable<
{ {
@ -23,7 +24,13 @@ type ExtractEntityTypeFromQueryResponse<T> = T extends {
type SearchFilter = { fieldNames: string[]; filter: string | number }; 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 // TODO: use this for all search queries, because we need selectedEntities and entitiesToSelect each time we want to search
// Filtered entities to select are // Filtered entities to select are
@ -31,7 +38,7 @@ export const useFilteredSearchEntityQueryV2 = ({
queryHook, queryHook,
orderByField, orderByField,
filters, filters,
sortOrder = SortOrder.Asc, sortOrder = 'AscNullsLast',
selectedIds, selectedIds,
mappingFunction, mappingFunction,
limit, limit,
@ -43,7 +50,7 @@ export const useFilteredSearchEntityQueryV2 = ({
) => QueryResult<any, any>; ) => QueryResult<any, any>;
orderByField: string; orderByField: string;
filters: SearchFilter[]; filters: SearchFilter[];
sortOrder?: SortOrder; sortOrder?: OrderBy;
selectedIds: string[]; selectedIds: string[];
mappingFunction: (entity: any) => EntityForSelect; mappingFunction: (entity: any) => EntityForSelect;
limit?: number; limit?: number;
@ -53,7 +60,7 @@ export const useFilteredSearchEntityQueryV2 = ({
const { loading: selectedEntitiesLoading, data: selectedEntitiesData } = const { loading: selectedEntitiesLoading, data: selectedEntitiesData } =
queryHook({ queryHook({
variables: { variables: {
where: { filter: {
id: { id: {
in: selectedIds, in: selectedIds,
}, },
@ -64,30 +71,39 @@ export const useFilteredSearchEntityQueryV2 = ({
} as any, } as any,
}); });
const searchFilter = filters.map(({ fieldNames, filter }) => { const searchFilter = filters
return { .map(({ fieldNames, filter }) => {
OR: fieldNames.map((fieldName) => ({ if (!isNonEmptyString(filter)) {
[fieldName]: { return undefined;
startsWith: `%${filter}%`, }
mode: QueryMode.Insensitive,
}, return {
})), or: fieldNames.map((fieldName) => ({
}; [fieldName]: {
}); like: `%${filter}%`,
// TODO: fix mode
// mode: QueryMode.Insensitive,
},
})),
};
})
.filter(isDefined);
const { const {
loading: filteredSelectedEntitiesLoading, loading: filteredSelectedEntitiesLoading,
data: filteredSelectedEntitiesData, data: filteredSelectedEntitiesData,
} = queryHook({ } = queryHook({
variables: { variables: {
where: { filter: {
AND: [ and: [
{ {
AND: searchFilter, and: searchFilter,
}, },
{ {
id: { not: {
in: selectedIds, id: {
in: selectedIds,
},
}, },
}, },
], ],
@ -101,14 +117,16 @@ export const useFilteredSearchEntityQueryV2 = ({
const { loading: entitiesToSelectLoading, data: entitiesToSelectData } = const { loading: entitiesToSelectLoading, data: entitiesToSelectData } =
queryHook({ queryHook({
variables: { variables: {
where: { filter: {
AND: [ and: [
{ {
AND: searchFilter, and: searchFilter,
}, },
{ {
id: { not: {
notIn: [...selectedIds, ...excludeEntityIds], id: {
in: [...selectedIds, ...excludeEntityIds],
},
}, },
}, },
], ],
@ -120,14 +138,6 @@ export const useFilteredSearchEntityQueryV2 = ({
} as any, } as any,
}); });
console.log({
selectedEntitiesData,
test: mapPaginatedObjectsToObjects({
objectNamePlural: objectNamePlural,
pagedObjects: selectedEntitiesData,
}),
});
return { return {
selectedEntities: mapPaginatedObjectsToObjects({ selectedEntities: mapPaginatedObjectsToObjects({
objectNamePlural: objectNamePlural, objectNamePlural: objectNamePlural,

View File

@ -65,10 +65,6 @@ export const SingleEntitySelectBase = <
assertNotNull(entity) && isNonEmptyString(entity.name), assertNotNull(entity) && isNonEmptyString(entity.name),
); );
console.log({
entitiesInDropdown,
});
const { preselectedOptionId, resetScroll } = useEntitySelectScroll({ const { preselectedOptionId, resetScroll } = useEntitySelectScroll({
selectableOptionIds: [ selectableOptionIds: [
EmptyButtonId, EmptyButtonId,

View File

@ -105,16 +105,11 @@ export const usePersistField = () => {
valueToPersist, valueToPersist,
); );
console.log({
fieldName,
valueToPersist,
});
updateEntity?.({ updateEntity?.({
variables: { variables: {
where: { id: entityId }, where: { id: entityId },
data: { data: {
[fieldName]: valueToPersist?.id, [fieldName]: valueToPersist?.id ?? null,
}, },
}, },
}); });

View File

@ -1,6 +1,7 @@
import { EntityChipProps } from '@/ui/display/chip/components/EntityChip'; import { EntityChipProps } from '@/ui/display/chip/components/EntityChip';
import { FieldDefinition } from '@/ui/object/field/types/FieldDefinition'; import { FieldDefinition } from '@/ui/object/field/types/FieldDefinition';
import { FieldRelationMetadata } from '@/ui/object/field/types/FieldMetadata'; import { FieldRelationMetadata } from '@/ui/object/field/types/FieldMetadata';
import { getLogoUrlFromDomainName } from '~/utils';
export const getEntityChipFromFieldMetadata = ( export const getEntityChipFromFieldMetadata = (
fieldDefinition: FieldDefinition<FieldRelationMetadata>, fieldDefinition: FieldDefinition<FieldRelationMetadata>,
@ -21,6 +22,11 @@ export const getEntityChipFromFieldMetadata = (
// TODO: use every // TODO: use every
if (fieldName === 'accountOwner' && fieldValue) { if (fieldName === 'accountOwner' && fieldValue) {
chipValue.name = fieldValue.firstName + ' ' + fieldValue.lastName; 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; return chipValue;

View File

@ -4,7 +4,6 @@ import styled from '@emotion/styled';
import { CompanyPicker } from '@/companies/components/CompanyPicker'; import { CompanyPicker } from '@/companies/components/CompanyPicker';
import { PeoplePicker } from '@/people/components/PeoplePicker'; import { PeoplePicker } from '@/people/components/PeoplePicker';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; 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 { UserPicker } from '@/users/components/UserPicker';
import { usePersistField } from '../../../hooks/usePersistField'; import { usePersistField } from '../../../hooks/usePersistField';
@ -55,7 +54,7 @@ export const RelationFieldInput = ({
onCancel={onCancel} onCancel={onCancel}
initialSearchFilter={initialSearchValue} initialSearchFilter={initialSearchValue}
/> />
) : fieldDefinition.metadata.relationType === Entity.Company ? ( ) : fieldDefinition.metadata.fieldName === 'company' ? (
<CompanyPicker <CompanyPicker
companyId={initialValue?.id ?? ''} companyId={initialValue?.id ?? ''}
onSubmit={handleSubmit} onSubmit={handleSubmit}

View File

@ -40,9 +40,9 @@ export const UserPicker = ({
objectNamePlural: 'workspaceMembersV2', objectNamePlural: 'workspaceMembersV2',
}); });
const useFindManyWorkspaceMembers = () => useQuery(findManyQuery, {}); const useFindManyWorkspaceMembers = (options: any) =>
useQuery(findManyQuery, options);
// TODO: put workspace member
const users = useFilteredSearchEntityQueryV2({ const users = useFilteredSearchEntityQueryV2({
queryHook: useFindManyWorkspaceMembers, queryHook: useFindManyWorkspaceMembers,
filters: [ filters: [
@ -51,7 +51,7 @@ export const UserPicker = ({
filter: relationPickerSearchFilter, filter: relationPickerSearchFilter,
}, },
], ],
orderByField: '', orderByField: 'firstName',
mappingFunction: (workspaceMember) => ({ mappingFunction: (workspaceMember) => ({
entityType: Entity.WorkspaceMember, entityType: Entity.WorkspaceMember,
id: workspaceMember.id, id: workspaceMember.id,
@ -64,10 +64,6 @@ export const UserPicker = ({
objectNamePlural: 'workspaceMembersV2', objectNamePlural: 'workspaceMembersV2',
}); });
console.log({
users,
});
const handleEntitySelected = async (selectedUser: any | null | undefined) => { const handleEntitySelected = async (selectedUser: any | null | undefined) => {
onSubmit(selectedUser ?? null); onSubmit(selectedUser ?? null);
}; };