Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use search in multi object pickers #7909

Merged
merged 15 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider adding a non-null constraint to the $search parameter if it's required

) {
${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')}
}
`;
};
Original file line number Diff line number Diff line change
@@ -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,
)
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Function looks correct, but consider adding a comment explaining the logic behind determining searchability

};
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Comment on lines +18 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: consider moving this utility function to a separate file for better code organization


export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({
selectedObjectRecordIds,
searchFilterValue,
Expand All @@ -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 =
Expand All @@ -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,
Expand All @@ -93,15 +91,25 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({
),
});

const multiSelectSearchQueryForSelectedIds =
useGenerateCombinedSearchRecordsQuery({
operationSignatures: objectMetadataItemsUsedInSelectedIdsQuery.map(
(objectMetadataItem) => ({
objectNameSingular: objectMetadataItem.nameSingular,
variables: {},
}),
),
});

const {
loading: selectedAndMatchesSearchFilterObjectRecordsLoading,
data: selectedAndMatchesSearchFilterObjectRecordsQueryResult,
} = useQuery<MultiObjectRecordQueryResult>(
multiSelectQueryForSelectedIds ?? EMPTY_QUERY,
multiSelectSearchQueryForSelectedIds ?? EMPTY_QUERY,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: multiSelectSearchQueryForSelectedIds is used here, but multiSelectQueryForSelectedIds is used in the skip condition. This inconsistency could lead to unexpected behavior

{
variables: {
search: searchFilterValue,
...selectedAndMatchesSearchFilterTextFilterPerMetadataItem,
...orderByFieldPerMetadataItem,
...limitPerMetadataItem,
},
skip: !isDefined(multiSelectQueryForSelectedIds),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: ensure multiSelectQueryForSelectedIds is still needed, or if it should be replaced with multiSelectSearchQueryForSelectedIds

Expand All @@ -111,8 +119,9 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({
const {
objectRecordForSelectArray: selectedAndMatchesSearchFilterObjectRecords,
} = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
multiObjectRecordsQueryResult:
multiObjectRecordsQueryResult: formatSearchResults(
selectedAndMatchesSearchFilterObjectRecordsQueryResult,
),
});

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -38,12 +37,6 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular);
});

const { searchFilterPerMetadataItemNameSingular } =
useSearchFilterPerMetadataItem({
objectMetadataItems: selectableObjectMetadataItems,
searchFilterValue,
});

const objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem =
Weiko marked this conversation as resolved.
Show resolved Hide resolved
Object.fromEntries(
selectableObjectMetadataItems
Expand All @@ -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,
Expand All @@ -101,8 +84,8 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
data: toSelectAndMatchesSearchFilterObjectRecordsQueryResult,
} = useQuery<MultiObjectRecordQueryResult>(multiSelectQuery ?? EMPTY_QUERY, {
variables: {
search: searchFilterValue,
...objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem,
...orderByFieldPerMetadataItem,
...limitPerMetadataItem,
},
skip: !isDefined(multiSelectQuery),
Expand All @@ -111,8 +94,9 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({
const {
objectRecordForSelectArray: toSelectAndMatchesSearchFilterObjectRecords,
} = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
multiObjectRecordsQueryResult:
multiObjectRecordsQueryResult: formatSearchResults(
toSelectAndMatchesSearchFilterObjectRecordsQueryResult,
),
});

return {
Expand Down
Loading
Loading