Improve multi word filtering (#5034)

improve multi word search

closes #4212 
closes #3386
This commit is contained in:
martmull 2024-04-18 15:46:59 +02:00 committed by GitHub
parent 88c14b7e52
commit 1c1a055c94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 212 additions and 146 deletions

View File

@ -2,7 +2,7 @@ import { Route, Routes, useLocation } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { VerifyEffect } from '@/auth/components/VerifyEffect';
import { billingState } from '@/client-config/states/billingState.ts';
import { billingState } from '@/client-config/states/billingState';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { BlankLayout } from '@/ui/layout/page/BlankLayout';

View File

@ -11,7 +11,7 @@ import { iconsState } from 'twenty-ui';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState.ts';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
import { isVerifyPendingState } from '@/auth/states/isVerifyPendingState';
import { workspacesState } from '@/auth/states/workspaces';
import { authProvidersState } from '@/client-config/states/authProvidersState';

View File

@ -6,11 +6,11 @@ import { motion } from 'framer-motion';
import { useRecoilState } from 'recoil';
import { IconGoogle } from 'twenty-ui';
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword.ts';
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm.ts';
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle.ts';
import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts';
import { authProvidersState } from '@/client-config/states/authProvidersState.ts';
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle';
import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { Loader } from '@/ui/feedback/loader/components/Loader';
import { MainButton } from '@/ui/input/button/components/MainButton';
import { TextInput } from '@/ui/input/components/TextInput';

View File

@ -2,10 +2,10 @@ import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState.ts';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
import { previousUrlState } from '@/auth/states/previousUrlState';
import { billingState } from '@/client-config/states/billingState.ts';
import { AppPath } from '@/types/AppPath.ts';
import { billingState } from '@/client-config/states/billingState';
import { AppPath } from '@/types/AppPath';
import { WorkspaceMember } from '~/generated/graphql.tsx';
export const useNavigateAfterSignInUp = () => {

View File

@ -2,8 +2,8 @@ import { useCallback, useState } from 'react';
import { SubmitHandler, UseFormReturn } from 'react-hook-form';
import { useParams } from 'react-router-dom';
import { useNavigateAfterSignInUp } from '@/auth/sign-in-up/hooks/useNavigateAfterSignInUp.ts';
import { Form } from '@/auth/sign-in-up/hooks/useSignInUpForm.ts';
import { useNavigateAfterSignInUp } from '@/auth/sign-in-up/hooks/useNavigateAfterSignInUp';
import { Form } from '@/auth/sign-in-up/hooks/useSignInUpForm';
import { AppPath } from '@/types/AppPath';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';

View File

@ -4,8 +4,8 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { useRecoilValue } from 'recoil';
import { z } from 'zod';
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex.ts';
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState.ts';
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
const validationSchema = z
.object({

View File

@ -1,6 +1,6 @@
import { useParams } from 'react-router-dom';
import { useAuth } from '@/auth/hooks/useAuth.ts';
import { useAuth } from '@/auth/hooks/useAuth';
export const useSignInWithGoogle = () => {
const workspaceInviteHash = useParams().workspaceInviteHash;

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled';
import { SubscriptionCardPrice } from '@/billing/components/SubscriptionCardPrice.tsx';
import { capitalize } from '~/utils/string/capitalize.ts';
import { capitalize } from '~/utils/string/capitalize';
type SubscriptionCardProps = {
type?: string;

View File

@ -22,6 +22,7 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { getLogoUrlFromDomainName } from '~/utils';
import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields';
import { isDefined } from '~/utils/isDefined';
import { useCommandMenu } from '../hooks/useCommandMenu';
@ -142,8 +143,10 @@ export const CommandMenu = () => {
objectNameSingular: CoreObjectNameSingular.Person,
filter: commandMenuSearch
? makeOrFilterVariables([
{ name: { firstName: { ilike: `%${commandMenuSearch}%` } } },
{ name: { lastName: { ilike: `%${commandMenuSearch}%` } } },
...generateILikeFiltersForCompositeFields(commandMenuSearch, 'name', [
'firstName',
'lastName',
]),
{ email: { ilike: `%${commandMenuSearch}%` } },
{ phone: { ilike: `%${commandMenuSearch}%` } },
])

View File

@ -1,7 +1,7 @@
import React from 'react';
import { useRecoilValue } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState.ts';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { ObjectMetadataItemsLoadEffect } from '@/object-metadata/components/ObjectMetadataItemsLoadEffect';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';

View File

@ -1,6 +1,6 @@
import { v4 } from 'uuid';
import { FieldMetadataOption } from '@/object-metadata/types/FieldMetadataOption.ts';
import { FieldMetadataOption } from '@/object-metadata/types/FieldMetadataOption';
import { getDefaultValueForBackend } from '@/object-metadata/utils/getDefaultValueForBackend';
import { Field } from '~/generated/graphql';

View File

@ -1,6 +1,6 @@
import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState.ts';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/ObjectMetadataNotFoundError';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';

View File

@ -1,6 +1,6 @@
import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState.ts';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { isDefined } from '~/utils/isDefined';

View File

@ -1,6 +1,6 @@
import { useRecoilValue } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState.ts';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import { isDefined } from '~/utils/isDefined';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import {
formatFieldMetadataItemInput,

View File

@ -2,7 +2,7 @@ import toCamelCase from 'lodash.camelcase';
import toSnakeCase from 'lodash.snakecase';
import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined.ts';
import { isDefined } from '~/utils/isDefined';
import { FieldMetadataOption } from '../types/FieldMetadataOption';

View File

@ -24,7 +24,7 @@ import { isFieldDateTime } from '../types/guards/isFieldDateTime';
import { isFieldEmail } from '../types/guards/isFieldEmail';
import { isFieldFullName } from '../types/guards/isFieldFullName';
import { isFieldLink } from '../types/guards/isFieldLink';
import { isFieldMultiSelect } from '../types/guards/isFieldMultiSelect.ts';
import { isFieldMultiSelect } from '../types/guards/isFieldMultiSelect';
import { isFieldNumber } from '../types/guards/isFieldNumber';
import { isFieldPhone } from '../types/guards/isFieldPhone';
import { isFieldRawJson } from '../types/guards/isFieldRawJson';

View File

@ -9,7 +9,7 @@ import { SelectFieldInput } from '@/object-record/record-field/meta-types/input/
import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope';
import { isFieldDate } from '@/object-record/record-field/types/guards/isFieldDate';
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect.ts';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';

View File

@ -7,8 +7,8 @@ import { isFieldDate } from '@/object-record/record-field/types/guards/isFieldDa
import { isFieldDateValue } from '@/object-record/record-field/types/guards/isFieldDateValue';
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
import { isFieldFullNameValue } from '@/object-record/record-field/types/guards/isFieldFullNameValue';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect.ts';
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue.ts';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue';
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';

View File

@ -1,6 +1,6 @@
import styled from '@emotion/styled';
import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField.ts';
import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField';
import { Tag } from '@/ui/display/tag/components/Tag';
const StyledTagContainer = styled.div`

View File

@ -1,14 +1,14 @@
import { useContext } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext.ts';
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField.ts';
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput.ts';
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata.ts';
import { assertFieldMetadata } from '@/object-record/record-field/types/guards/assertFieldMetadata.ts';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect.ts';
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue.ts';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector.ts';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
import { assertFieldMetadata } from '@/object-record/record-field/types/guards/assertFieldMetadata';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { FieldMetadataType } from '~/generated/graphql.tsx';
export const useMultiSelectField = () => {

View File

@ -1,7 +1,7 @@
import { useRef, useState } from 'react';
import styled from '@emotion/styled';
import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField.ts';
import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldAddressMetadata, FieldMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldBooleanMetadata, FieldMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldCurrencyMetadata, FieldMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldDateTimeMetadata, FieldMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldEmailMetadata, FieldMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldFullNameMetadata, FieldMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldLinkMetadata, FieldMetadata } from '../FieldMetadata';

View File

@ -1,9 +1,9 @@
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition.ts';
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import {
FieldMetadata,
FieldMultiSelectMetadata,
} from '@/object-record/record-field/types/FieldMetadata.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
} from '@/object-record/record-field/types/FieldMetadata';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const isFieldMultiSelect = (
field: Pick<FieldDefinition<FieldMetadata>, 'type'>,

View File

@ -1,5 +1,5 @@
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata.ts';
import { multiSelectFieldValueSchema } from '@/object-record/record-field/validation-schemas/multiSelectFieldValueSchema.ts';
import { FieldMultiSelectValue } from '@/object-record/record-field/types/FieldMetadata';
import { multiSelectFieldValueSchema } from '@/object-record/record-field/validation-schemas/multiSelectFieldValueSchema';
export const isFieldMultiSelectValue = (
fieldValue: unknown,

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldMetadata, FieldNumberMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldMetadata, FieldPhoneMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldMetadata, FieldRawJsonMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldMetadata, FieldRelationMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldMetadata, FieldTextMetadata } from '../FieldMetadata';

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FieldDefinition } from '../FieldDefinition';
import { FieldMetadata, FieldUuidMetadata } from '../FieldMetadata';

View File

@ -12,8 +12,8 @@ import { isFieldFullName } from '@/object-record/record-field/types/guards/isFie
import { isFieldFullNameValue } from '@/object-record/record-field/types/guards/isFieldFullNameValue';
import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLink';
import { isFieldLinkValue } from '@/object-record/record-field/types/guards/isFieldLinkValue';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect.ts';
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue.ts';
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue';
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating';
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';

View File

@ -5,7 +5,6 @@ import {
CurrencyFilter,
DateFilter,
FloatFilter,
FullNameFilter,
ObjectRecordQueryFilter,
StringFilter,
URLFilter,
@ -14,6 +13,7 @@ import {
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { Field } from '~/generated/graphql';
import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields';
import { isDefined } from '~/utils/isDefined';
import { Filter } from '../../object-filter-dropdown/types/Filter';
@ -203,50 +203,25 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
);
}
break;
case 'FULL_NAME':
case 'FULL_NAME': {
const fullNameFilters = generateILikeFiltersForCompositeFields(
rawUIFilter.value,
correspondingField.name,
['firstName', 'lastName'],
);
switch (rawUIFilter.operand) {
case ViewFilterOperand.Contains:
objectRecordFilters.push({
or: [
{
[correspondingField.name]: {
firstName: {
ilike: `%${rawUIFilter.value}%`,
},
} as FullNameFilter,
},
{
[correspondingField.name]: {
lastName: {
ilike: `%${rawUIFilter.value}%`,
},
} as FullNameFilter,
},
],
or: fullNameFilters,
});
break;
case ViewFilterOperand.DoesNotContain:
objectRecordFilters.push({
and: [
{
not: {
[correspondingField.name]: {
firstName: {
ilike: `%${rawUIFilter.value}%`,
},
} as FullNameFilter,
},
},
{
not: {
[correspondingField.name]: {
lastName: {
ilike: `%${rawUIFilter.value}%`,
},
} as FullNameFilter,
},
},
],
and: fullNameFilters.map((filter) => {
return {
not: filter,
};
}),
});
break;
default:
@ -255,6 +230,7 @@ export const turnObjectDropdownFilterIntoQueryFilter = (
);
}
break;
}
case 'ADDRESS':
switch (rawUIFilter.operand) {
case ViewFilterOperand.Contains:

View File

@ -1,6 +1,6 @@
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState.ts';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';

View File

@ -9,7 +9,7 @@ import {
} from 'twenty-ui';
import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId';
import { useExportTableData } from '@/object-record/record-index/options/hooks/useExportTableData.ts';
import { useExportTableData } from '@/object-record/record-index/options/hooks/useExportTableData';
import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard';
import { useRecordIndexOptionsForTable } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForTable';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';

View File

@ -14,7 +14,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { RecordChip } from '@/object-record/components/RecordChip';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord.ts';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useLazyFindOneRecord } from '@/object-record/hooks/useLazyFindOneRecord';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import {

View File

@ -5,6 +5,7 @@ import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/get
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
import { FieldMetadataType } from '~/generated/graphql';
import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields';
import { isDefined } from '~/utils/isDefined';
export const useSearchFilterPerMetadataItem = ({
@ -29,25 +30,16 @@ export const useSearchFilterPerMetadataItem = ({
switch (labelIdentifierFieldMetadataItem.type) {
case FieldMetadataType.FullName: {
if (isNonEmptyString(searchFilterValue)) {
const fullNameFilter = makeOrFilterVariables([
{
[labelIdentifierFieldMetadataItem.name]: {
firstName: {
ilike: `%${searchFilterValue}%`,
},
},
},
{
[labelIdentifierFieldMetadataItem.name]: {
lastName: {
ilike: `%${searchFilterValue}%`,
},
},
},
]);
const compositeFilter = makeOrFilterVariables(
generateILikeFiltersForCompositeFields(
searchFilterValue,
labelIdentifierFieldMetadataItem.name,
['firstName', 'lastName'],
),
);
if (isDefined(fullNameFilter)) {
searchFilter = fullNameFilter;
if (isDefined(compositeFilter)) {
searchFilter = compositeFilter;
}
}
break;

View File

@ -4,11 +4,13 @@ import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapTo
import { OrderBy } from '@/object-metadata/types/OrderBy';
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields';
import { isDefined } from '~/utils/isDefined';
type SearchFilter = { fieldNames: string[]; filter: string | number };
@ -56,28 +58,33 @@ export const useFilteredSearchEntityQuery = ({
return undefined;
}
return makeOrFilterVariables(
fieldNames.map((fieldName) => {
const formattedFilters = fieldNames.reduce(
(previousValue: ObjectRecordQueryFilter[], fieldName) => {
const [parentFieldName, subFieldName] = fieldName.split('.');
if (isNonEmptyString(subFieldName)) {
// Composite field
return {
[parentFieldName]: {
[subFieldName]: {
ilike: `%${filter}%`,
},
},
};
return [
...previousValue,
...generateILikeFiltersForCompositeFields(filter, parentFieldName, [
subFieldName,
]),
];
}
return {
[fieldName]: {
ilike: `%${filter}%`,
return [
...previousValue,
{
[fieldName]: {
ilike: `%${filter}%`,
},
},
};
}),
];
},
[],
);
return makeOrFilterVariables(formattedFilters);
});
const {

View File

@ -17,7 +17,7 @@ import {
} from 'twenty-ui';
import { useAuth } from '@/auth/hooks/useAuth';
import { billingState } from '@/client-config/states/billingState.ts';
import { billingState } from '@/client-config/states/billingState';
import { SettingsNavigationDrawerItem } from '@/settings/components/SettingsNavigationDrawerItem';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';

View File

@ -1,4 +1,4 @@
import { NEVER_EXPIRE_DELTA_IN_YEARS } from '@/settings/developers/constants/NeverExpireDeltaInYears.ts';
import { NEVER_EXPIRE_DELTA_IN_YEARS } from '@/settings/developers/constants/NeverExpireDeltaInYears';
export const EXPIRATION_DATES: {
value: number | null;

View File

@ -1,7 +1,7 @@
import { isNonEmptyString } from '@sniptt/guards';
import { DateTime } from 'luxon';
import { NEVER_EXPIRE_DELTA_IN_YEARS } from '@/settings/developers/constants/NeverExpireDeltaInYears.ts';
import { NEVER_EXPIRE_DELTA_IN_YEARS } from '@/settings/developers/constants/NeverExpireDeltaInYears';
import { ApiFieldItem } from '@/settings/developers/types/api-key/ApiFieldItem';
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
import { beautifyDateDiff } from '~/utils/date-utils';

View File

@ -7,10 +7,10 @@ import { SubTitle } from '@/auth/components/SubTitle.tsx';
import { Title } from '@/auth/components/Title.tsx';
import { SubscriptionBenefit } from '@/billing/components/SubscriptionBenefit.tsx';
import { SubscriptionCard } from '@/billing/components/SubscriptionCard.tsx';
import { billingState } from '@/client-config/states/billingState.ts';
import { AppPath } from '@/types/AppPath.ts';
import { billingState } from '@/client-config/states/billingState';
import { AppPath } from '@/types/AppPath';
import { Loader } from '@/ui/feedback/loader/components/Loader.tsx';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar.ts';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { MainButton } from '@/ui/input/button/components/MainButton.tsx';
import { CardPicker } from '@/ui/input/components/CardPicker.tsx';
import {

View File

@ -13,7 +13,7 @@ import { Logo } from '@/auth/components/Logo';
import { Title } from '@/auth/components/Title';
import { useAuth } from '@/auth/hooks/useAuth';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { useNavigateAfterSignInUp } from '@/auth/sign-in-up/hooks/useNavigateAfterSignInUp.ts';
import { useNavigateAfterSignInUp } from '@/auth/sign-in-up/hooks/useNavigateAfterSignInUp';
import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex';
import { AppPath } from '@/types/AppPath';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';

View File

@ -6,9 +6,9 @@ import { IconCheck } from 'twenty-ui';
import { SubTitle } from '@/auth/components/SubTitle.tsx';
import { Title } from '@/auth/components/Title.tsx';
import { AppPath } from '@/types/AppPath.ts';
import { AppPath } from '@/types/AppPath';
import { MainButton } from '@/ui/input/button/components/MainButton.tsx';
import { RGBA } from '@/ui/theme/constants/Rgba.ts';
import { RGBA } from '@/ui/theme/constants/Rgba';
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn.tsx';
const StyledCheckContainer = styled.div`

View File

@ -9,13 +9,13 @@ import {
IconCurrencyDollar,
} from 'twenty-ui';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus.ts';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState.ts';
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus.ts';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { SettingsBillingCoverImage } from '@/billing/components/SettingsBillingCoverImage';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SupportChat } from '@/support/components/SupportChat';
import { AppPath } from '@/types/AppPath.ts';
import { AppPath } from '@/types/AppPath';
import { Info } from '@/ui/display/info/components/Info';
import { H1Title } from '@/ui/display/typography/components/H1Title';
import { H2Title } from '@/ui/display/typography/components/H2Title';

View File

@ -28,7 +28,7 @@ import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { View } from '@/views/types/View';
import { ViewType } from '@/views/types/ViewType';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled.ts';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';

View File

@ -0,0 +1,64 @@
import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields';
describe('generateILikeFiltersForCompositeFields', () => {
it('should format composite filters for simple filter string', () => {
expect(
generateILikeFiltersForCompositeFields('john', 'baseField', [
'subField1',
'subField2',
]),
).toEqual([
{
baseField: {
subField1: {
ilike: '%john%',
},
},
},
{
baseField: {
subField2: {
ilike: '%john%',
},
},
},
]);
});
it('should format composite filters for complex filter string', () => {
expect(
generateILikeFiltersForCompositeFields('john doe', 'name', [
'firstName',
'lastName',
]),
).toEqual([
{
name: {
firstName: {
ilike: '%john%',
},
},
},
{
name: {
lastName: {
ilike: '%john%',
},
},
},
{
name: {
firstName: {
ilike: '%doe%',
},
},
},
{
name: {
lastName: {
ilike: '%doe%',
},
},
},
]);
});
});

View File

@ -0,0 +1,24 @@
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
export const generateILikeFiltersForCompositeFields = (
filterString: string,
baseFieldName: string,
subFields: string[],
) => {
return filterString
.split(' ')
.reduce((previousValue: ObjectRecordQueryFilter[], currentValue) => {
return [
...previousValue,
...subFields.map((subField) => {
return {
[baseFieldName]: {
[subField]: {
ilike: `%${currentValue}%`,
},
},
};
}),
];
}, []);
};