mirror of
				https://github.com/lingble/twenty.git
				synced 2025-10-31 04:37:56 +00:00 
			
		
		
		
	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:
		| @@ -2,6 +2,7 @@ import { useOpenCopilotRightDrawer } from '@/activities/copilot/right-drawer/hoo | |||||||
| import { copilotQueryState } from '@/activities/copilot/right-drawer/states/copilotQueryState'; | import { copilotQueryState } from '@/activities/copilot/right-drawer/states/copilotQueryState'; | ||||||
| import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; | import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; | ||||||
| import { Note } from '@/activities/types/Note'; | import { Note } from '@/activities/types/Note'; | ||||||
|  | import { Task } from '@/activities/types/Task'; | ||||||
| import { CommandGroup } from '@/command-menu/components/CommandGroup'; | import { CommandGroup } from '@/command-menu/components/CommandGroup'; | ||||||
| import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; | import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; | ||||||
| import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; | import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; | ||||||
| @@ -12,11 +13,13 @@ import { Command, CommandType } from '@/command-menu/types/Command'; | |||||||
| import { Company } from '@/companies/types/Company'; | import { Company } from '@/companies/types/Company'; | ||||||
| import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; | import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId'; | ||||||
| import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu'; | import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu'; | ||||||
|  | import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; | ||||||
| import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; | import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; | ||||||
| import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName'; | import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName'; | ||||||
| import { useSearchRecords } from '@/object-record/hooks/useSearchRecords'; | import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; | ||||||
| import { Opportunity } from '@/opportunities/types/Opportunity'; | import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; | ||||||
| import { Person } from '@/people/types/Person'; | import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap'; | ||||||
|  | import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables'; | ||||||
| import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; | import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; | ||||||
| import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; | import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; | ||||||
| import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; | import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; | ||||||
| @@ -27,11 +30,14 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; | |||||||
| import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; | import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; | ||||||
| import styled from '@emotion/styled'; | import styled from '@emotion/styled'; | ||||||
| import { isNonEmptyString } from '@sniptt/guards'; | import { isNonEmptyString } from '@sniptt/guards'; | ||||||
|  | import isEmpty from 'lodash.isempty'; | ||||||
| import { useMemo, useRef } from 'react'; | import { useMemo, useRef } from 'react'; | ||||||
| import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; | import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; | ||||||
| import { Key } from 'ts-key-enum'; | import { Key } from 'ts-key-enum'; | ||||||
| import { | import { | ||||||
|   Avatar, |   Avatar, | ||||||
|  |   IconCheckbox, | ||||||
|  |   IconComponent, | ||||||
|   IconNotes, |   IconNotes, | ||||||
|   IconSparkles, |   IconSparkles, | ||||||
|   IconX, |   IconX, | ||||||
| @@ -40,11 +46,25 @@ import { | |||||||
| } from 'twenty-ui'; | } from 'twenty-ui'; | ||||||
| import { useDebounce } from 'use-debounce'; | import { useDebounce } from 'use-debounce'; | ||||||
| import { getLogoUrlFromDomainName } from '~/utils'; | import { getLogoUrlFromDomainName } from '~/utils'; | ||||||
|  | import { capitalize } from '~/utils/string/capitalize'; | ||||||
|  |  | ||||||
| const SEARCH_BAR_HEIGHT = 56; | const SEARCH_BAR_HEIGHT = 56; | ||||||
| const SEARCH_BAR_PADDING = 3; | const SEARCH_BAR_PADDING = 3; | ||||||
| const MOBILE_NAVIGATION_BAR_HEIGHT = 64; | const MOBILE_NAVIGATION_BAR_HEIGHT = 64; | ||||||
|  |  | ||||||
|  | type CommandGroupConfig = { | ||||||
|  |   heading: string; | ||||||
|  |   items?: any[]; | ||||||
|  |   renderItem: (item: any) => { | ||||||
|  |     id: string; | ||||||
|  |     Icon?: IconComponent; | ||||||
|  |     label: string; | ||||||
|  |     to?: string; | ||||||
|  |     onClick?: () => void; | ||||||
|  |     key?: string; | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  |  | ||||||
| const StyledCommandMenu = styled.div` | const StyledCommandMenu = styled.div` | ||||||
|   background: ${({ theme }) => theme.background.secondary}; |   background: ${({ theme }) => theme.background.secondary}; | ||||||
|   border-left: 1px solid ${({ theme }) => theme.border.color.medium}; |   border-left: 1px solid ${({ theme }) => theme.border.color.medium}; | ||||||
| @@ -170,37 +190,68 @@ export const CommandMenu = () => { | |||||||
|     [closeCommandMenu], |     [closeCommandMenu], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   const { loading: isPeopleLoading, records: people } = |   const { | ||||||
|     useSearchRecords<Person>({ |     matchesSearchFilterObjectRecordsQueryResult, | ||||||
|       skip: !isCommandMenuOpened, |     matchesSearchFilterObjectRecordsLoading: loading, | ||||||
|       objectNameSingular: CoreObjectNameSingular.Person, |   } = useMultiObjectSearch({ | ||||||
|       limit: 3, |     excludedObjects: [CoreObjectNameSingular.Task, CoreObjectNameSingular.Note], | ||||||
|       searchInput: deferredCommandMenuSearch ?? undefined, |     searchFilterValue: deferredCommandMenuSearch ?? undefined, | ||||||
|     }); |  | ||||||
|  |  | ||||||
|   const { loading: isCompaniesLoading, records: companies } = |  | ||||||
|     useSearchRecords<Company>({ |  | ||||||
|       skip: !isCommandMenuOpened, |  | ||||||
|       objectNameSingular: CoreObjectNameSingular.Company, |  | ||||||
|       limit: 3, |  | ||||||
|       searchInput: deferredCommandMenuSearch ?? undefined, |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|   const { loading: isNotesLoading, records: notes } = useSearchRecords<Note>({ |  | ||||||
|     skip: !isCommandMenuOpened, |  | ||||||
|     objectNameSingular: CoreObjectNameSingular.Note, |  | ||||||
|     limit: 3, |     limit: 3, | ||||||
|     searchInput: deferredCommandMenuSearch ?? undefined, |  | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   const { loading: isOpportunitiesLoading, records: opportunities } = |   const { objectRecordsMap: matchesSearchFilterObjectRecords } = | ||||||
|     useSearchRecords<Opportunity>({ |     useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap({ | ||||||
|       skip: !isCommandMenuOpened, |       multiObjectRecordsQueryResult: | ||||||
|       objectNameSingular: CoreObjectNameSingular.Opportunity, |         matchesSearchFilterObjectRecordsQueryResult, | ||||||
|       limit: 3, |  | ||||||
|       searchInput: deferredCommandMenuSearch ?? undefined, |  | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |   const { loading: isNotesLoading, records: notes } = useFindManyRecords<Note>({ | ||||||
|  |     skip: !isCommandMenuOpened, | ||||||
|  |     objectNameSingular: CoreObjectNameSingular.Note, | ||||||
|  |     filter: deferredCommandMenuSearch | ||||||
|  |       ? makeOrFilterVariables([ | ||||||
|  |           { title: { ilike: `%${deferredCommandMenuSearch}%` } }, | ||||||
|  |           { body: { ilike: `%${deferredCommandMenuSearch}%` } }, | ||||||
|  |         ]) | ||||||
|  |       : undefined, | ||||||
|  |     limit: 3, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const { loading: isTasksLoading, records: tasks } = useFindManyRecords<Task>({ | ||||||
|  |     skip: !isCommandMenuOpened, | ||||||
|  |     objectNameSingular: CoreObjectNameSingular.Task, | ||||||
|  |     filter: deferredCommandMenuSearch | ||||||
|  |       ? makeOrFilterVariables([ | ||||||
|  |           { title: { ilike: `%${deferredCommandMenuSearch}%` } }, | ||||||
|  |           { body: { ilike: `%${deferredCommandMenuSearch}%` } }, | ||||||
|  |         ]) | ||||||
|  |       : undefined, | ||||||
|  |     limit: 3, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const people = matchesSearchFilterObjectRecords.people?.map( | ||||||
|  |     (people) => people.record, | ||||||
|  |   ); | ||||||
|  |   const companies = matchesSearchFilterObjectRecords.companies?.map( | ||||||
|  |     (companies) => companies.record, | ||||||
|  |   ); | ||||||
|  |   const opportunities = matchesSearchFilterObjectRecords.opportunities?.map( | ||||||
|  |     (opportunities) => opportunities.record, | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const customObjectRecordsMap = useMemo(() => { | ||||||
|  |     return Object.fromEntries( | ||||||
|  |       Object.entries(matchesSearchFilterObjectRecords).filter( | ||||||
|  |         ([namePlural, records]) => | ||||||
|  |           ![ | ||||||
|  |             CoreObjectNamePlural.Person, | ||||||
|  |             CoreObjectNamePlural.Opportunity, | ||||||
|  |             CoreObjectNamePlural.Company, | ||||||
|  |           ].includes(namePlural as CoreObjectNamePlural) && !isEmpty(records), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   }, [matchesSearchFilterObjectRecords]); | ||||||
|  |  | ||||||
|   const peopleCommands = useMemo( |   const peopleCommands = useMemo( | ||||||
|     () => |     () => | ||||||
|       people?.map(({ id, name: { firstName, lastName } }) => ({ |       people?.map(({ id, name: { firstName, lastName } }) => ({ | ||||||
| @@ -242,6 +293,32 @@ export const CommandMenu = () => { | |||||||
|     [notes, openActivityRightDrawer], |     [notes, openActivityRightDrawer], | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   const tasksCommands = useMemo( | ||||||
|  |     () => | ||||||
|  |       tasks?.map((task) => ({ | ||||||
|  |         id: task.id, | ||||||
|  |         label: task.title ?? '', | ||||||
|  |         to: '', | ||||||
|  |         onCommandClick: () => openActivityRightDrawer(task.id), | ||||||
|  |       })), | ||||||
|  |     [tasks, openActivityRightDrawer], | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const customObjectCommands = useMemo(() => { | ||||||
|  |     const customObjectCommandsArray: Command[] = []; | ||||||
|  |     Object.values(customObjectRecordsMap).forEach((objectRecords) => { | ||||||
|  |       customObjectCommandsArray.push( | ||||||
|  |         ...objectRecords.map((objectRecord) => ({ | ||||||
|  |           id: objectRecord.record.id, | ||||||
|  |           label: objectRecord.recordIdentifier.name, | ||||||
|  |           to: `object/${objectRecord.objectMetadataItem.nameSingular}/${objectRecord.record.id}`, | ||||||
|  |         })), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return customObjectCommandsArray; | ||||||
|  |   }, [customObjectRecordsMap]); | ||||||
|  |  | ||||||
|   const otherCommands = useMemo(() => { |   const otherCommands = useMemo(() => { | ||||||
|     const commandsArray: Command[] = []; |     const commandsArray: Command[] = []; | ||||||
|     if (peopleCommands?.length > 0) { |     if (peopleCommands?.length > 0) { | ||||||
| @@ -256,8 +333,21 @@ export const CommandMenu = () => { | |||||||
|     if (noteCommands?.length > 0) { |     if (noteCommands?.length > 0) { | ||||||
|       commandsArray.push(...(noteCommands as Command[])); |       commandsArray.push(...(noteCommands as Command[])); | ||||||
|     } |     } | ||||||
|  |     if (tasksCommands?.length > 0) { | ||||||
|  |       commandsArray.push(...(tasksCommands as Command[])); | ||||||
|  |     } | ||||||
|  |     if (customObjectCommands?.length > 0) { | ||||||
|  |       commandsArray.push(...(customObjectCommands as Command[])); | ||||||
|  |     } | ||||||
|     return commandsArray; |     return commandsArray; | ||||||
|   }, [peopleCommands, companyCommands, noteCommands, opportunityCommands]); |   }, [ | ||||||
|  |     peopleCommands, | ||||||
|  |     companyCommands, | ||||||
|  |     opportunityCommands, | ||||||
|  |     noteCommands, | ||||||
|  |     customObjectCommands, | ||||||
|  |     tasksCommands, | ||||||
|  |   ]); | ||||||
|  |  | ||||||
|   const checkInShortcuts = (cmd: Command, search: string) => { |   const checkInShortcuts = (cmd: Command, search: string) => { | ||||||
|     return (cmd.firstHotKey + (cmd.secondHotKey ?? '')) |     return (cmd.firstHotKey + (cmd.secondHotKey ?? '')) | ||||||
| @@ -335,7 +425,15 @@ export const CommandMenu = () => { | |||||||
|     .concat(people?.map((person) => person.id)) |     .concat(people?.map((person) => person.id)) | ||||||
|     .concat(companies?.map((company) => company.id)) |     .concat(companies?.map((company) => company.id)) | ||||||
|     .concat(opportunities?.map((opportunity) => opportunity.id)) |     .concat(opportunities?.map((opportunity) => opportunity.id)) | ||||||
|     .concat(notes?.map((note) => note.id)); |     .concat(notes?.map((note) => note.id)) | ||||||
|  |     .concat(tasks?.map((task) => task.id)) | ||||||
|  |     .concat( | ||||||
|  |       Object.values(customObjectRecordsMap) | ||||||
|  |         ?.map((objectRecords) => | ||||||
|  |           objectRecords.map((objectRecord) => objectRecord.record.id), | ||||||
|  |         ) | ||||||
|  |         .flat() ?? [], | ||||||
|  |     ); | ||||||
|  |  | ||||||
|   const isNoResults = |   const isNoResults = | ||||||
|     !matchingStandardActionCommands.length && |     !matchingStandardActionCommands.length && | ||||||
| @@ -345,18 +443,133 @@ export const CommandMenu = () => { | |||||||
|     !people?.length && |     !people?.length && | ||||||
|     !companies?.length && |     !companies?.length && | ||||||
|     !notes?.length && |     !notes?.length && | ||||||
|     !opportunities?.length; |     !tasks?.length && | ||||||
|  |     !opportunities?.length && | ||||||
|  |     isEmpty(customObjectRecordsMap); | ||||||
|  |  | ||||||
|   const isLoading = |   const isLoading = loading || isNotesLoading || isTasksLoading; | ||||||
|     isPeopleLoading || |  | ||||||
|     isNotesLoading || |  | ||||||
|     isOpportunitiesLoading || |  | ||||||
|     isCompaniesLoading; |  | ||||||
|  |  | ||||||
|   const mainContextStoreComponentInstanceId = useRecoilValue( |   const mainContextStoreComponentInstanceId = useRecoilValue( | ||||||
|     mainContextStoreComponentInstanceIdState, |     mainContextStoreComponentInstanceIdState, | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   const commandGroups: CommandGroupConfig[] = [ | ||||||
|  |     { | ||||||
|  |       heading: 'Navigate', | ||||||
|  |       items: matchingNavigateCommand, | ||||||
|  |       renderItem: (command) => ({ | ||||||
|  |         id: command.id, | ||||||
|  |         Icon: command.Icon, | ||||||
|  |         label: command.label, | ||||||
|  |         to: command.to, | ||||||
|  |         onClick: command.onCommandClick, | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       heading: 'Other', | ||||||
|  |       items: matchingCreateCommand, | ||||||
|  |       renderItem: (command) => ({ | ||||||
|  |         id: command.id, | ||||||
|  |         Icon: command.Icon, | ||||||
|  |         label: command.label, | ||||||
|  |         to: command.to, | ||||||
|  |         onClick: command.onCommandClick, | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       heading: 'People', | ||||||
|  |       items: people, | ||||||
|  |       renderItem: (person) => ({ | ||||||
|  |         id: person.id, | ||||||
|  |         label: `${person.name.firstName} ${person.name.lastName}`, | ||||||
|  |         to: `object/person/${person.id}`, | ||||||
|  |         Icon: () => ( | ||||||
|  |           <Avatar | ||||||
|  |             type="rounded" | ||||||
|  |             avatarUrl={null} | ||||||
|  |             placeholderColorSeed={person.id} | ||||||
|  |             placeholder={`${person.name.firstName} ${person.name.lastName}`} | ||||||
|  |           /> | ||||||
|  |         ), | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       heading: 'Companies', | ||||||
|  |       items: companies, | ||||||
|  |       renderItem: (company) => ({ | ||||||
|  |         id: company.id, | ||||||
|  |         label: company.name, | ||||||
|  |         to: `object/company/${company.id}`, | ||||||
|  |         Icon: () => ( | ||||||
|  |           <Avatar | ||||||
|  |             placeholderColorSeed={company.id} | ||||||
|  |             placeholder={company.name} | ||||||
|  |             avatarUrl={getLogoUrlFromDomainName( | ||||||
|  |               getCompanyDomainName(company as Company), | ||||||
|  |             )} | ||||||
|  |           /> | ||||||
|  |         ), | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       heading: 'Opportunities', | ||||||
|  |       items: opportunities, | ||||||
|  |       renderItem: (opportunity) => ({ | ||||||
|  |         id: opportunity.id, | ||||||
|  |         label: opportunity.name ?? '', | ||||||
|  |         to: `object/opportunity/${opportunity.id}`, | ||||||
|  |         Icon: () => ( | ||||||
|  |           <Avatar | ||||||
|  |             type="rounded" | ||||||
|  |             avatarUrl={null} | ||||||
|  |             placeholderColorSeed={opportunity.id} | ||||||
|  |             placeholder={opportunity.name ?? ''} | ||||||
|  |           /> | ||||||
|  |         ), | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       heading: 'Notes', | ||||||
|  |       items: notes, | ||||||
|  |       renderItem: (note) => ({ | ||||||
|  |         id: note.id, | ||||||
|  |         Icon: IconNotes, | ||||||
|  |         label: note.title ?? '', | ||||||
|  |         onClick: () => openActivityRightDrawer(note.id), | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       heading: 'Tasks', | ||||||
|  |       items: tasks, | ||||||
|  |       renderItem: (task) => ({ | ||||||
|  |         id: task.id, | ||||||
|  |         Icon: IconCheckbox, | ||||||
|  |         label: task.title ?? '', | ||||||
|  |         onClick: () => openActivityRightDrawer(task.id), | ||||||
|  |       }), | ||||||
|  |     }, | ||||||
|  |     ...Object.entries(customObjectRecordsMap).map( | ||||||
|  |       ([customObjectNamePlural, objectRecords]): CommandGroupConfig => ({ | ||||||
|  |         heading: capitalize(customObjectNamePlural), | ||||||
|  |         items: objectRecords, | ||||||
|  |         renderItem: (objectRecord) => ({ | ||||||
|  |           key: objectRecord.record.id, | ||||||
|  |           id: objectRecord.record.id, | ||||||
|  |           label: objectRecord.recordIdentifier.name, | ||||||
|  |           to: `object/${objectRecord.objectMetadataItem.nameSingular}/${objectRecord.record.id}`, | ||||||
|  |           Icon: () => ( | ||||||
|  |             <Avatar | ||||||
|  |               type="rounded" | ||||||
|  |               avatarUrl={null} | ||||||
|  |               placeholderColorSeed={objectRecord.id} | ||||||
|  |               placeholder={objectRecord.recordIdentifier.name ?? ''} | ||||||
|  |             /> | ||||||
|  |           ), | ||||||
|  |         }), | ||||||
|  |       }), | ||||||
|  |     ), | ||||||
|  |   ]; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       {isCommandMenuOpened && ( |       {isCommandMenuOpened && ( | ||||||
| @@ -457,121 +670,28 @@ export const CommandMenu = () => { | |||||||
|                       </CommandGroup> |                       </CommandGroup> | ||||||
|                     </> |                     </> | ||||||
|                   )} |                   )} | ||||||
|                   <CommandGroup heading="Navigate"> |                   {commandGroups.map(({ heading, items, renderItem }) => | ||||||
|                     {matchingNavigateCommand.map((cmd) => ( |                     items?.length ? ( | ||||||
|                       <SelectableItem itemId={cmd.id} key={cmd.id}> |                       <CommandGroup heading={heading} key={heading}> | ||||||
|                         <CommandMenuItem |                         {items.map((item) => { | ||||||
|                           id={cmd.id} |                           const { id, Icon, label, to, onClick, key } = | ||||||
|                           to={cmd.to} |                             renderItem(item); | ||||||
|                           key={cmd.id} |                           return ( | ||||||
|                           label={cmd.label} |                             <SelectableItem itemId={id} key={id}> | ||||||
|                           Icon={cmd.Icon} |                               <CommandMenuItem | ||||||
|                           onClick={cmd.onCommandClick} |                                 key={key} | ||||||
|                           firstHotKey={cmd.firstHotKey} |                                 id={id} | ||||||
|                           secondHotKey={cmd.secondHotKey} |                                 Icon={Icon} | ||||||
|                         /> |                                 label={label} | ||||||
|                       </SelectableItem> |                                 to={to} | ||||||
|                     ))} |                                 onClick={onClick} | ||||||
|                   </CommandGroup> |                               /> | ||||||
|                   <CommandGroup heading="Other"> |                             </SelectableItem> | ||||||
|                     {matchingCreateCommand.map((cmd) => ( |                           ); | ||||||
|                       <SelectableItem itemId={cmd.id} key={cmd.id}> |                         })} | ||||||
|                         <CommandMenuItem |                       </CommandGroup> | ||||||
|                           id={cmd.id} |                     ) : null, | ||||||
|                           to={cmd.to} |                   )} | ||||||
|                           key={cmd.id} |  | ||||||
|                           Icon={cmd.Icon} |  | ||||||
|                           label={cmd.label} |  | ||||||
|                           onClick={cmd.onCommandClick} |  | ||||||
|                           firstHotKey={cmd.firstHotKey} |  | ||||||
|                           secondHotKey={cmd.secondHotKey} |  | ||||||
|                         /> |  | ||||||
|                       </SelectableItem> |  | ||||||
|                     ))} |  | ||||||
|                   </CommandGroup> |  | ||||||
|                   <CommandGroup heading="People"> |  | ||||||
|                     {people?.map((person) => ( |  | ||||||
|                       <SelectableItem itemId={person.id} key={person.id}> |  | ||||||
|                         <CommandMenuItem |  | ||||||
|                           id={person.id} |  | ||||||
|                           key={person.id} |  | ||||||
|                           to={`object/person/${person.id}`} |  | ||||||
|                           label={ |  | ||||||
|                             person.name.firstName + ' ' + person.name.lastName |  | ||||||
|                           } |  | ||||||
|                           Icon={() => ( |  | ||||||
|                             <Avatar |  | ||||||
|                               type="rounded" |  | ||||||
|                               avatarUrl={null} |  | ||||||
|                               placeholderColorSeed={person.id} |  | ||||||
|                               placeholder={ |  | ||||||
|                                 person.name.firstName + |  | ||||||
|                                 ' ' + |  | ||||||
|                                 person.name.lastName |  | ||||||
|                               } |  | ||||||
|                             /> |  | ||||||
|                           )} |  | ||||||
|                         /> |  | ||||||
|                       </SelectableItem> |  | ||||||
|                     ))} |  | ||||||
|                   </CommandGroup> |  | ||||||
|                   <CommandGroup heading="Companies"> |  | ||||||
|                     {companies?.map((company) => ( |  | ||||||
|                       <SelectableItem itemId={company.id} key={company.id}> |  | ||||||
|                         <CommandMenuItem |  | ||||||
|                           id={company.id} |  | ||||||
|                           key={company.id} |  | ||||||
|                           label={company.name} |  | ||||||
|                           to={`object/company/${company.id}`} |  | ||||||
|                           Icon={() => ( |  | ||||||
|                             <Avatar |  | ||||||
|                               placeholderColorSeed={company.id} |  | ||||||
|                               placeholder={company.name} |  | ||||||
|                               avatarUrl={getLogoUrlFromDomainName( |  | ||||||
|                                 getCompanyDomainName(company), |  | ||||||
|                               )} |  | ||||||
|                             /> |  | ||||||
|                           )} |  | ||||||
|                         /> |  | ||||||
|                       </SelectableItem> |  | ||||||
|                     ))} |  | ||||||
|                   </CommandGroup> |  | ||||||
|                   <CommandGroup heading="Opportunities"> |  | ||||||
|                     {opportunities?.map((opportunity) => ( |  | ||||||
|                       <SelectableItem |  | ||||||
|                         itemId={opportunity.id} |  | ||||||
|                         key={opportunity.id} |  | ||||||
|                       > |  | ||||||
|                         <CommandMenuItem |  | ||||||
|                           id={opportunity.id} |  | ||||||
|                           key={opportunity.id} |  | ||||||
|                           label={opportunity.name ?? ''} |  | ||||||
|                           to={`object/opportunity/${opportunity.id}`} |  | ||||||
|                           Icon={() => ( |  | ||||||
|                             <Avatar |  | ||||||
|                               type="rounded" |  | ||||||
|                               avatarUrl={null} |  | ||||||
|                               placeholderColorSeed={opportunity.id} |  | ||||||
|                               placeholder={opportunity.name ?? ''} |  | ||||||
|                             /> |  | ||||||
|                           )} |  | ||||||
|                         /> |  | ||||||
|                       </SelectableItem> |  | ||||||
|                     ))} |  | ||||||
|                   </CommandGroup> |  | ||||||
|                   <CommandGroup heading="Notes"> |  | ||||||
|                     {notes?.map((note) => ( |  | ||||||
|                       <SelectableItem itemId={note.id} key={note.id}> |  | ||||||
|                         <CommandMenuItem |  | ||||||
|                           id={note.id} |  | ||||||
|                           Icon={IconNotes} |  | ||||||
|                           key={note.id} |  | ||||||
|                           label={note.title ?? ''} |  | ||||||
|                           onClick={() => openActivityRightDrawer(note.id)} |  | ||||||
|                         /> |  | ||||||
|                       </SelectableItem> |  | ||||||
|                     ))} |  | ||||||
|                   </CommandGroup> |  | ||||||
|                 </SelectableList> |                 </SelectableList> | ||||||
|               </StyledInnerList> |               </StyledInnerList> | ||||||
|             </ScrollWrapper> |             </ScrollWrapper> | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ export type Command = { | |||||||
|   id: string; |   id: string; | ||||||
|   to?: string; |   to?: string; | ||||||
|   label: string; |   label: string; | ||||||
|   type: |   type?: | ||||||
|     | CommandType.Navigate |     | CommandType.Navigate | ||||||
|     | CommandType.Create |     | CommandType.Create | ||||||
|     | CommandType.StandardAction |     | CommandType.StandardAction | ||||||
|   | |||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | export enum CoreObjectNamePlural { | ||||||
|  |   Activity = 'activities', | ||||||
|  |   ActivityTarget = 'activityTargets', | ||||||
|  |   ApiKey = 'apiKeys', | ||||||
|  |   Attachment = 'attachments', | ||||||
|  |   Blocklist = 'blocklists', | ||||||
|  |   CalendarChannel = 'calendarChannels', | ||||||
|  |   CalendarEvent = 'calendarEvents', | ||||||
|  |   Comment = 'comments', | ||||||
|  |   Company = 'companies', | ||||||
|  |   ConnectedAccount = 'connectedAccounts', | ||||||
|  |   TimelineActivity = 'timelineActivities', | ||||||
|  |   Favorite = 'favorites', | ||||||
|  |   Message = 'messages', | ||||||
|  |   MessageChannel = 'messageChannels', | ||||||
|  |   MessageParticipant = 'messageParticipants', | ||||||
|  |   MessageThread = 'messageThreads', | ||||||
|  |   Note = 'notes', | ||||||
|  |   NoteTarget = 'noteTargets', | ||||||
|  |   Opportunity = 'opportunities', | ||||||
|  |   Person = 'people', | ||||||
|  |   Task = 'tasks', | ||||||
|  |   TaskTarget = 'taskTargets', | ||||||
|  |   View = 'views', | ||||||
|  |   ViewField = 'viewFields', | ||||||
|  |   ViewFilter = 'viewFilters', | ||||||
|  |   ViewFilterGroup = 'viewFilterGroups', | ||||||
|  |   ViewSort = 'viewSorts', | ||||||
|  |   ViewGroup = 'viewGroups', | ||||||
|  |   Webhook = 'webhooks', | ||||||
|  |   WorkspaceMember = 'workspaceMembers', | ||||||
|  |   MessageThreadSubscriber = 'messageThreadSubscribers', | ||||||
|  |   Workflow = 'workflows', | ||||||
|  |   MessageChannelMessageAssociation = 'messageChannelMessageAssociations', | ||||||
|  |   WorkflowVersion = 'workflowVersions', | ||||||
|  |   WorkflowRun = 'workflowRuns', | ||||||
|  | } | ||||||
| @@ -4,7 +4,8 @@ import { useRecoilValue, useSetRecoilState } from 'recoil'; | |||||||
| import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; | import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; | ||||||
| import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; | import { objectRecordMultiSelectMatchesFilterRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectMatchesFilterRecordsIdsComponentState'; | ||||||
| import { useRelationPickerScopedStates } from '@/object-record/relation-picker/hooks/internal/useRelationPickerScopedStates'; | 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 { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; | ||||||
| import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; | import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; | ||||||
|  |  | ||||||
| @@ -31,8 +32,8 @@ export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect = | |||||||
|       relationPickerSearchFilterState, |       relationPickerSearchFilterState, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     const { matchesSearchFilterObjectRecords } = |     const { matchesSearchFilterObjectRecordsQueryResult } = | ||||||
|       useMultiObjectSearchMatchesSearchFilterQuery({ |       useMultiObjectSearch({ | ||||||
|         excludedObjects: [ |         excludedObjects: [ | ||||||
|           CoreObjectNameSingular.Task, |           CoreObjectNameSingular.Task, | ||||||
|           CoreObjectNameSingular.Note, |           CoreObjectNameSingular.Note, | ||||||
| @@ -41,14 +42,15 @@ export const ActivityTargetInlineCellEditModeMultiRecordsSearchFilterEffect = | |||||||
|         limit: 10, |         limit: 10, | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|  |     const { objectRecordForSelectArray } = | ||||||
|  |       useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ | ||||||
|  |         multiObjectRecordsQueryResult: | ||||||
|  |           matchesSearchFilterObjectRecordsQueryResult, | ||||||
|  |       }); | ||||||
|  |  | ||||||
|     useEffect(() => { |     useEffect(() => { | ||||||
|       setRecordMultiSelectMatchesFilterRecords( |       setRecordMultiSelectMatchesFilterRecords(objectRecordForSelectArray); | ||||||
|         matchesSearchFilterObjectRecords, |     }, [setRecordMultiSelectMatchesFilterRecords, objectRecordForSelectArray]); | ||||||
|       ); |  | ||||||
|     }, [ |  | ||||||
|       setRecordMultiSelectMatchesFilterRecords, |  | ||||||
|       matchesSearchFilterObjectRecords, |  | ||||||
|     ]); |  | ||||||
|  |  | ||||||
|     return <></>; |     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 { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; | ||||||
| import { useGenerateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery'; | import { useGenerateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery'; | ||||||
| import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem'; | import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem'; | ||||||
| import { | import { MultiObjectRecordQueryResult } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; | ||||||
|   MultiObjectRecordQueryResult, |  | ||||||
|   useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray, |  | ||||||
| } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; |  | ||||||
| import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest'; | import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest'; | ||||||
| import { isDefined } from '~/utils/isDefined'; | import { isDefined } from '~/utils/isDefined'; | ||||||
| 
 | 
 | ||||||
| export const useMultiObjectSearchMatchesSearchFilterQuery = ({ | export const useMultiObjectSearch = ({ | ||||||
|   searchFilterValue, |   searchFilterValue, | ||||||
|   limit, |   limit, | ||||||
|   excludedObjects, |   excludedObjects, | ||||||
| @@ -62,14 +59,8 @@ export const useMultiObjectSearchMatchesSearchFilterQuery = ({ | |||||||
|     }, |     }, | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   const { objectRecordForSelectArray: matchesSearchFilterObjectRecords } = |  | ||||||
|     useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ |  | ||||||
|       multiObjectRecordsQueryResult: |  | ||||||
|         matchesSearchFilterObjectRecordsQueryResult, |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|   return { |   return { | ||||||
|     matchesSearchFilterObjectRecordsLoading, |     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, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
| @@ -114,6 +114,45 @@ export const graphqlMocks = { | |||||||
|         }, |         }, | ||||||
|       }); |       }); | ||||||
|     }), |     }), | ||||||
|  |     graphql.query('CombinedSearchRecords', () => { | ||||||
|  |       return HttpResponse.json({ | ||||||
|  |         data: { | ||||||
|  |           searchOpportunities: { | ||||||
|  |             edges: [], | ||||||
|  |             pageInfo: { | ||||||
|  |               hasNextPage: false, | ||||||
|  |               hasPreviousPage: false, | ||||||
|  |               startCursor: null, | ||||||
|  |               endCursor: null, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           searchCompanies: { | ||||||
|  |             edges: companiesMock.slice(0, 3).map((company) => ({ | ||||||
|  |               node: company, | ||||||
|  |               cursor: null, | ||||||
|  |             })), | ||||||
|  |             pageInfo: { | ||||||
|  |               hasNextPage: false, | ||||||
|  |               hasPreviousPage: false, | ||||||
|  |               startCursor: null, | ||||||
|  |               endCursor: null, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |           searchPeople: { | ||||||
|  |             edges: peopleMock.slice(0, 3).map((person) => ({ | ||||||
|  |               node: person, | ||||||
|  |               cursor: null, | ||||||
|  |             })), | ||||||
|  |             pageInfo: { | ||||||
|  |               hasNextPage: false, | ||||||
|  |               hasPreviousPage: false, | ||||||
|  |               startCursor: null, | ||||||
|  |               endCursor: null, | ||||||
|  |             }, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     }), | ||||||
|     graphql.query('FindManyViews', ({ variables }) => { |     graphql.query('FindManyViews', ({ variables }) => { | ||||||
|       const objectMetadataId = variables.filter?.objectMetadataId?.eq; |       const objectMetadataId = variables.filter?.objectMetadataId?.eq; | ||||||
|       const viewType = variables.filter?.type?.eq; |       const viewType = variables.filter?.type?.eq; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Marie
					Marie