diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts
index bde0675d4..efef632d8 100644
--- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts
+++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts
@@ -1,6 +1,6 @@
import { FieldMetadataType } from '~/generated-metadata/graphql';
-import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
+import { FilterableAndTSVectorFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
export const getRelationObjectMetadataNameSingular = ({
@@ -13,7 +13,7 @@ export const getRelationObjectMetadataNameSingular = ({
export const getFilterTypeFromFieldType = (
fieldType: FieldMetadataType,
-): FilterableFieldType => {
+): FilterableAndTSVectorFieldType => {
switch (fieldType) {
case FieldMetadataType.DATE_TIME:
return 'DATE_TIME';
@@ -49,6 +49,8 @@ export const getFilterTypeFromFieldType = (
return 'RAW_JSON';
case FieldMetadataType.BOOLEAN:
return 'BOOLEAN';
+ case FieldMetadataType.TS_VECTOR:
+ return 'TS_VECTOR';
default:
return 'TEXT';
}
diff --git a/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationFilter.ts b/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationFilter.ts
index 28c0d6b45..78ce99b03 100644
--- a/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationFilter.ts
+++ b/packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationFilter.ts
@@ -142,6 +142,10 @@ export type RichTextV2Filter = {
markdown?: RichTextV2LeafFilter;
};
+export type TSVectorFilter = {
+ search: string;
+};
+
export type LeafFilter =
| UUIDFilter
| StringFilter
@@ -158,6 +162,7 @@ export type LeafFilter =
| ArrayFilter
| RawJsonFilter
| RichTextV2Filter
+ | TSVectorFilter
| undefined;
export type AndObjectRecordFilter = {
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx
index 38d7dd55e..a2cf93c49 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx
@@ -5,6 +5,7 @@ import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-d
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
+import { ViewBarFilterDropdownVectorSearchInput } from '@/views/components/ViewBarFilterDropdownVectorSearchInput';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
@@ -55,6 +56,17 @@ export const ObjectFilterDropdownFilterInput = ({
ViewFilterOperand.IsRelative,
].includes(selectedOperandInDropdown);
+ const isVectorSearchFilter =
+ selectedOperandInDropdown === ViewFilterOperand.VectorSearch;
+
+ if (isVectorSearchFilter && isDefined(filterDropdownId)) {
+ return (
+
+ );
+ }
+
if (!isDefined(fieldMetadataItemUsedInDropdown)) {
return null;
}
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getInitialFilterValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getInitialFilterValue.ts
index 95f689b21..ab6752897 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getInitialFilterValue.ts
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getInitialFilterValue.ts
@@ -1,10 +1,10 @@
-import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
+import { FilterableAndTSVectorFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
import { z } from 'zod';
export const getInitialFilterValue = (
- newType: FilterableFieldType,
+ newType: FilterableAndTSVectorFieldType,
newOperand: RecordFilterOperand,
oldValue?: string,
oldDisplayValue?: string,
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts
index 0c0d20798..3ac0876c4 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts
@@ -1,7 +1,11 @@
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
+import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
+import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState';
+import { isVectorSearchFilter } from '@/views/utils/isVectorSearchFilter';
import { useRecoilCallback } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
export const useRemoveRecordFilter = () => {
const currentRecordFiltersCallbackState = useRecoilComponentCallbackStateV2(
@@ -16,24 +20,34 @@ export const useRemoveRecordFilter = () => {
currentRecordFiltersCallbackState,
);
- const foundRecordFilterInCurrentRecordFilters =
- currentRecordFilters.some(
+ const filterToRemove = currentRecordFilters.find(
+ (existingFilter) => existingFilter.id === recordFilterId,
+ );
+
+ if (!isDefined(filterToRemove)) {
+ return;
+ }
+
+ if (isVectorSearchFilter(filterToRemove)) {
+ set(
+ vectorSearchInputComponentState.atomFamily({
+ instanceId: VIEW_BAR_FILTER_DROPDOWN_ID,
+ }),
+ '',
+ );
+ }
+
+ set(currentRecordFiltersCallbackState, (currentRecordFilters) => {
+ const newCurrentRecordFilters = [...currentRecordFilters];
+
+ const indexOfFilterToRemove = newCurrentRecordFilters.findIndex(
(existingFilter) => existingFilter.id === recordFilterId,
);
- if (foundRecordFilterInCurrentRecordFilters) {
- set(currentRecordFiltersCallbackState, (currentRecordFilters) => {
- const newCurrentRecordFilters = [...currentRecordFilters];
+ newCurrentRecordFilters.splice(indexOfFilterToRemove, 1);
- const indexOfFilterToRemove = newCurrentRecordFilters.findIndex(
- (existingFilter) => existingFilter.id === recordFilterId,
- );
-
- newCurrentRecordFilters.splice(indexOfFilterToRemove, 1);
-
- return newCurrentRecordFilters;
- });
- }
+ return newCurrentRecordFilters;
+ });
},
[currentRecordFiltersCallbackState],
);
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/types/FilterableFieldType.ts b/packages/twenty-front/src/modules/object-record/record-filter/types/FilterableFieldType.ts
index b54b72b80..2d6c2d7af 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/types/FilterableFieldType.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/types/FilterableFieldType.ts
@@ -28,3 +28,5 @@ export type FilterableFieldType = PickLiteral<
FieldType,
FilterableFieldTypeBaseLiteral
>;
+
+export type FilterableAndTSVectorFieldType = FilterableFieldType | 'TS_VECTOR';
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
index 88101fd17..93396f314 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
@@ -1,4 +1,4 @@
-import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
+import { FilterableAndTSVectorFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
@@ -8,7 +8,7 @@ export type RecordFilter = {
fieldMetadataId: string;
value: string;
displayValue: string;
- type: FilterableFieldType;
+ type: FilterableAndTSVectorFieldType;
recordFilterGroupId?: string;
displayAvatarUrl?: string;
operand: ViewFilterOperand;
@@ -17,5 +17,6 @@ export type RecordFilter = {
subFieldName?: CompositeFieldSubFieldName | null | undefined;
};
-export type RecordFilterToRecordInputOperand =
- (typeof FILTER_OPERANDS_MAP)[T][number];
+export type RecordFilterToRecordInputOperand<
+ T extends FilterableAndTSVectorFieldType,
+> = (typeof FILTER_OPERANDS_MAP)[T][number];
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingTSVectorFilter.test.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingTSVectorFilter.test.ts
new file mode 100644
index 000000000..c015bfc4e
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingTSVectorFilter.test.ts
@@ -0,0 +1,61 @@
+import { isMatchingTSVectorFilter } from '@/object-record/record-filter/utils/isMatchingTSVectorFilter';
+
+describe('isMatchingTSVectorFilter', () => {
+ describe('search', () => {
+ it('value matches search filter', () => {
+ expect(
+ isMatchingTSVectorFilter({
+ tsVectorFilter: { search: 'test' },
+ value: 'test document',
+ }),
+ ).toBe(true);
+ });
+
+ it('value does not match search filter', () => {
+ expect(
+ isMatchingTSVectorFilter({
+ tsVectorFilter: { search: 'missing' },
+ value: 'test document',
+ }),
+ ).toBe(false);
+ });
+
+ it('search is case insensitive', () => {
+ expect(
+ isMatchingTSVectorFilter({
+ tsVectorFilter: { search: 'TEST' },
+ value: 'test document',
+ }),
+ ).toBe(true);
+ });
+
+ it('search matches partial words', () => {
+ expect(
+ isMatchingTSVectorFilter({
+ tsVectorFilter: { search: 'doc' },
+ value: 'test document',
+ }),
+ ).toBe(true);
+ });
+
+ it('search matches multiple words', () => {
+ expect(
+ isMatchingTSVectorFilter({
+ tsVectorFilter: { search: 'test doc' },
+ value: 'test document',
+ }),
+ ).toBe(true);
+ });
+ });
+
+ describe('error handling', () => {
+ it('should throw error for unknown filter type', () => {
+ expect(() =>
+ isMatchingTSVectorFilter({
+ tsVectorFilter: { unknownFilter: 'test' } as any,
+ value: 'test document',
+ }),
+ ).toThrow('Unexpected value for ts_vector filter');
+ });
+ });
+});
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/checkIfShouldComputeEmptinessFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/checkIfShouldComputeEmptinessFilter.ts
index 0d06a990c..c385655c2 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/checkIfShouldComputeEmptinessFilter.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/checkIfShouldComputeEmptinessFilter.ts
@@ -1,6 +1,6 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
-import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
+import { FilterableAndTSVectorFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { isEmptinessOperand } from '@/object-record/record-filter/utils/isEmptinessOperand';
@@ -13,9 +13,8 @@ export const checkIfShouldComputeEmptinessFilter = ({
}) => {
const isAnEmptinessOperand = isEmptinessOperand(recordFilter.operand);
- const filterTypesThatHaveNoEmptinessOperand: FilterableFieldType[] = [
- 'BOOLEAN',
- ];
+ const filterTypesThatHaveNoEmptinessOperand: FilterableAndTSVectorFieldType[] =
+ ['BOOLEAN', 'TS_VECTOR'];
const filterType = getFilterTypeFromFieldType(
correspondingFieldMetadataItem.type,
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter.ts
index 092992bea..d7993c42d 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter.ts
@@ -16,6 +16,7 @@ import {
RelationFilter,
SelectFilter,
StringFilter,
+ TSVectorFilter,
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
import { Field } from '~/generated/graphql';
import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields';
@@ -118,6 +119,19 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({
`Unknown operand ${recordFilter.operand} for ${filterType} filter`,
);
}
+ case 'TS_VECTOR':
+ switch (recordFilter.operand) {
+ case RecordFilterOperand.VectorSearch:
+ return {
+ [correspondingFieldMetadataItem.name]: {
+ search: recordFilter.value,
+ } as TSVectorFilter,
+ };
+ default:
+ throw new Error(
+ `Unknown operand ${recordFilter.operand} for ${filterType} filter`,
+ );
+ }
case 'RAW_JSON':
switch (recordFilter.operand) {
case RecordFilterOperand.Contains:
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
index 828e68462..c3d83330c 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
@@ -1,13 +1,16 @@
import { isExpectedSubFieldName } from '@/object-record/object-filter-dropdown/utils/isExpectedSubFieldName';
import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField';
-import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
+import {
+ FilterableAndTSVectorFieldType,
+ FilterableFieldType,
+} from '@/object-record/record-filter/types/FilterableFieldType';
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
import { ViewFilterOperand as RecordFilterOperand } from '@/views/types/ViewFilterOperand';
import { FieldMetadataType } from 'twenty-shared/types';
import { assertUnreachable } from 'twenty-shared/utils';
export type GetRecordFilterOperandsParams = {
- filterType: FilterableFieldType;
+ filterType: FilterableAndTSVectorFieldType;
subFieldName?: string | null | undefined;
};
@@ -22,7 +25,7 @@ const relationOperands = [
] as const;
type FilterOperandMap = {
- [K in FilterableFieldType]: readonly RecordFilterOperand[];
+ [K in FilterableAndTSVectorFieldType]: readonly RecordFilterOperand[];
};
// TODO: we would need to refactor the typing of SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS first
@@ -124,6 +127,7 @@ export const FILTER_OPERANDS_MAP = {
...emptyOperands,
],
BOOLEAN: [RecordFilterOperand.Is],
+ TS_VECTOR: [RecordFilterOperand.VectorSearch],
} as const satisfies FilterOperandMap;
export const COMPOSITE_FIELD_FILTER_OPERANDS_MAP = {
@@ -198,6 +202,8 @@ export const getRecordFilterOperands = ({
return FILTER_OPERANDS_MAP.ARRAY;
case 'BOOLEAN':
return FILTER_OPERANDS_MAP.BOOLEAN;
+ case 'TS_VECTOR':
+ return FILTER_OPERANDS_MAP.TS_VECTOR;
default:
assertUnreachable(filterType, `Unknown filter type ${filterType}`);
}
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingTSVectorFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingTSVectorFilter.ts
new file mode 100644
index 000000000..0e0a67e66
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingTSVectorFilter.ts
@@ -0,0 +1,28 @@
+import { TSVectorFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
+
+export const isMatchingTSVectorFilter = ({
+ tsVectorFilter,
+ value,
+}: {
+ tsVectorFilter: TSVectorFilter;
+ value: string | undefined;
+}) => {
+ // For optimistic updates where value is undefined, skip filtering
+ if (value === undefined) {
+ return true;
+ }
+
+ switch (true) {
+ case tsVectorFilter.search !== undefined: {
+ const searchQuery = tsVectorFilter.search.toLowerCase();
+ const searchValue = value.toLowerCase();
+ const searchWords = searchQuery.split(/\s+/).filter(Boolean);
+ return searchWords.every((word) => searchValue.includes(word));
+ }
+ default: {
+ throw new Error(
+ `Unexpected value for ts_vector filter : ${JSON.stringify(tsVectorFilter)}`,
+ );
+ }
+ }
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts
index c0234e771..e8c2f0e3c 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts
@@ -24,6 +24,7 @@ import {
RichTextV2Filter,
SelectFilter,
StringFilter,
+ TSVectorFilter,
UUIDFilter,
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
import { isMatchingArrayFilter } from '@/object-record/record-filter/utils/isMatchingArrayFilter';
@@ -37,6 +38,7 @@ import { isMatchingRawJsonFilter } from '@/object-record/record-filter/utils/isM
import { isMatchingRichTextV2Filter } from '@/object-record/record-filter/utils/isMatchingRichTextV2Filter';
import { isMatchingSelectFilter } from '@/object-record/record-filter/utils/isMatchingSelectFilter';
import { isMatchingStringFilter } from '@/object-record/record-filter/utils/isMatchingStringFilter';
+import { isMatchingTSVectorFilter } from '@/object-record/record-filter/utils/isMatchingTSVectorFilter';
import { isMatchingUUIDFilter } from '@/object-record/record-filter/utils/isMatchingUUIDFilter';
import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
@@ -374,10 +376,15 @@ export const isRecordMatchingFilter = ({
}
throw new Error(
- `Not implemented yet, use UUID filter instead on the corredponding "${filterKey}Id" field`,
+ `Not implemented yet, use UUID filter instead on the corresponding "${filterKey}Id" field`,
);
}
-
+ case FieldMetadataType.TS_VECTOR: {
+ return isMatchingTSVectorFilter({
+ tsVectorFilter: filterValue as TSVectorFilter,
+ value: record[filterKey],
+ });
+ }
default: {
throw new Error(
`Not implemented yet for field type "${objectMetadataField.type}"`,
diff --git a/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
index ed8022073..2ec4fd786 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
@@ -66,6 +66,11 @@ export const buildValueFromFilter = ({
filter.operand as (typeof FILTER_OPERANDS_MAP)['BOOLEAN'][number],
filter.value,
);
+ case 'TS_VECTOR':
+ return computeValueFromFilterTSVector(
+ filter.operand as (typeof FILTER_OPERANDS_MAP)['TS_VECTOR'][number],
+ filter.value,
+ );
case 'ARRAY':
return computeValueFromFilterArray(
filter.operand as (typeof FILTER_OPERANDS_MAP)['ARRAY'][number],
@@ -296,3 +301,15 @@ const computeValueFromFilterRelation = (
assertUnreachable(operand);
}
};
+
+const computeValueFromFilterTSVector = (
+ operand: RecordFilterToRecordInputOperand<'TS_VECTOR'>,
+ value: string,
+) => {
+ switch (operand) {
+ case ViewFilterOperand.VectorSearch:
+ return value;
+ default:
+ assertUnreachable(operand);
+ }
+};
diff --git a/packages/twenty-front/src/modules/object-record/utils/isSystemSearchVectorField.ts b/packages/twenty-front/src/modules/object-record/utils/isSystemSearchVectorField.ts
new file mode 100644
index 000000000..48c58f987
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/utils/isSystemSearchVectorField.ts
@@ -0,0 +1,5 @@
+import { SEARCH_VECTOR_FIELD_NAME } from '@/views/constants/ViewFieldConstants';
+
+export const isSystemSearchVectorField = (fieldName: string): boolean => {
+ return fieldName === SEARCH_VECTOR_FIELD_NAME;
+};
diff --git a/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts b/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts
index 7201c1ebc..79d711eaf 100644
--- a/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts
+++ b/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts
@@ -1,5 +1,6 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
+import { isSystemSearchVectorField } from '@/object-record/utils/isSystemSearchVectorField';
import { isDefined } from 'twenty-shared/utils';
import { RelationDefinitionType } from '~/generated-metadata/graphql';
import { FieldMetadataType } from '~/generated/graphql';
@@ -14,6 +15,10 @@ export const sanitizeRecordInput = ({
const filteredResultRecord = Object.fromEntries(
Object.entries(recordInput)
.map<[string, unknown] | undefined>(([fieldName, fieldValue]) => {
+ if (isSystemSearchVectorField(fieldName)) {
+ return undefined;
+ }
+
const fieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.name === fieldName,
);
diff --git a/packages/twenty-front/src/modules/views/components/ViewBar.tsx b/packages/twenty-front/src/modules/views/components/ViewBar.tsx
index 1517757f6..494d8ae31 100644
--- a/packages/twenty-front/src/modules/views/components/ViewBar.tsx
+++ b/packages/twenty-front/src/modules/views/components/ViewBar.tsx
@@ -2,14 +2,12 @@ import { ReactNode } from 'react';
import { useParams } from 'react-router-dom';
import { ObjectSortDropdownButton } from '@/object-record/object-sort-dropdown/components/ObjectSortDropdownButton';
-
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { TopBar } from '@/ui/layout/top-bar/components/TopBar';
import { QueryParamsFiltersEffect } from '@/views/components/QueryParamsFiltersEffect';
import { ViewBarPageTitle } from '@/views/components/ViewBarPageTitle';
import { ViewBarSkeletonLoader } from '@/views/components/ViewBarSkeletonLoader';
import { ViewPickerDropdown } from '@/views/view-picker/components/ViewPickerDropdown';
-
import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
@@ -24,7 +22,7 @@ import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDrop
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
import { ViewBarDetails } from './ViewBarDetails';
-export type ViewBarProps = {
+type ViewBarProps = {
viewBarId: string;
className?: string;
optionsDropdownButton: ReactNode;
@@ -36,7 +34,6 @@ export const ViewBar = ({
optionsDropdownButton,
}: ViewBarProps) => {
const { objectNamePlural } = useParams();
-
const loading = useIsPrefetchLoading();
if (!objectNamePlural) {
diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdown.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdown.tsx
index 6c0d1d3a1..0ebb66515 100644
--- a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdown.tsx
+++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdown.tsx
@@ -2,6 +2,7 @@ import { useResetFilterDropdown } from '@/object-record/object-filter-dropdown/h
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
+import { useVectorSearchFilterActions } from '@/views/hooks/useVectorSearchFilterActions';
import { OPERAND_DROPDOWN_CLICK_OUTSIDE_ID } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandDropdown';
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
@@ -20,7 +21,7 @@ export const ViewBarFilterDropdown = ({
hotkeyScope,
}: ViewBarFilterDropdownProps) => {
const { resetFilterDropdown } = useResetFilterDropdown();
-
+ const { removeEmptyVectorSearchFilter } = useVectorSearchFilterActions();
const { removeRecordFilter } = useRemoveRecordFilter();
const objectFilterDropdownCurrentRecordFilter = useRecoilComponentValueV2(
@@ -37,10 +38,13 @@ export const ViewBarFilterDropdown = ({
recordFilterId: objectFilterDropdownCurrentRecordFilter.id,
});
}
+
+ removeEmptyVectorSearchFilter();
};
const handleDropdownClose = () => {
resetFilterDropdown();
+ removeEmptyVectorSearchFilter();
};
return (
diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownAdvancedFilterButton.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownAdvancedFilterButton.tsx
index e41b90410..bfef615e8 100644
--- a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownAdvancedFilterButton.tsx
+++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownAdvancedFilterButton.tsx
@@ -3,6 +3,10 @@ import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-met
import { useUpsertRecordFilterGroup } from '@/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
+import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
+import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
+import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
+import { VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS } from '@/views/constants/ViewBarFilterBottomMenuItemIds';
import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow';
@@ -18,24 +22,10 @@ import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { Pill } from 'twenty-ui/components';
import { IconFilter } from 'twenty-ui/display';
-import { MenuItemLeftContent, StyledMenuItemBase } from 'twenty-ui/navigation';
+import { MenuItem } from 'twenty-ui/navigation';
import { v4 } from 'uuid';
-export const StyledContainer = styled.div`
- align-items: center;
- display: flex;
- justify-content: space-between;
- padding: ${({ theme }) => theme.spacing(1)};
- border-top: 1px solid ${({ theme }) => theme.border.color.light};
-`;
-
-export const StyledMenuItemSelect = styled(StyledMenuItemBase)`
- &:hover {
- background: ${({ theme }) => theme.background.transparent.light};
- }
-`;
-
-export const StyledPill = styled(Pill)`
+const StyledPill = styled(Pill)`
background: ${({ theme }) => theme.color.blueAccent10};
color: ${({ theme }) => theme.color.blue};
`;
@@ -45,6 +35,11 @@ export const ViewBarFilterDropdownAdvancedFilterButton = () => {
const { t } = useLingui();
+ const isSelected = useRecoilComponentFamilyValueV2(
+ isSelectedItemIdComponentFamilySelector,
+ VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS.ADVANCED_FILTER,
+ );
+
const { openDropdown: openAdvancedFilterDropdown } = useDropdown(
ADVANCED_FILTER_DROPDOWN_ID,
);
@@ -130,13 +125,19 @@ export const ViewBarFilterDropdownAdvancedFilterButton = () => {
};
return (
-
-
-
- {advancedFilterQuerySubFilterCount > 0 && (
-
- )}
-
-
+
+
+ {advancedFilterQuerySubFilterCount > 0 && (
+
+ )}
+
);
};
diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownBottomMenu.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownBottomMenu.tsx
new file mode 100644
index 000000000..d9ca5a3e4
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownBottomMenu.tsx
@@ -0,0 +1,20 @@
+import { ViewBarFilterDropdownAdvancedFilterButton } from '@/views/components/ViewBarFilterDropdownAdvancedFilterButton';
+import { ViewBarFilterDropdownVectorSearchButton } from '@/views/components/ViewBarFilterDropdownVectorSearchButton';
+import styled from '@emotion/styled';
+
+const StyledContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ padding: ${({ theme }) => theme.spacing(1)};
+`;
+
+export const ViewBarFilterDropdownBottomMenu = () => {
+ return (
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx
index fad4f0476..c3e2980cb 100644
--- a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx
+++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownFieldSelectMenu.tsx
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
+import React from 'react';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@@ -12,8 +13,10 @@ import { useFilterDropdownSelectableFieldMetadataItems } from '@/object-record/o
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
-import { ViewBarFilterDropdownAdvancedFilterButton } from '@/views/components/ViewBarFilterDropdownAdvancedFilterButton';
+import { ViewBarFilterDropdownBottomMenu } from '@/views/components/ViewBarFilterDropdownBottomMenu';
import { ViewBarFilterDropdownFieldSelectMenuItem } from '@/views/components/ViewBarFilterDropdownFieldSelectMenuItem';
+
+import { VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS } from '@/views/constants/ViewBarFilterBottomMenuItemIds';
import { useLingui } from '@lingui/react/macro';
export const StyledInput = styled.input`
@@ -58,12 +61,18 @@ export const ViewBarFilterDropdownFieldSelectMenu = () => {
...selectableHiddenFieldMetadataItems.map(
(fieldMetadataItem) => fieldMetadataItem.id,
),
+ VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS.SEARCH,
+ VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS.ADVANCED_FILTER,
];
const shouldShowSeparator =
selectableVisibleFieldMetadataItems.length > 0 &&
selectableHiddenFieldMetadataItems.length > 0;
+ const hasSelectableItems =
+ selectableVisibleFieldMetadataItems.length > 0 ||
+ selectableHiddenFieldMetadataItems.length > 0;
+
const { t } = useLingui();
return (
@@ -81,25 +90,30 @@ export const ViewBarFilterDropdownFieldSelectMenu = () => {
selectableItemIdArray={selectableFieldMetadataItemIds}
selectableListInstanceId={FILTER_FIELD_LIST_ID}
>
-
- {selectableVisibleFieldMetadataItems.map(
- (visibleFieldMetadataItem) => (
-
- ),
- )}
- {shouldShowSeparator && }
- {selectableHiddenFieldMetadataItems.map((hiddenFieldMetadataItem) => (
-
- ))}
-
+ {hasSelectableItems && (
+
+ {selectableVisibleFieldMetadataItems.map(
+ (visibleFieldMetadataItem) => (
+
+ ),
+ )}
+ {shouldShowSeparator && }
+ {selectableHiddenFieldMetadataItems.map(
+ (hiddenFieldMetadataItem) => (
+
+ ),
+ )}
+
+ )}
+ {hasSelectableItems && }
+
-
);
};
diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchButton.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchButton.tsx
new file mode 100644
index 000000000..a9c4d826e
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchButton.tsx
@@ -0,0 +1,80 @@
+import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
+import { isSelectedItemIdComponentFamilySelector } from '@/ui/layout/selectable-list/states/selectors/isSelectedItemIdComponentFamilySelector';
+import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
+import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import styled from '@emotion/styled';
+import { useLingui } from '@lingui/react/macro';
+import { IconSearch } from 'twenty-ui/display';
+import { MenuItem } from 'twenty-ui/navigation';
+
+import { VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS } from '@/views/constants/ViewBarFilterBottomMenuItemIds';
+import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
+
+import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
+import { useOpenVectorSearchFilter } from '@/views/hooks/useOpenVectorSearchFilter';
+import { useSetVectorSearchInputValueFromExistingFilter } from '@/views/hooks/useSetVectorSearchInputValueFromExistingFilter';
+import { useVectorSearchFilterActions } from '@/views/hooks/useVectorSearchFilterActions';
+import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState';
+
+const StyledSearchText = styled.span`
+ color: ${({ theme }) => theme.font.color.light};
+ margin-left: ${({ theme }) => theme.spacing(1)};
+`;
+
+export const ViewBarFilterDropdownVectorSearchButton = () => {
+ const { t } = useLingui();
+ const [, setVectorSearchInputValue] = useRecoilComponentStateV2(
+ vectorSearchInputComponentState,
+ VIEW_BAR_FILTER_DROPDOWN_ID,
+ );
+ const { setVectorSearchInputValueFromExistingFilter } =
+ useSetVectorSearchInputValueFromExistingFilter(VIEW_BAR_FILTER_DROPDOWN_ID);
+
+ const fieldSearchInputValue = useRecoilComponentValueV2(
+ objectFilterDropdownSearchInputComponentState,
+ VIEW_BAR_FILTER_DROPDOWN_ID,
+ );
+
+ const { applyVectorSearchFilter } = useVectorSearchFilterActions();
+ const { openVectorSearchFilter } = useOpenVectorSearchFilter(
+ VIEW_BAR_FILTER_DROPDOWN_ID,
+ );
+
+ const isSelected = useRecoilComponentFamilyValueV2(
+ isSelectedItemIdComponentFamilySelector,
+ VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS.SEARCH,
+ );
+
+ const handleSearchClick = () => {
+ openVectorSearchFilter();
+
+ if (fieldSearchInputValue.length > 0) {
+ setVectorSearchInputValue(fieldSearchInputValue);
+ applyVectorSearchFilter(fieldSearchInputValue);
+ } else {
+ setVectorSearchInputValueFromExistingFilter();
+ }
+ };
+
+ return (
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchInput.tsx b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchInput.tsx
new file mode 100644
index 000000000..e3f89c06e
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/components/ViewBarFilterDropdownVectorSearchInput.tsx
@@ -0,0 +1,47 @@
+import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
+import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
+import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
+import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
+import { useVectorSearchFilterActions } from '@/views/hooks/useVectorSearchFilterActions';
+import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState';
+import { useLingui } from '@lingui/react/macro';
+import { useDebouncedCallback } from 'use-debounce';
+
+export const ViewBarFilterDropdownVectorSearchInput = ({
+ filterDropdownId,
+}: {
+ filterDropdownId: string;
+}) => {
+ const { t } = useLingui();
+ const [vectorSearchInputValue, setVectorSearchInputValue] =
+ useRecoilComponentStateV2(
+ vectorSearchInputComponentState,
+ filterDropdownId,
+ );
+ const { applyVectorSearchFilter } = useVectorSearchFilterActions();
+
+ const debouncedApplyVectorSearchFilter = useDebouncedCallback(
+ (value: string) => {
+ applyVectorSearchFilter(value);
+ },
+ 500,
+ );
+
+ const handleSearchChange = (e: React.ChangeEvent) => {
+ const inputValue = e.target.value;
+ setVectorSearchInputValue(inputValue);
+ debouncedApplyVectorSearchFilter(inputValue);
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx
index bf51b315e..89726f50f 100644
--- a/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx
+++ b/packages/twenty-front/src/modules/views/components/ViewBarRecordFilterEffect.tsx
@@ -1,13 +1,12 @@
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
+import { useMapViewFiltersToFilters } from '@/views/hooks/useMapViewFiltersToFilters';
import { hasInitializedCurrentRecordFiltersComponentFamilyState } from '@/views/states/hasInitializedCurrentRecordFiltersComponentFamilyState';
-import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -39,9 +38,7 @@ export const ViewBarRecordFilterEffect = () => {
currentRecordFiltersComponentState,
);
- const { filterableFieldMetadataItems } = useFilterableFieldMetadataItems(
- objectMetadataItem.id,
- );
+ const { mapViewFiltersToRecordFilters } = useMapViewFiltersToFilters();
useEffect(() => {
if (!hasInitializedCurrentRecordFilters && isDefined(currentView)) {
@@ -50,10 +47,7 @@ export const ViewBarRecordFilterEffect = () => {
}
setCurrentRecordFilters(
- mapViewFiltersToFilters(
- currentView.viewFilters,
- filterableFieldMetadataItems,
- ),
+ mapViewFiltersToRecordFilters(currentView.viewFilters),
);
setHasInitializedCurrentRecordFilters(true);
@@ -61,7 +55,7 @@ export const ViewBarRecordFilterEffect = () => {
}, [
currentViewId,
setCurrentRecordFilters,
- filterableFieldMetadataItems,
+ mapViewFiltersToRecordFilters,
hasInitializedCurrentRecordFilters,
setHasInitializedCurrentRecordFilters,
currentView,
diff --git a/packages/twenty-front/src/modules/views/constants/ViewBarFilterBottomMenuItemIds.ts b/packages/twenty-front/src/modules/views/constants/ViewBarFilterBottomMenuItemIds.ts
new file mode 100644
index 000000000..a220807b2
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/constants/ViewBarFilterBottomMenuItemIds.ts
@@ -0,0 +1,4 @@
+export const VIEW_BAR_FILTER_BOTTOM_MENU_ITEM_IDS = {
+ SEARCH: 'search-button',
+ ADVANCED_FILTER: 'advanced-filter-button',
+};
diff --git a/packages/twenty-front/src/modules/views/constants/ViewFieldConstants.ts b/packages/twenty-front/src/modules/views/constants/ViewFieldConstants.ts
new file mode 100644
index 000000000..44a7682cf
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/constants/ViewFieldConstants.ts
@@ -0,0 +1 @@
+export const SEARCH_VECTOR_FIELD_NAME = 'searchVector';
diff --git a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts
index c2477dd7c..bc585104f 100644
--- a/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts
+++ b/packages/twenty-front/src/modules/views/hooks/useApplyCurrentViewFiltersToCurrentRecordFilters.ts
@@ -1,12 +1,11 @@
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
-import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { prefetchViewFromViewIdFamilySelector } from '@/prefetch/states/selector/prefetchViewFromViewIdFamilySelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
-import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
+import { useMapViewFiltersToFilters } from './useMapViewFiltersToFilters';
export const useApplyCurrentViewFiltersToCurrentRecordFilters = () => {
const currentViewId = useRecoilComponentValueV2(
@@ -17,8 +16,7 @@ export const useApplyCurrentViewFiltersToCurrentRecordFilters = () => {
currentRecordFiltersComponentState,
);
- const { filterableFieldMetadataItems } =
- useFilterableFieldMetadataItemsInRecordIndexContext();
+ const { mapViewFiltersToRecordFilters } = useMapViewFiltersToFilters();
const applyCurrentViewFiltersToCurrentRecordFilters = useRecoilCallback(
({ snapshot }) =>
@@ -33,14 +31,11 @@ export const useApplyCurrentViewFiltersToCurrentRecordFilters = () => {
if (isDefined(currentView)) {
setCurrentRecordFilters(
- mapViewFiltersToFilters(
- currentView.viewFilters,
- filterableFieldMetadataItems,
- ),
+ mapViewFiltersToRecordFilters(currentView.viewFilters),
);
}
},
- [currentViewId, filterableFieldMetadataItems, setCurrentRecordFilters],
+ [currentViewId, mapViewFiltersToRecordFilters, setCurrentRecordFilters],
);
return {
diff --git a/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts b/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts
index 0bec3cb5e..09b6070b5 100644
--- a/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts
+++ b/packages/twenty-front/src/modules/views/hooks/useApplyViewFiltersToCurrentRecordFilters.ts
@@ -1,28 +1,19 @@
-import { useFilterableFieldMetadataItems } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItems';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
-import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { ViewFilter } from '@/views/types/ViewFilter';
-import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
+import { useMapViewFiltersToFilters } from './useMapViewFiltersToFilters';
export const useApplyViewFiltersToCurrentRecordFilters = () => {
const setCurrentRecordFilters = useSetRecoilComponentStateV2(
currentRecordFiltersComponentState,
);
- const { objectMetadataItem } = useRecordIndexContextOrThrow();
-
- const { filterableFieldMetadataItems } = useFilterableFieldMetadataItems(
- objectMetadataItem.id,
- );
+ const { mapViewFiltersToRecordFilters } = useMapViewFiltersToFilters();
const applyViewFiltersToCurrentRecordFilters = (
viewFilters: ViewFilter[],
) => {
- const recordFiltersToApply = mapViewFiltersToFilters(
- viewFilters,
- filterableFieldMetadataItems,
- );
+ const recordFiltersToApply = mapViewFiltersToRecordFilters(viewFilters);
setCurrentRecordFilters(recordFiltersToApply);
};
diff --git a/packages/twenty-front/src/modules/views/hooks/useMapViewFiltersToFilters.ts b/packages/twenty-front/src/modules/views/hooks/useMapViewFiltersToFilters.ts
new file mode 100644
index 000000000..27717daf2
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/hooks/useMapViewFiltersToFilters.ts
@@ -0,0 +1,16 @@
+import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
+import { ViewFilter } from '../types/ViewFilter';
+import { getFilterableFieldsWithVectorSearch } from '../utils/getFilterableFieldsWithVectorSearch';
+import { mapViewFiltersToFilters } from '../utils/mapViewFiltersToFilters';
+
+export const useMapViewFiltersToFilters = () => {
+ const { objectMetadataItem } = useRecordIndexContextOrThrow();
+
+ const mapViewFiltersToRecordFilters = (viewFilters: ViewFilter[]) => {
+ const filterableFieldMetadataItems =
+ getFilterableFieldsWithVectorSearch(objectMetadataItem);
+ return mapViewFiltersToFilters(viewFilters, filterableFieldMetadataItems);
+ };
+
+ return { mapViewFiltersToRecordFilters };
+};
diff --git a/packages/twenty-front/src/modules/views/hooks/useOpenVectorSearchFilter.ts b/packages/twenty-front/src/modules/views/hooks/useOpenVectorSearchFilter.ts
new file mode 100644
index 000000000..8eaff6f9a
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/hooks/useOpenVectorSearchFilter.ts
@@ -0,0 +1,25 @@
+import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
+import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
+import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+
+export const useOpenVectorSearchFilter = (filterDropdownId?: string) => {
+ const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
+ selectedOperandInDropdownComponentState,
+ filterDropdownId,
+ );
+
+ const setObjectFilterDropdownFilterIsSelected = useSetRecoilComponentStateV2(
+ objectFilterDropdownFilterIsSelectedComponentState,
+ filterDropdownId,
+ );
+
+ const openVectorSearchFilter = () => {
+ setObjectFilterDropdownFilterIsSelected(true);
+ setSelectedOperandInDropdown(ViewFilterOperand.VectorSearch);
+ };
+
+ return {
+ openVectorSearchFilter,
+ };
+};
diff --git a/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts b/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts
index 527f6b6b7..6a7d8b044 100644
--- a/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts
+++ b/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromActiveFieldsOfViewOrDefaultView.ts
@@ -1,8 +1,7 @@
-import { useActiveFieldMetadataItems } from '@/object-metadata/hooks/useActiveFieldMetadataItems';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useViewOrDefaultViewFromPrefetchedViews } from '@/views/hooks/useViewOrDefaultViewFromPrefetchedViews';
-import { getQueryVariablesFromView } from '@/views/utils/getQueryVariablesFromView';
+import { useQueryVariablesFromView } from './useQueryVariablesFromView';
export const useQueryVariablesFromActiveFieldsOfViewOrDefaultView = ({
objectMetadataItem,
@@ -13,14 +12,9 @@ export const useQueryVariablesFromActiveFieldsOfViewOrDefaultView = ({
objectMetadataItemId: objectMetadataItem.id,
});
- const { activeFieldMetadataItems } = useActiveFieldMetadataItems({
- objectMetadataItem,
- });
-
const { filterValueDependencies } = useFilterValueDependencies();
- const { filter, orderBy } = getQueryVariablesFromView({
- fieldMetadataItems: activeFieldMetadataItems,
+ const { filter, orderBy } = useQueryVariablesFromView({
objectMetadataItem,
view,
filterValueDependencies,
diff --git a/packages/twenty-front/src/modules/views/utils/getQueryVariablesFromView.ts b/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromView.ts
similarity index 85%
rename from packages/twenty-front/src/modules/views/utils/getQueryVariablesFromView.ts
rename to packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromView.ts
index 240bd3a59..63c2d10d3 100644
--- a/packages/twenty-front/src/modules/views/utils/getQueryVariablesFromView.ts
+++ b/packages/twenty-front/src/modules/views/hooks/useQueryVariablesFromView.ts
@@ -1,24 +1,20 @@
-import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
-
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
-
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
import { View } from '@/views/types/View';
+import { getFilterableFieldsWithVectorSearch } from '@/views/utils/getFilterableFieldsWithVectorSearch';
import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { isDefined } from 'twenty-shared/utils';
-export const getQueryVariablesFromView = ({
+export const useQueryVariablesFromView = ({
view,
- fieldMetadataItems,
objectMetadataItem,
filterValueDependencies,
}: {
view: View | null | undefined;
- fieldMetadataItems: FieldMetadataItem[];
objectMetadataItem: ObjectMetadataItem;
filterValueDependencies: RecordFilterValueDependencies;
}) => {
@@ -35,9 +31,12 @@ export const getQueryVariablesFromView = ({
viewFilterGroups ?? [],
);
+ const filterableFieldMetadataItems =
+ getFilterableFieldsWithVectorSearch(objectMetadataItem);
+
const recordFilters = mapViewFiltersToFilters(
viewFilters,
- fieldMetadataItems,
+ filterableFieldMetadataItems,
);
const filter = computeRecordGqlOperationFilter({
diff --git a/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts b/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts
index d411d037f..6dd452c4b 100644
--- a/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts
+++ b/packages/twenty-front/src/modules/views/hooks/useSetEditableFilterChipDropdownStates.ts
@@ -4,6 +4,9 @@ import { selectedOperandInDropdownComponentState } from '@/object-record/object-
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { useVectorSearchFieldInRecordIndexContextOrThrow } from '@/views/hooks/useVectorSearchFieldInRecordIndexContextOrThrow';
+import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState';
+import { isVectorSearchFilter } from '@/views/utils/isVectorSearchFilter';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -11,10 +14,17 @@ export const useSetEditableFilterChipDropdownStates = () => {
const { filterableFieldMetadataItems } =
useFilterableFieldMetadataItemsInRecordIndexContext();
+ const { vectorSearchField } =
+ useVectorSearchFieldInRecordIndexContextOrThrow();
+
const setEditableFilterChipDropdownStates = useRecoilCallback(
({ set }) =>
(recordFilter: RecordFilter) => {
- const fieldMetadataItem = filterableFieldMetadataItems.find(
+ const filterableFieldsWithVector = vectorSearchField
+ ? filterableFieldMetadataItems.concat(vectorSearchField)
+ : filterableFieldMetadataItems;
+
+ const fieldMetadataItem = filterableFieldsWithVector.find(
(fieldMetadataItem) =>
fieldMetadataItem.id === recordFilter.fieldMetadataId,
);
@@ -23,6 +33,15 @@ export const useSetEditableFilterChipDropdownStates = () => {
return;
}
+ if (isVectorSearchFilter(recordFilter)) {
+ set(
+ vectorSearchInputComponentState.atomFamily({
+ instanceId: recordFilter.id,
+ }),
+ recordFilter.value,
+ );
+ }
+
set(
fieldMetadataItemIdUsedInDropdownComponentState.atomFamily({
instanceId: recordFilter.id,
@@ -51,7 +70,7 @@ export const useSetEditableFilterChipDropdownStates = () => {
recordFilter.subFieldName,
);
},
- [filterableFieldMetadataItems],
+ [filterableFieldMetadataItems, vectorSearchField],
);
return {
diff --git a/packages/twenty-front/src/modules/views/hooks/useSetVectorSearchInputValueFromExistingFilter.ts b/packages/twenty-front/src/modules/views/hooks/useSetVectorSearchInputValueFromExistingFilter.ts
new file mode 100644
index 000000000..3b01c0414
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/hooks/useSetVectorSearchInputValueFromExistingFilter.ts
@@ -0,0 +1,23 @@
+import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
+import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState';
+import { isDefined } from 'twenty-shared/utils';
+import { useVectorSearchFilterState } from './useVectorSearchFilterState';
+
+export const useSetVectorSearchInputValueFromExistingFilter = (
+ filterDropdownId: string,
+) => {
+ const [, setVectorSearchInputValue] = useRecoilComponentStateV2(
+ vectorSearchInputComponentState,
+ filterDropdownId,
+ );
+ const { getExistingVectorSearchFilter } = useVectorSearchFilterState();
+
+ const setVectorSearchInputValueFromExistingFilter = () => {
+ const existingVectorSearchFilter = getExistingVectorSearchFilter();
+ if (isDefined(existingVectorSearchFilter)) {
+ setVectorSearchInputValue(existingVectorSearchFilter.value);
+ }
+ };
+
+ return { setVectorSearchInputValueFromExistingFilter };
+};
diff --git a/packages/twenty-front/src/modules/views/hooks/useVectorSearchFieldInRecordIndexContextOrThrow.ts b/packages/twenty-front/src/modules/views/hooks/useVectorSearchFieldInRecordIndexContextOrThrow.ts
new file mode 100644
index 000000000..7101c8d3c
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/hooks/useVectorSearchFieldInRecordIndexContextOrThrow.ts
@@ -0,0 +1,13 @@
+import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
+import { SEARCH_VECTOR_FIELD_NAME } from '../constants/ViewFieldConstants';
+
+export const useVectorSearchFieldInRecordIndexContextOrThrow = () => {
+ const { objectMetadataItem } = useRecordIndexContextOrThrow();
+
+ const vectorSearchField = objectMetadataItem.fields.find(
+ (field) =>
+ field.type === 'TS_VECTOR' && field.name === SEARCH_VECTOR_FIELD_NAME,
+ );
+
+ return { vectorSearchField };
+};
diff --git a/packages/twenty-front/src/modules/views/hooks/useVectorSearchFilterActions.ts b/packages/twenty-front/src/modules/views/hooks/useVectorSearchFilterActions.ts
new file mode 100644
index 000000000..bebe8546c
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/hooks/useVectorSearchFilterActions.ts
@@ -0,0 +1,55 @@
+import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
+import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRemoveRecordFilter';
+import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
+import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
+import { useVectorSearchFieldInRecordIndexContextOrThrow } from '@/views/hooks/useVectorSearchFieldInRecordIndexContextOrThrow';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { isDefined } from 'twenty-shared/utils';
+import { v4 } from 'uuid';
+import { useVectorSearchFilterState } from './useVectorSearchFilterState';
+
+export const useVectorSearchFilterActions = () => {
+ const { vectorSearchField } =
+ useVectorSearchFieldInRecordIndexContextOrThrow();
+ const { getExistingVectorSearchFilter } = useVectorSearchFilterState();
+ const { upsertRecordFilter } = useUpsertRecordFilter();
+ const { removeRecordFilter } = useRemoveRecordFilter();
+
+ const applyVectorSearchFilter = (value: string) => {
+ if (!vectorSearchField) {
+ return;
+ }
+
+ const existingVectorSearchFilter = getExistingVectorSearchFilter();
+
+ const vectorSearchRecordFilter = {
+ id: existingVectorSearchFilter?.id ?? v4(),
+ fieldMetadataId: vectorSearchField.id,
+ value: value,
+ displayValue: value,
+ operand: ViewFilterOperand.VectorSearch,
+ type: getFilterTypeFromFieldType(vectorSearchField.type),
+ label: 'Search',
+ };
+
+ upsertRecordFilter(vectorSearchRecordFilter);
+ };
+
+ const removeEmptyVectorSearchFilter = () => {
+ const vectorSearchFilter = getExistingVectorSearchFilter();
+
+ if (
+ isDefined(vectorSearchFilter) &&
+ isRecordFilterConsideredEmpty(vectorSearchFilter)
+ ) {
+ removeRecordFilter({
+ recordFilterId: vectorSearchFilter.id,
+ });
+ }
+ };
+
+ return {
+ applyVectorSearchFilter,
+ removeEmptyVectorSearchFilter,
+ };
+};
diff --git a/packages/twenty-front/src/modules/views/hooks/useVectorSearchFilterState.ts b/packages/twenty-front/src/modules/views/hooks/useVectorSearchFilterState.ts
new file mode 100644
index 000000000..5924aa138
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/hooks/useVectorSearchFilterState.ts
@@ -0,0 +1,17 @@
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { isVectorSearchFilter } from '@/views/utils/isVectorSearchFilter';
+
+export const useVectorSearchFilterState = () => {
+ const currentRecordFilters = useRecoilComponentValueV2(
+ currentRecordFiltersComponentState,
+ );
+
+ const getExistingVectorSearchFilter = () => {
+ return currentRecordFilters.find(isVectorSearchFilter);
+ };
+
+ return {
+ getExistingVectorSearchFilter,
+ };
+};
diff --git a/packages/twenty-front/src/modules/views/states/vectorSearchInputComponentState.ts b/packages/twenty-front/src/modules/views/states/vectorSearchInputComponentState.ts
new file mode 100644
index 000000000..29c83f674
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/states/vectorSearchInputComponentState.ts
@@ -0,0 +1,8 @@
+import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
+import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
+
+export const vectorSearchInputComponentState = createComponentStateV2({
+ key: 'vectorSearchInputComponentState',
+ defaultValue: '',
+ componentInstanceContext: ViewComponentInstanceContext,
+});
diff --git a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts b/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts
index 0d6446de9..7ea686348 100644
--- a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts
+++ b/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts
@@ -14,4 +14,5 @@ export enum ViewFilterOperand {
IsInPast = 'isInPast',
IsInFuture = 'isInFuture',
IsToday = 'isToday',
+ VectorSearch = 'search',
}
diff --git a/packages/twenty-front/src/modules/views/utils/getFilterableFieldsWithVectorSearch.ts b/packages/twenty-front/src/modules/views/utils/getFilterableFieldsWithVectorSearch.ts
new file mode 100644
index 000000000..3a4b36167
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/utils/getFilterableFieldsWithVectorSearch.ts
@@ -0,0 +1,19 @@
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { getFilterFilterableFieldMetadataItems } from '@/object-metadata/utils/getFilterFilterableFieldMetadataItems';
+import { SEARCH_VECTOR_FIELD_NAME } from '../constants/ViewFieldConstants';
+
+export const getFilterableFieldsWithVectorSearch = (
+ objectMetadataItem: ObjectMetadataItem,
+) => {
+ const vectorSearchField = objectMetadataItem.fields.find(
+ (field) =>
+ field.type === 'TS_VECTOR' && field.name === SEARCH_VECTOR_FIELD_NAME,
+ );
+
+ return [
+ ...objectMetadataItem.fields.filter(
+ getFilterFilterableFieldMetadataItems({ isJsonFilterEnabled: true }),
+ ),
+ ...(vectorSearchField ? [vectorSearchField] : []),
+ ];
+};
diff --git a/packages/twenty-front/src/modules/views/utils/isVectorSearchFilter.ts b/packages/twenty-front/src/modules/views/utils/isVectorSearchFilter.ts
new file mode 100644
index 000000000..29ed05fc4
--- /dev/null
+++ b/packages/twenty-front/src/modules/views/utils/isVectorSearchFilter.ts
@@ -0,0 +1,6 @@
+import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+
+export const isVectorSearchFilter = (filter: RecordFilter) => {
+ return filter.operand === ViewFilterOperand.VectorSearch;
+};
diff --git a/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts b/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts
index 521991d87..c821a2713 100644
--- a/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts
+++ b/packages/twenty-front/src/modules/views/utils/mapViewFiltersToFilters.ts
@@ -3,6 +3,7 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
+import { isSystemSearchVectorField } from '@/object-record/utils/isSystemSearchVectorField';
import { isDefined } from 'twenty-shared/utils';
import { ViewFilter } from '../types/ViewFilter';
@@ -26,6 +27,10 @@ export const mapViewFiltersToFilters = (
availableFieldMetadataItem.type,
);
+ const label = isSystemSearchVectorField(availableFieldMetadataItem.name)
+ ? 'Search'
+ : availableFieldMetadataItem.label;
+
return {
id: viewFilter.id,
fieldMetadataId: viewFilter.fieldMetadataId,
@@ -34,7 +39,7 @@ export const mapViewFiltersToFilters = (
operand: viewFilter.operand,
recordFilterGroupId: viewFilter.viewFilterGroupId,
positionInRecordFilterGroup: viewFilter.positionInViewFilterGroup,
- label: availableFieldMetadataItem.label,
+ label,
type: filterType,
subFieldName: viewFilter.subFieldName,
} satisfies RecordFilter;
diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts.ts
index 159caca2f..01ac741ad 100644
--- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts.ts
+++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts.ts
@@ -4,6 +4,7 @@ import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
+import { formatSearchTerms } from 'src/engine/core-modules/search/utils/format-search-terms';
type WhereConditionParts = {
sql: string;
@@ -96,10 +97,7 @@ export const computeWhereConditionParts = ({
params: { [`${key}${uuid}`]: value },
};
case 'search': {
- const tsQuery = value
- .split(/\s+/)
- .map((term: string) => `${term}:*`)
- .join(' & ');
+ const tsQuery = formatSearchTerms(value, 'and');
return {
sql: `(