replace search resolvers by global search in front (#11086)
Done - Replace global search in multi record picker and single record picker To do - refactor SingleRecordPicker to match MultipleRecordPicker - next 1:1 - items in this issue https://github.com/twentyhq/core-team-issues/issues/643 closes https://github.com/twentyhq/core-team-issues/issues/535
This commit is contained in:
@ -0,0 +1,81 @@
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import {
|
||||
ObjectRecordFilterInput,
|
||||
useGlobalSearchQuery,
|
||||
} from '~/generated/graphql';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
export type UseSearchRecordsParams = ObjectMetadataItemIdentifier & {
|
||||
limit?: number;
|
||||
onError?: (error?: Error) => void;
|
||||
skip?: boolean;
|
||||
fetchPolicy?: WatchQueryFetchPolicy;
|
||||
searchInput?: string;
|
||||
filter?: ObjectRecordFilterInput;
|
||||
};
|
||||
|
||||
export const useObjectRecordSearchRecords = ({
|
||||
objectNameSingular,
|
||||
searchInput,
|
||||
limit,
|
||||
skip,
|
||||
filter,
|
||||
fetchPolicy,
|
||||
}: UseSearchRecordsParams) => {
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const { data, loading, error, previousData } = useGlobalSearchQuery({
|
||||
skip:
|
||||
skip ||
|
||||
!objectMetadataItem ||
|
||||
!currentWorkspaceMember ||
|
||||
!isDefined(searchInput),
|
||||
variables: {
|
||||
searchInput: searchInput ?? '',
|
||||
limit: limit ?? MAX_SEARCH_RESULTS,
|
||||
filter: filter ?? {},
|
||||
includedObjectNameSingulars: [objectNameSingular],
|
||||
},
|
||||
fetchPolicy: fetchPolicy,
|
||||
onError: (error) => {
|
||||
logError(
|
||||
`useGlobalSearchRecords for "${objectMetadataItem.namePlural}" error : ` +
|
||||
error,
|
||||
);
|
||||
enqueueSnackBar(
|
||||
`Error during useGlobalSearchRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
|
||||
{
|
||||
variant: SnackBarVariant.Error,
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const effectiveData = loading ? previousData : data;
|
||||
|
||||
const searchRecords = useMemo(
|
||||
() => effectiveData?.globalSearch || [],
|
||||
[effectiveData],
|
||||
);
|
||||
|
||||
return {
|
||||
objectMetadataItem,
|
||||
searchRecords,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
@ -1,98 +0,0 @@
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||
import { RecordGqlOperationSearchResult } from '@/object-record/graphql/types/RecordGqlOperationSearchResult';
|
||||
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
|
||||
import { useSearchRecordsQuery } from '@/object-record/hooks/useSearchRecordsQuery';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getSearchRecordsQueryResponseField } from '@/object-record/utils/getSearchRecordsQueryResponseField';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useQuery, WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
export type UseSearchRecordsParams = ObjectMetadataItemIdentifier &
|
||||
Pick<RecordGqlOperationVariables, 'filter' | 'limit'> & {
|
||||
onError?: (error?: Error) => void;
|
||||
skip?: boolean;
|
||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||
fetchPolicy?: WatchQueryFetchPolicy;
|
||||
searchInput?: string;
|
||||
};
|
||||
|
||||
export const useSearchRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectNameSingular,
|
||||
searchInput,
|
||||
limit,
|
||||
skip,
|
||||
filter,
|
||||
recordGqlFields,
|
||||
fetchPolicy,
|
||||
}: UseSearchRecordsParams) => {
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
const { searchRecordsQuery } = useSearchRecordsQuery({
|
||||
objectNameSingular,
|
||||
recordGqlFields,
|
||||
});
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const { data, loading, error, previousData } =
|
||||
useQuery<RecordGqlOperationSearchResult>(searchRecordsQuery, {
|
||||
skip:
|
||||
skip ||
|
||||
!objectMetadataItem ||
|
||||
!currentWorkspaceMember ||
|
||||
!isDefined(searchInput),
|
||||
variables: {
|
||||
search: searchInput,
|
||||
limit: limit,
|
||||
filter: filter,
|
||||
},
|
||||
fetchPolicy: fetchPolicy,
|
||||
onError: (error) => {
|
||||
logError(
|
||||
`useSearchRecords for "${objectMetadataItem.namePlural}" error : ` +
|
||||
error,
|
||||
);
|
||||
enqueueSnackBar(
|
||||
`Error during useSearchRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
|
||||
{
|
||||
variant: SnackBarVariant.Error,
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const effectiveData = loading ? previousData : data;
|
||||
|
||||
const queryResponseField = getSearchRecordsQueryResponseField(
|
||||
objectMetadataItem.namePlural,
|
||||
);
|
||||
|
||||
const result = effectiveData?.[queryResponseField];
|
||||
|
||||
const records = useMemo(
|
||||
() =>
|
||||
result
|
||||
? (getRecordsFromRecordConnection({
|
||||
recordConnection: result,
|
||||
}) as T[])
|
||||
: [],
|
||||
[result],
|
||||
);
|
||||
|
||||
return {
|
||||
objectMetadataItem,
|
||||
records: records,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
@ -1,33 +0,0 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||
import { generateSearchRecordsQuery } from '@/object-record/utils/generateSearchRecordsQuery';
|
||||
|
||||
export const useSearchRecordsQuery = ({
|
||||
objectNameSingular,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||
computeReferences?: boolean;
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const searchRecordsQuery = generateSearchRecordsQuery({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
});
|
||||
|
||||
return {
|
||||
searchRecordsQuery,
|
||||
};
|
||||
};
|
||||
@ -1,23 +0,0 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
|
||||
import { generateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/utils/generateCombinedSearchRecordsQuery';
|
||||
import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
|
||||
|
||||
export const useGenerateCombinedSearchRecordsQuery = ({
|
||||
operationSignatures,
|
||||
}: {
|
||||
operationSignatures: RecordGqlOperationSignature[];
|
||||
}) => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
if (!isNonEmptyArray(operationSignatures)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return generateCombinedSearchRecordsQuery({
|
||||
objectMetadataItems,
|
||||
operationSignatures,
|
||||
});
|
||||
};
|
||||
@ -1,82 +0,0 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||
import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature';
|
||||
import { getSearchRecordsQueryResponseField } from '@/object-record/utils/getSearchRecordsQueryResponseField';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import gql from 'graphql-tag';
|
||||
import { capitalize } from 'twenty-shared';
|
||||
|
||||
export const generateCombinedSearchRecordsQuery = ({
|
||||
objectMetadataItems,
|
||||
operationSignatures,
|
||||
}: {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
operationSignatures: RecordGqlOperationSignature[];
|
||||
}) => {
|
||||
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 }) => objectMetadataItem.isSearchable,
|
||||
);
|
||||
|
||||
return gql`
|
||||
query CombinedSearchRecords(
|
||||
${filterPerMetadataItemArray},
|
||||
${limitPerMetadataItemArray},
|
||||
$search: String,
|
||||
) {
|
||||
${filteredQueryKeyWithObjectMetadataItemArray
|
||||
.map(
|
||||
({ objectMetadataItem }) =>
|
||||
`${getSearchRecordsQueryResponseField(objectMetadataItem.namePlural)}(filter: $filter${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)},
|
||||
limit: $limit${capitalize(objectMetadataItem.nameSingular)},
|
||||
searchInput: $search
|
||||
){
|
||||
edges {
|
||||
node ${mapObjectMetadataToGraphQLQuery({
|
||||
objectMetadataItems: objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
})}
|
||||
cursor
|
||||
}
|
||||
totalCount
|
||||
}`,
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
`;
|
||||
};
|
||||
@ -77,9 +77,8 @@ const RelationToOneFieldInputWithContext = ({
|
||||
iconName: 'IconLink',
|
||||
metadata: {
|
||||
fieldName: 'Relation',
|
||||
relationObjectMetadataNamePlural: 'workspaceMembers',
|
||||
relationObjectMetadataNameSingular:
|
||||
CoreObjectNameSingular.WorkspaceMember,
|
||||
relationObjectMetadataNamePlural: 'companies',
|
||||
relationObjectMetadataNameSingular: CoreObjectNameSingular.Company,
|
||||
objectMetadataNameSingular: 'person',
|
||||
relationFieldMetadataId: '20202020-8c37-4163-ba06-1dada334ce3e',
|
||||
},
|
||||
@ -149,7 +148,7 @@ export const Submit: Story = {
|
||||
|
||||
expect(submitJestFn).toHaveBeenCalledTimes(0);
|
||||
|
||||
const item = await canvas.findByText('John Wick', undefined, {
|
||||
const item = await canvas.findByText('Linkedin', undefined, {
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
@ -167,7 +166,7 @@ export const Cancel: Story = {
|
||||
const canvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
expect(cancelJestFn).toHaveBeenCalledTimes(0);
|
||||
await canvas.findByText('John Wick', undefined, { timeout: 3000 });
|
||||
await canvas.findByText('Linkedin', undefined, { timeout: 3000 });
|
||||
|
||||
const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
|
||||
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { multipleRecordPickerSinglePickableMorphItemComponentFamilySelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerSinglePickableMorphItemComponentFamilySelector';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
type UseRecordPickerGetRecordAndObjectMetadataItemFromRecordIdProps = {
|
||||
recordId: string;
|
||||
};
|
||||
|
||||
export const useRecordPickerGetRecordAndObjectMetadataItemFromRecordId = ({
|
||||
recordId,
|
||||
}: UseRecordPickerGetRecordAndObjectMetadataItemFromRecordIdProps) => {
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const pickableMorphItem = useRecoilComponentFamilyValueV2(
|
||||
multipleRecordPickerSinglePickableMorphItemComponentFamilySelector,
|
||||
recordId,
|
||||
);
|
||||
|
||||
const record = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
if (!isDefined(pickableMorphItem)) {
|
||||
return { record: null, objectMetadataItem: null };
|
||||
}
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id === pickableMorphItem.objectMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
return { record: null, objectMetadataItem: null };
|
||||
}
|
||||
|
||||
return { record, objectMetadataItem };
|
||||
};
|
||||
@ -0,0 +1,41 @@
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { searchRecordStoreComponentFamilyState } from '@/object-record/record-picker/multiple-record-picker/states/searchRecordStoreComponentFamilyState';
|
||||
import { multipleRecordPickerSinglePickableMorphItemComponentFamilySelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerSinglePickableMorphItemComponentFamilySelector';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
type UseRecordPickerGetRecordAndObjectMetadataItemFromRecordIdProps = {
|
||||
recordId: string;
|
||||
};
|
||||
|
||||
export const useRecordPickerGetSearchRecordAndObjectMetadataItemFromRecordId =
|
||||
({
|
||||
recordId,
|
||||
}: UseRecordPickerGetRecordAndObjectMetadataItemFromRecordIdProps) => {
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const pickableMorphItem = useRecoilComponentFamilyValueV2(
|
||||
multipleRecordPickerSinglePickableMorphItemComponentFamilySelector,
|
||||
recordId,
|
||||
);
|
||||
|
||||
const searchRecord = useRecoilComponentFamilyValueV2(
|
||||
searchRecordStoreComponentFamilyState,
|
||||
recordId,
|
||||
);
|
||||
|
||||
if (!isDefined(pickableMorphItem) || !isDefined(searchRecord)) {
|
||||
return { searchRecord: null, objectMetadataItem: null };
|
||||
}
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id === pickableMorphItem.objectMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
return { searchRecord: null, objectMetadataItem: null };
|
||||
}
|
||||
|
||||
return { searchRecord, objectMetadataItem };
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useRecordPickerGetRecordAndObjectMetadataItemFromRecordId } from '@/object-record/record-picker/hooks/useRecordPickerGetRecordAndObjectMetadataItemFromRecordId';
|
||||
import { useRecordPickerGetSearchRecordAndObjectMetadataItemFromRecordId } from '@/object-record/record-picker/hooks/useRecordPickerGetSearchRecordAndObjectMetadataItemFromRecordId';
|
||||
import { MultipleRecordPickerMenuItemContent } from '@/object-record/record-picker/multiple-record-picker/components/MultipleRecordPickerMenuItemContent';
|
||||
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
@ -20,18 +20,18 @@ export const MultipleRecordPickerMenuItem = ({
|
||||
recordId,
|
||||
onChange,
|
||||
}: MultipleRecordPickerMenuItemProps) => {
|
||||
const { record, objectMetadataItem } =
|
||||
useRecordPickerGetRecordAndObjectMetadataItemFromRecordId({
|
||||
const { searchRecord, objectMetadataItem } =
|
||||
useRecordPickerGetSearchRecordAndObjectMetadataItemFromRecordId({
|
||||
recordId,
|
||||
});
|
||||
|
||||
if (!isDefined(record) || !isDefined(objectMetadataItem)) {
|
||||
if (!isDefined(searchRecord) || !isDefined(objectMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultipleRecordPickerMenuItemContent
|
||||
record={record}
|
||||
searchRecord={searchRecord}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
@ -3,17 +3,16 @@ import { useRecoilValue } from 'recoil';
|
||||
import { Avatar, MenuItemMultiSelectAvatar } from 'twenty-ui';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||
import { getAvatarType } from '@/object-metadata/utils/getAvatarType';
|
||||
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
|
||||
import { multipleRecordPickerIsSelectedComponentFamilySelector } from '@/object-record/record-picker/multiple-record-picker/states/selectors/multipleRecordPickerIsSelectedComponentFamilySelector';
|
||||
import { getMultipleRecordPickerSelectableListId } from '@/object-record/record-picker/multiple-record-picker/utils/getMultipleRecordPickerSelectableListId';
|
||||
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { GlobalSearchRecord } from '~/generated-metadata/graphql';
|
||||
|
||||
export const StyledSelectableItem = styled(SelectableItem)`
|
||||
height: 100%;
|
||||
@ -21,13 +20,13 @@ export const StyledSelectableItem = styled(SelectableItem)`
|
||||
`;
|
||||
|
||||
type MultipleRecordPickerMenuItemContentProps = {
|
||||
record: ObjectRecord;
|
||||
searchRecord: GlobalSearchRecord;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
onChange: (morphItem: RecordPickerPickableMorphItem) => void;
|
||||
};
|
||||
|
||||
export const MultipleRecordPickerMenuItemContent = ({
|
||||
record,
|
||||
searchRecord,
|
||||
objectMetadataItem,
|
||||
onChange,
|
||||
}: MultipleRecordPickerMenuItemContentProps) => {
|
||||
@ -43,49 +42,43 @@ export const MultipleRecordPickerMenuItemContent = ({
|
||||
);
|
||||
|
||||
const isSelectedByKeyboard = useRecoilValue(
|
||||
isSelectedItemIdSelector(record.id),
|
||||
isSelectedItemIdSelector(searchRecord.recordId),
|
||||
);
|
||||
|
||||
const isRecordSelectedWithObjectItem = useRecoilComponentFamilyValueV2(
|
||||
multipleRecordPickerIsSelectedComponentFamilySelector,
|
||||
record.id,
|
||||
searchRecord.recordId,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const handleSelectChange = (isSelected: boolean) => {
|
||||
onChange({
|
||||
recordId: record.id,
|
||||
recordId: searchRecord.recordId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
isSelected,
|
||||
isMatchingSearchFilter: true,
|
||||
});
|
||||
};
|
||||
|
||||
const recordIdentifier = getObjectRecordIdentifier({
|
||||
objectMetadataItem,
|
||||
record,
|
||||
});
|
||||
|
||||
if (!isDefined(recordIdentifier)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledSelectableItem itemId={record.id} key={record.id}>
|
||||
<StyledSelectableItem
|
||||
itemId={searchRecord.recordId}
|
||||
key={searchRecord.recordId}
|
||||
>
|
||||
<MenuItemMultiSelectAvatar
|
||||
onSelectChange={(isSelected) => handleSelectChange(isSelected)}
|
||||
isKeySelected={isSelectedByKeyboard}
|
||||
selected={isRecordSelectedWithObjectItem}
|
||||
avatar={
|
||||
<Avatar
|
||||
avatarUrl={recordIdentifier.avatarUrl}
|
||||
placeholderColorSeed={record.id}
|
||||
placeholder={recordIdentifier.name}
|
||||
avatarUrl={searchRecord.imageUrl}
|
||||
placeholderColorSeed={searchRecord.recordId}
|
||||
placeholder={searchRecord.label}
|
||||
size="md"
|
||||
type={recordIdentifier.avatarType ?? 'rounded'}
|
||||
type={getAvatarType(objectMetadataItem.nameSingular) ?? 'rounded'}
|
||||
/>
|
||||
}
|
||||
text={recordIdentifier.name}
|
||||
text={searchRecord.label}
|
||||
/>
|
||||
</StyledSelectableItem>
|
||||
);
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
|
||||
import { globalSearch } from '@/command-menu/graphql/queries/globalSearch';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { CombinedFindManyRecordsQueryResult } from '@/object-record/multiple-objects/types/CombinedFindManyRecordsQueryResult';
|
||||
import { generateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/utils/generateCombinedSearchRecordsQuery';
|
||||
import { getLimitPerMetadataItem } from '@/object-record/multiple-objects/utils/getLimitPerMetadataItem';
|
||||
import { usePerformCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/usePerformCombinedFindManyRecords';
|
||||
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
|
||||
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
|
||||
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
|
||||
import { multipleRecordPickerformatQueryResultAsRecordsWithObjectMetadataId } from '@/object-record/record-picker/multiple-record-picker/utils/multipleRecordPickerformatQueryResultAsRecordWithObjectMetadataId';
|
||||
import { searchRecordStoreComponentFamilyState } from '@/object-record/record-picker/multiple-record-picker/states/searchRecordStoreComponentFamilyState';
|
||||
import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { ApolloClient, useApolloClient } from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { capitalize, isDefined } from 'twenty-shared';
|
||||
import { GlobalSearchRecord } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useMultipleRecordPickerPerformSearch = () => {
|
||||
const client = useApolloClient();
|
||||
|
||||
const { performCombinedFindManyRecords } =
|
||||
usePerformCombinedFindManyRecords();
|
||||
|
||||
const performSearch = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async ({
|
||||
@ -29,57 +32,52 @@ export const useMultipleRecordPickerPerformSearch = () => {
|
||||
forceSearchableObjectMetadataItems?: ObjectMetadataItem[];
|
||||
forcePickableMorphItems?: RecordPickerPickableMorphItem[];
|
||||
}) => {
|
||||
const recordPickerSearchFilter = snapshot
|
||||
.getLoadable(
|
||||
multipleRecordPickerSearchFilterComponentState.atomFamily({
|
||||
instanceId: multipleRecordPickerInstanceId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
const { getLoadable } = snapshot;
|
||||
|
||||
const recordPickerSearchFilter = getLoadable(
|
||||
multipleRecordPickerSearchFilterComponentState.atomFamily({
|
||||
instanceId: multipleRecordPickerInstanceId,
|
||||
}),
|
||||
).getValue();
|
||||
|
||||
const searchFilter = forceSearchFilter ?? recordPickerSearchFilter;
|
||||
|
||||
const recordPickerSearchableObjectMetadataItems = snapshot
|
||||
.getLoadable(
|
||||
multipleRecordPickerSearchableObjectMetadataItemsComponentState.atomFamily(
|
||||
{ instanceId: multipleRecordPickerInstanceId },
|
||||
),
|
||||
)
|
||||
.getValue();
|
||||
const recordPickerSearchableObjectMetadataItems = getLoadable(
|
||||
multipleRecordPickerSearchableObjectMetadataItemsComponentState.atomFamily(
|
||||
{ instanceId: multipleRecordPickerInstanceId },
|
||||
),
|
||||
).getValue();
|
||||
|
||||
const searchableObjectMetadataItems =
|
||||
forceSearchableObjectMetadataItems.length > 0
|
||||
? forceSearchableObjectMetadataItems
|
||||
: recordPickerSearchableObjectMetadataItems;
|
||||
|
||||
const recordPickerPickableMorphItems = snapshot
|
||||
.getLoadable(
|
||||
multipleRecordPickerPickableMorphItemsComponentState.atomFamily({
|
||||
instanceId: multipleRecordPickerInstanceId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
const recordPickerPickableMorphItems = getLoadable(
|
||||
multipleRecordPickerPickableMorphItemsComponentState.atomFamily({
|
||||
instanceId: multipleRecordPickerInstanceId,
|
||||
}),
|
||||
).getValue();
|
||||
|
||||
const pickableMorphItems =
|
||||
forcePickableMorphItems.length > 0
|
||||
? forcePickableMorphItems
|
||||
: recordPickerPickableMorphItems;
|
||||
const selectedPickableMorphItems = pickableMorphItems.filter(
|
||||
({ isSelected }) => isSelected,
|
||||
);
|
||||
|
||||
const recordsWithObjectMetadataIdFilteredOnPickedRecords =
|
||||
await performSearchForPickedRecords({
|
||||
client,
|
||||
searchFilter,
|
||||
searchableObjectMetadataItems,
|
||||
pickableMorphItems,
|
||||
});
|
||||
|
||||
const recordsWithObjectMetadataIdExcludingPickedRecords =
|
||||
await performSearchExcludingPickedRecords({
|
||||
client,
|
||||
searchFilter,
|
||||
searchableObjectMetadataItems,
|
||||
pickableMorphItems,
|
||||
});
|
||||
const [
|
||||
searchRecordsFilteredOnPickedRecords,
|
||||
searchRecordsExcludingPickedRecords,
|
||||
] = await performSearchQueries({
|
||||
client,
|
||||
searchFilter,
|
||||
searchableObjectMetadataItems,
|
||||
pickedRecordIds: selectedPickableMorphItems.map(
|
||||
({ recordId }) => recordId,
|
||||
),
|
||||
});
|
||||
|
||||
const pickedMorphItems = pickableMorphItems.filter(
|
||||
({ isSelected }) => isSelected,
|
||||
@ -87,10 +85,9 @@ export const useMultipleRecordPickerPerformSearch = () => {
|
||||
|
||||
// We update the existing pickedMorphItems to be matching the search filter
|
||||
const updatedPickedMorphItems = pickedMorphItems.map((morphItem) => {
|
||||
const record =
|
||||
recordsWithObjectMetadataIdFilteredOnPickedRecords.find(
|
||||
({ record }) => record.id === morphItem.recordId,
|
||||
);
|
||||
const record = searchRecordsFilteredOnPickedRecords.find(
|
||||
({ recordId }) => recordId === morphItem.recordId,
|
||||
);
|
||||
|
||||
return {
|
||||
...morphItem,
|
||||
@ -98,40 +95,47 @@ export const useMultipleRecordPickerPerformSearch = () => {
|
||||
};
|
||||
});
|
||||
|
||||
const recordsWithObjectMetadataIdFilteredOnPickedRecordsWithoutDuplicates =
|
||||
recordsWithObjectMetadataIdFilteredOnPickedRecords.filter(
|
||||
({ record }) =>
|
||||
const searchRecordsFilteredOnPickedRecordsWithoutDuplicates =
|
||||
searchRecordsFilteredOnPickedRecords.filter(
|
||||
(searchRecord) =>
|
||||
!updatedPickedMorphItems.some(
|
||||
({ recordId }) => recordId === record.id,
|
||||
({ recordId }) => recordId === searchRecord.recordId,
|
||||
),
|
||||
);
|
||||
|
||||
const recordsWithObjectMetadataIdExcludingPickedRecordsWithoutDuplicates =
|
||||
recordsWithObjectMetadataIdExcludingPickedRecords.filter(
|
||||
({ record }) =>
|
||||
!recordsWithObjectMetadataIdFilteredOnPickedRecords.some(
|
||||
({ record: recordFilteredOnPickedRecords }) =>
|
||||
recordFilteredOnPickedRecords.id === record.id,
|
||||
const searchRecordsExcludingPickedRecordsWithoutDuplicates =
|
||||
searchRecordsExcludingPickedRecords.filter(
|
||||
(searchRecord) =>
|
||||
!searchRecordsFilteredOnPickedRecords.some(
|
||||
({ recordId }) => recordId === searchRecord.recordId,
|
||||
) &&
|
||||
!pickedMorphItems.some(({ recordId }) => recordId === record.id),
|
||||
!pickedMorphItems.some(
|
||||
({ recordId }) => recordId === searchRecord.recordId,
|
||||
),
|
||||
);
|
||||
|
||||
const morphItems = [
|
||||
...updatedPickedMorphItems,
|
||||
...recordsWithObjectMetadataIdFilteredOnPickedRecordsWithoutDuplicates.map(
|
||||
({ record, objectMetadataItem }) => ({
|
||||
...searchRecordsFilteredOnPickedRecordsWithoutDuplicates.map(
|
||||
({ recordId, objectSingularName }) => ({
|
||||
isMatchingSearchFilter: true,
|
||||
isSelected: true,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
recordId: record.id,
|
||||
objectMetadataId: searchableObjectMetadataItems.find(
|
||||
(objectMetadata) =>
|
||||
objectMetadata.nameSingular === objectSingularName,
|
||||
)?.id,
|
||||
recordId,
|
||||
}),
|
||||
),
|
||||
...recordsWithObjectMetadataIdExcludingPickedRecordsWithoutDuplicates.map(
|
||||
({ record, objectMetadataItem }) => ({
|
||||
...searchRecordsExcludingPickedRecordsWithoutDuplicates.map(
|
||||
({ recordId, objectSingularName }) => ({
|
||||
isMatchingSearchFilter: true,
|
||||
isSelected: false,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
recordId: record.id,
|
||||
objectMetadataId: searchableObjectMetadataItems.find(
|
||||
(objectMetadata) =>
|
||||
objectMetadata.nameSingular === objectSingularName,
|
||||
)?.id,
|
||||
recordId,
|
||||
}),
|
||||
),
|
||||
];
|
||||
@ -143,192 +147,153 @@ export const useMultipleRecordPickerPerformSearch = () => {
|
||||
morphItems,
|
||||
);
|
||||
|
||||
[
|
||||
...recordsWithObjectMetadataIdFilteredOnPickedRecords,
|
||||
...recordsWithObjectMetadataIdExcludingPickedRecordsWithoutDuplicates,
|
||||
].forEach(({ record }) => {
|
||||
set(recordStoreFamilyState(record.id), record);
|
||||
const searchRecords = [
|
||||
...searchRecordsFilteredOnPickedRecords,
|
||||
...searchRecordsExcludingPickedRecordsWithoutDuplicates,
|
||||
];
|
||||
|
||||
searchRecords.forEach((searchRecord) => {
|
||||
set(
|
||||
searchRecordStoreComponentFamilyState.atomFamily({
|
||||
instanceId: multipleRecordPickerInstanceId,
|
||||
familyKey: searchRecord.recordId,
|
||||
}),
|
||||
searchRecord,
|
||||
);
|
||||
});
|
||||
|
||||
if (searchRecords.length > 0) {
|
||||
const filterPerMetadataItemFilteredOnRecordId = Object.fromEntries(
|
||||
searchableObjectMetadataItems
|
||||
.map(({ nameSingular }) => {
|
||||
const recordIdsForMetadataItem = searchRecords
|
||||
.filter(
|
||||
({ objectSingularName }) =>
|
||||
objectSingularName === nameSingular,
|
||||
)
|
||||
.map(({ recordId }) => recordId);
|
||||
|
||||
if (!isNonEmptyArray(recordIdsForMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
`filter${capitalize(nameSingular)}`,
|
||||
{
|
||||
id: {
|
||||
in: recordIdsForMetadataItem,
|
||||
},
|
||||
},
|
||||
];
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
const operationSignatures = searchableObjectMetadataItems
|
||||
.filter(({ nameSingular }) =>
|
||||
isDefined(
|
||||
filterPerMetadataItemFilteredOnRecordId[
|
||||
`filter${capitalize(nameSingular)}`
|
||||
],
|
||||
),
|
||||
)
|
||||
.map((objectMetadataItem) => ({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
variables: {
|
||||
filter:
|
||||
filterPerMetadataItemFilteredOnRecordId[
|
||||
`filter${capitalize(objectMetadataItem.nameSingular)}`
|
||||
],
|
||||
},
|
||||
}));
|
||||
|
||||
performCombinedFindManyRecords({ operationSignatures }).then(
|
||||
({ result }) => {
|
||||
Object.values(result)
|
||||
.flat()
|
||||
.forEach((objectRecord) => {
|
||||
const searchRecord = searchRecords.find(
|
||||
({ recordId }) => recordId === objectRecord.id,
|
||||
);
|
||||
|
||||
if (!searchRecord) {
|
||||
return;
|
||||
}
|
||||
|
||||
set(
|
||||
searchRecordStoreComponentFamilyState.atomFamily({
|
||||
instanceId: multipleRecordPickerInstanceId,
|
||||
familyKey: objectRecord.id,
|
||||
}),
|
||||
{
|
||||
...searchRecord,
|
||||
record: objectRecord,
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
[client],
|
||||
[client, performCombinedFindManyRecords],
|
||||
);
|
||||
|
||||
return { performSearch };
|
||||
};
|
||||
|
||||
const performSearchForPickedRecords = async ({
|
||||
const performSearchQueries = async ({
|
||||
client,
|
||||
searchFilter,
|
||||
searchableObjectMetadataItems,
|
||||
pickableMorphItems,
|
||||
pickedRecordIds,
|
||||
}: {
|
||||
client: ApolloClient<object>;
|
||||
searchFilter: string;
|
||||
searchableObjectMetadataItems: ObjectMetadataItem[];
|
||||
pickableMorphItems: RecordPickerPickableMorphItem[];
|
||||
}) => {
|
||||
const pickedMorphItems = pickableMorphItems.filter(
|
||||
({ isSelected }) => isSelected,
|
||||
);
|
||||
|
||||
const filterPerMetadataItemFilteredOnPickedRecordId = Object.fromEntries(
|
||||
searchableObjectMetadataItems
|
||||
.map(({ id, nameSingular }) => {
|
||||
const pickedRecordIdsForMetadataItem = pickedMorphItems
|
||||
.filter(
|
||||
({ objectMetadataId, isSelected }) =>
|
||||
objectMetadataId === id && isSelected,
|
||||
)
|
||||
.map(({ recordId }) => recordId);
|
||||
|
||||
if (!isNonEmptyArray(pickedRecordIdsForMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
`filter${capitalize(nameSingular)}`,
|
||||
{
|
||||
id: {
|
||||
in: pickedRecordIdsForMetadataItem,
|
||||
},
|
||||
},
|
||||
];
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
const searchableObjectMetadataItemsFilteredOnPickedRecordId =
|
||||
searchableObjectMetadataItems.filter(({ nameSingular }) =>
|
||||
isDefined(
|
||||
filterPerMetadataItemFilteredOnPickedRecordId[
|
||||
`filter${capitalize(nameSingular)}`
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (!isNonEmptyArray(searchableObjectMetadataItemsFilteredOnPickedRecordId)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const combinedSearchRecordsQueryFilteredOnPickedRecords =
|
||||
generateCombinedSearchRecordsQuery({
|
||||
objectMetadataItems:
|
||||
searchableObjectMetadataItemsFilteredOnPickedRecordId,
|
||||
operationSignatures:
|
||||
searchableObjectMetadataItemsFilteredOnPickedRecordId.map(
|
||||
(objectMetadataItem) => ({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
variables: {},
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const limitPerMetadataItem = getLimitPerMetadataItem(
|
||||
searchableObjectMetadataItemsFilteredOnPickedRecordId,
|
||||
10,
|
||||
);
|
||||
|
||||
const { data: combinedSearchRecordFilteredOnPickedRecordsQueryResult } =
|
||||
await client.query<CombinedFindManyRecordsQueryResult>({
|
||||
query: combinedSearchRecordsQueryFilteredOnPickedRecords,
|
||||
variables: {
|
||||
search: searchFilter,
|
||||
...limitPerMetadataItem,
|
||||
...filterPerMetadataItemFilteredOnPickedRecordId,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
recordsWithObjectMetadataId:
|
||||
recordsWithObjectMetadataIdFilteredOnPickedRecords,
|
||||
} = multipleRecordPickerformatQueryResultAsRecordsWithObjectMetadataId({
|
||||
objectMetadataItems: searchableObjectMetadataItems,
|
||||
searchQueryResult: combinedSearchRecordFilteredOnPickedRecordsQueryResult,
|
||||
});
|
||||
|
||||
return recordsWithObjectMetadataIdFilteredOnPickedRecords;
|
||||
};
|
||||
|
||||
const performSearchExcludingPickedRecords = async ({
|
||||
client,
|
||||
searchFilter,
|
||||
searchableObjectMetadataItems,
|
||||
pickableMorphItems,
|
||||
}: {
|
||||
client: ApolloClient<object>;
|
||||
searchFilter: string;
|
||||
searchableObjectMetadataItems: ObjectMetadataItem[];
|
||||
pickableMorphItems: RecordPickerPickableMorphItem[];
|
||||
}) => {
|
||||
pickedRecordIds: string[];
|
||||
}): Promise<[GlobalSearchRecord[], GlobalSearchRecord[]]> => {
|
||||
if (searchableObjectMetadataItems.length === 0) {
|
||||
return [];
|
||||
return [[], []];
|
||||
}
|
||||
|
||||
const pickedMorphItems = pickableMorphItems.filter(
|
||||
({ isSelected }) => isSelected,
|
||||
);
|
||||
|
||||
const filterPerMetadataItemExcludingPickedRecordId = Object.fromEntries(
|
||||
searchableObjectMetadataItems
|
||||
.map(({ id, nameSingular }) => {
|
||||
const pickedRecordIdsForMetadataItem = pickedMorphItems
|
||||
.filter(
|
||||
({ objectMetadataId, isSelected }) =>
|
||||
objectMetadataId === id && isSelected,
|
||||
)
|
||||
.map(({ recordId }) => recordId);
|
||||
|
||||
if (!isNonEmptyArray(pickedRecordIdsForMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
`filter${capitalize(nameSingular)}`,
|
||||
{
|
||||
not: {
|
||||
id: {
|
||||
in: pickedRecordIdsForMetadataItem,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
|
||||
const combinedSearchRecordsQueryExcludingPickedRecords =
|
||||
generateCombinedSearchRecordsQuery({
|
||||
objectMetadataItems: searchableObjectMetadataItems,
|
||||
operationSignatures: searchableObjectMetadataItems.map(
|
||||
(objectMetadataItem) => ({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
variables: {},
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const limitPerMetadataItem = getLimitPerMetadataItem(
|
||||
searchableObjectMetadataItems,
|
||||
10,
|
||||
);
|
||||
|
||||
const { data: combinedSearchRecordExcludingPickedRecordsQueryResult } =
|
||||
await client.query<CombinedFindManyRecordsQueryResult>({
|
||||
query: combinedSearchRecordsQueryExcludingPickedRecords,
|
||||
const searchRecords = async (filter: any) => {
|
||||
const { data } = await client.query({
|
||||
query: globalSearch,
|
||||
variables: {
|
||||
search: searchFilter,
|
||||
...limitPerMetadataItem,
|
||||
...filterPerMetadataItemExcludingPickedRecordId,
|
||||
searchInput: searchFilter,
|
||||
includedObjectNameSingulars: searchableObjectMetadataItems.map(
|
||||
({ nameSingular }) => nameSingular,
|
||||
),
|
||||
filter,
|
||||
limit: MAX_SEARCH_RESULTS,
|
||||
},
|
||||
});
|
||||
return data.globalSearch;
|
||||
};
|
||||
|
||||
const {
|
||||
recordsWithObjectMetadataId:
|
||||
recordsWithObjectMetadataIdExcludingPickedRecords,
|
||||
} = multipleRecordPickerformatQueryResultAsRecordsWithObjectMetadataId({
|
||||
objectMetadataItems: searchableObjectMetadataItems,
|
||||
searchQueryResult: combinedSearchRecordExcludingPickedRecordsQueryResult,
|
||||
});
|
||||
const searchRecordsExcludingPickedRecords = await searchRecords(
|
||||
pickedRecordIds.length > 0
|
||||
? {
|
||||
not: {
|
||||
id: {
|
||||
in: pickedRecordIds,
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
return recordsWithObjectMetadataIdExcludingPickedRecords;
|
||||
const searchRecordsIncludingPickedRecords =
|
||||
pickedRecordIds.length > 0
|
||||
? await searchRecords({
|
||||
id: {
|
||||
in: pickedRecordIds,
|
||||
},
|
||||
})
|
||||
: [];
|
||||
|
||||
return [
|
||||
searchRecordsIncludingPickedRecords,
|
||||
searchRecordsExcludingPickedRecords,
|
||||
];
|
||||
};
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
|
||||
import { GlobalSearchRecord } from '~/generated-metadata/graphql';
|
||||
|
||||
export const searchRecordStoreComponentFamilyState =
|
||||
createComponentFamilyStateV2<
|
||||
(GlobalSearchRecord & { record?: ObjectRecord }) | undefined,
|
||||
string
|
||||
>({
|
||||
key: 'searchRecordStoreComponentFamilyState',
|
||||
defaultValue: undefined,
|
||||
componentInstanceContext: MultipleRecordPickerComponentInstanceContext,
|
||||
});
|
||||
@ -110,16 +110,22 @@ export const FieldsCard = ({
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<ActivityTargetsInlineCell
|
||||
activityObjectNameSingular={
|
||||
objectNameSingular as
|
||||
| CoreObjectNameSingular.Note
|
||||
| CoreObjectNameSingular.Task
|
||||
}
|
||||
activityRecordId={objectRecordId}
|
||||
showLabel={true}
|
||||
maxWidth={200}
|
||||
/>
|
||||
<RecordFieldComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: objectRecordId + fieldMetadataItem.id,
|
||||
}}
|
||||
>
|
||||
<ActivityTargetsInlineCell
|
||||
activityObjectNameSingular={
|
||||
objectNameSingular as
|
||||
| CoreObjectNameSingular.Note
|
||||
| CoreObjectNameSingular.Task
|
||||
}
|
||||
activityRecordId={objectRecordId}
|
||||
showLabel={true}
|
||||
maxWidth={200}
|
||||
/>
|
||||
</RecordFieldComponentInstanceContext.Provider>
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
)}
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||
import { getSearchRecordsQueryResponseField } from '@/object-record/utils/getSearchRecordsQueryResponseField';
|
||||
import { capitalize } from 'twenty-shared';
|
||||
|
||||
export type QueryCursorDirection = 'before' | 'after';
|
||||
|
||||
export const generateSearchRecordsQuery = ({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||
computeReferences?: boolean;
|
||||
}) => gql`
|
||||
query Search${capitalize(objectMetadataItem.namePlural)}($search: String, $limit: Int, $filter: ${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}FilterInput) {
|
||||
${getSearchRecordsQueryResponseField(objectMetadataItem.namePlural)}(searchInput: $search, limit: $limit, filter: $filter){
|
||||
edges {
|
||||
node ${mapObjectMetadataToGraphQLQuery({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
})}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -1,4 +0,0 @@
|
||||
import { capitalize } from 'twenty-shared';
|
||||
|
||||
export const getSearchRecordsQueryResponseField = (objectNamePlural: string) =>
|
||||
`search${capitalize(objectNamePlural)}`;
|
||||
Reference in New Issue
Block a user