Add custom objects to command menu search + use ilike for notes search (#8564)
In this PR - Re-introduce previously used search based on "ILIKE" queries for search on notes since the tsvector search with json text is not working correctly (@charlesBochet) - Add search on custom objects in Command Menu bar (closes https://github.com/twentyhq/twenty/issues/8522) https://github.com/user-attachments/assets/0cc064cf-889d-4f2c-8747-6d8670f35a39
This commit is contained in:
@ -4,7 +4,8 @@ import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
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 { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
|
||||
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
|
||||
@ -31,8 +32,8 @@ export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect =
|
||||
relationPickerSearchFilterState,
|
||||
);
|
||||
|
||||
const { matchesSearchFilterObjectRecords } =
|
||||
useMultiObjectSearchMatchesSearchFilterQuery({
|
||||
const { matchesSearchFilterObjectRecordsQueryResult } =
|
||||
useMultiObjectSearch({
|
||||
excludedObjects: [
|
||||
CoreObjectNameSingular.Task,
|
||||
CoreObjectNameSingular.Note,
|
||||
@ -41,14 +42,15 @@ export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect =
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
const { objectRecordForSelectArray } =
|
||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
||||
multiObjectRecordsQueryResult:
|
||||
matchesSearchFilterObjectRecordsQueryResult,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setRecordMultiSelectMatchesFilterRecords(
|
||||
matchesSearchFilterObjectRecords,
|
||||
);
|
||||
}, [
|
||||
setRecordMultiSelectMatchesFilterRecords,
|
||||
matchesSearchFilterObjectRecords,
|
||||
]);
|
||||
setRecordMultiSelectMatchesFilterRecords(objectRecordForSelectArray);
|
||||
}, [setRecordMultiSelectMatchesFilterRecords, objectRecordForSelectArray]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
@ -0,0 +1,97 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
|
||||
import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||
|
||||
const scopeId = 'scopeId';
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RelationPickerScopeInternalContext.Provider value={{ scopeId }}>
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
</RelationPickerScopeInternalContext.Provider>
|
||||
);
|
||||
|
||||
const opportunityId = 'cb702502-4b1d-488e-9461-df3fb096ebf6';
|
||||
const personId = 'ab091fd9-1b81-4dfd-bfdb-564ffee032a2';
|
||||
|
||||
describe('useMultiObjectRecordsQueryResultFormattedAsObjectRecordsMap', () => {
|
||||
it('should return object formatted from objectMetadataItemsState', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
return {
|
||||
formattedRecord:
|
||||
useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap({
|
||||
multiObjectRecordsQueryResult: {
|
||||
opportunities: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: opportunityId,
|
||||
pointOfContactId:
|
||||
'e992bda7-d797-4e12-af04-9b427f42244c',
|
||||
updatedAt: '2023-11-30T11:13:15.308Z',
|
||||
createdAt: '2023-11-30T11:13:15.308Z',
|
||||
__typename: 'Opportunity',
|
||||
},
|
||||
cursor: 'cursor',
|
||||
__typename: 'OpportunityEdge',
|
||||
},
|
||||
],
|
||||
pageInfo: {},
|
||||
},
|
||||
people: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
id: personId,
|
||||
updatedAt: '2023-11-30T11:13:15.308Z',
|
||||
createdAt: '2023-11-30T11:13:15.308Z',
|
||||
__typename: 'Person',
|
||||
},
|
||||
cursor: 'cursor',
|
||||
__typename: 'PersonEdge',
|
||||
},
|
||||
],
|
||||
pageInfo: {},
|
||||
},
|
||||
},
|
||||
}),
|
||||
setObjectMetadata: useSetRecoilState(objectMetadataItemsState),
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
act(() => {
|
||||
result.current.setObjectMetadata(generatedMockObjectMetadataItems);
|
||||
});
|
||||
|
||||
expect(
|
||||
Object.values(result.current.formattedRecord.objectRecordsMap).flat()
|
||||
.length,
|
||||
).toBe(2);
|
||||
|
||||
const opportunityObjectRecords =
|
||||
result.current.formattedRecord.objectRecordsMap.opportunities;
|
||||
|
||||
const personObjectRecords =
|
||||
result.current.formattedRecord.objectRecordsMap.people;
|
||||
|
||||
expect(opportunityObjectRecords[0].objectMetadataItem.namePlural).toBe(
|
||||
'opportunities',
|
||||
);
|
||||
expect(opportunityObjectRecords[0].record.id).toBe(opportunityId);
|
||||
expect(opportunityObjectRecords[0].recordIdentifier.linkToShowPage).toBe(
|
||||
`/object/opportunity/${opportunityId}`,
|
||||
);
|
||||
|
||||
expect(personObjectRecords[0].objectMetadataItem.namePlural).toBe('people');
|
||||
expect(personObjectRecords[0].record.id).toBe(personId);
|
||||
expect(personObjectRecords[0].recordIdentifier.linkToShowPage).toBe(
|
||||
`/object/person/${personId}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -6,14 +6,11 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
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 { MultiObjectRecordQueryResult } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useMultiObjectSearchMatchesSearchFilterQuery = ({
|
||||
export const useMultiObjectSearch = ({
|
||||
searchFilterValue,
|
||||
limit,
|
||||
excludedObjects,
|
||||
@ -62,14 +59,8 @@ export const useMultiObjectSearchMatchesSearchFilterQuery = ({
|
||||
},
|
||||
);
|
||||
|
||||
const { objectRecordForSelectArray: matchesSearchFilterObjectRecords } =
|
||||
useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({
|
||||
multiObjectRecordsQueryResult:
|
||||
matchesSearchFilterObjectRecordsQueryResult,
|
||||
});
|
||||
|
||||
return {
|
||||
matchesSearchFilterObjectRecordsLoading,
|
||||
matchesSearchFilterObjectRecords,
|
||||
matchesSearchFilterObjectRecordsQueryResult,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,61 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsByNamePluralMapSelector } from '@/object-metadata/states/objectMetadataItemsByNamePluralMapSelector';
|
||||
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||
import { MultiObjectRecordQueryResult } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray';
|
||||
import { formatMultiObjectRecordSearchResults } from '@/object-record/relation-picker/utils/formatMultiObjectRecordSearchResults';
|
||||
import { ObjectRecordForSelect } from '@/object-record/types/ObjectRecordForSelect';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap = ({
|
||||
multiObjectRecordsQueryResult,
|
||||
}: {
|
||||
multiObjectRecordsQueryResult:
|
||||
| MultiObjectRecordQueryResult
|
||||
| null
|
||||
| undefined;
|
||||
}) => {
|
||||
const objectMetadataItemsByNamePluralMap = useRecoilValue(
|
||||
objectMetadataItemsByNamePluralMapSelector,
|
||||
);
|
||||
|
||||
const formattedMultiObjectRecordsQueryResult = useMemo(() => {
|
||||
return formatMultiObjectRecordSearchResults(multiObjectRecordsQueryResult);
|
||||
}, [multiObjectRecordsQueryResult]);
|
||||
|
||||
const objectRecordsMap = useMemo(() => {
|
||||
const recordsByNamePlural: { [key: string]: ObjectRecordForSelect[] } = {};
|
||||
Object.entries(formattedMultiObjectRecordsQueryResult ?? {}).forEach(
|
||||
([namePlural, objectRecordConnection]) => {
|
||||
const objectMetadataItem =
|
||||
objectMetadataItemsByNamePluralMap.get(namePlural);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) return [];
|
||||
if (!isDefined(recordsByNamePlural[namePlural])) {
|
||||
recordsByNamePlural[namePlural] = [];
|
||||
}
|
||||
|
||||
objectRecordConnection.edges.forEach(({ node }) => {
|
||||
const record = {
|
||||
objectMetadataItem,
|
||||
record: node,
|
||||
recordIdentifier: getObjectRecordIdentifier({
|
||||
objectMetadataItem,
|
||||
record: node,
|
||||
}),
|
||||
} as ObjectRecordForSelect;
|
||||
recordsByNamePlural[namePlural].push(record);
|
||||
});
|
||||
},
|
||||
);
|
||||
return recordsByNamePlural;
|
||||
}, [
|
||||
formattedMultiObjectRecordsQueryResult,
|
||||
objectMetadataItemsByNamePluralMap,
|
||||
]);
|
||||
|
||||
return {
|
||||
objectRecordsMap,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user