Use search in multi object pickers (#7909)
Fixes https://github.com/twentyhq/twenty/issues/3298. We still have some existing glitches in the picker yet to fix. --------- Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
@ -4,18 +4,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 +41,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,38 +63,25 @@ 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,
|
||||
});
|
||||
|
||||
const multiSelectQueryForSelectedIds =
|
||||
useGenerateCombinedFindManyRecordsQuery({
|
||||
const multiSelectSearchQueryForSelectedIds =
|
||||
useGenerateCombinedSearchRecordsQuery({
|
||||
operationSignatures: objectMetadataItemsUsedInSelectedIdsQuery.map(
|
||||
(objectMetadataItem) => ({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
@ -97,22 +94,23 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({
|
||||
loading: selectedAndMatchesSearchFilterObjectRecordsLoading,
|
||||
data: selectedAndMatchesSearchFilterObjectRecordsQueryResult,
|
||||
} = useQuery<MultiObjectRecordQueryResult>(
|
||||
multiSelectQueryForSelectedIds ?? EMPTY_QUERY,
|
||||
multiSelectSearchQueryForSelectedIds ?? EMPTY_QUERY,
|
||||
{
|
||||
variables: {
|
||||
search: searchFilterValue,
|
||||
...selectedAndMatchesSearchFilterTextFilterPerMetadataItem,
|
||||
...orderByFieldPerMetadataItem,
|
||||
...limitPerMetadataItem,
|
||||
},
|
||||
skip: !isDefined(multiSelectQueryForSelectedIds),
|
||||
skip: !isDefined(multiSelectSearchQueryForSelectedIds),
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
objectRecordForSelectArray: selectedAndMatchesSearchFilterObjectRecords,
|
||||
} = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
||||
multiObjectRecordsQueryResult:
|
||||
multiObjectRecordsQueryResult: formatSearchResults(
|
||||
selectedAndMatchesSearchFilterObjectRecordsQueryResult,
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@ -4,15 +4,15 @@ 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 { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest';
|
||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
@ -36,13 +36,10 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
|
||||
.filter(({ isSystem, isRemote }) => !isSystem && !isRemote)
|
||||
.filter(({ nameSingular }) => {
|
||||
return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular);
|
||||
});
|
||||
|
||||
const { searchFilterPerMetadataItemNameSingular } =
|
||||
useSearchFilterPerMetadataItem({
|
||||
objectMetadataItems: selectableObjectMetadataItems,
|
||||
searchFilterValue,
|
||||
});
|
||||
})
|
||||
.filter((object) =>
|
||||
isObjectMetadataItemSearchableInCombinedRequest(object),
|
||||
);
|
||||
|
||||
const objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem =
|
||||
Object.fromEntries(
|
||||
@ -65,29 +62,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 +88,8 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
|
||||
data: toSelectAndMatchesSearchFilterObjectRecordsQueryResult,
|
||||
} = useQuery<MultiObjectRecordQueryResult>(multiSelectQuery ?? EMPTY_QUERY, {
|
||||
variables: {
|
||||
search: searchFilterValue,
|
||||
...objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem,
|
||||
...orderByFieldPerMetadataItem,
|
||||
...limitPerMetadataItem,
|
||||
},
|
||||
skip: !isDefined(multiSelectQuery),
|
||||
@ -111,8 +98,9 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
|
||||
const {
|
||||
objectRecordForSelectArray: toSelectAndMatchesSearchFilterObjectRecords,
|
||||
} = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
||||
multiObjectRecordsQueryResult:
|
||||
multiObjectRecordsQueryResult: formatSearchResults(
|
||||
toSelectAndMatchesSearchFilterObjectRecordsQueryResult,
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@ -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<RecordGqlOperationFilter>(
|
||||
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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user