diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeRunsWorkflowSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeRunsWorkflowSingleRecordAction.tsx index 242a9c0f9..cbd7d5b39 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeRunsWorkflowSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeRunsWorkflowSingleRecordAction.tsx @@ -2,8 +2,8 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { AppPath } from '@/types/AppPath'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export const SeeRunsWorkflowSingleRecordAction = () => { const recordId = useSelectedRecordIdOrThrow(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeVersionsWorkflowSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeVersionsWorkflowSingleRecordAction.tsx index 3270e2e22..3db6d1f44 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeVersionsWorkflowSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeVersionsWorkflowSingleRecordAction.tsx @@ -2,8 +2,8 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { AppPath } from '@/types/AppPath'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export const SeeVersionsWorkflowSingleRecordAction = () => { const recordId = useSelectedRecordIdOrThrow(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeRunsWorkflowVersionSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeRunsWorkflowVersionSingleRecordAction.tsx index c34027c91..bc387faa6 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeRunsWorkflowVersionSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeRunsWorkflowVersionSingleRecordAction.tsx @@ -3,9 +3,9 @@ import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { AppPath } from '@/types/AppPath'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { useRecoilValue } from 'recoil'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export const SeeRunsWorkflowVersionSingleRecordAction = () => { const recordId = useSelectedRecordIdOrThrow(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeVersionsWorkflowVersionSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeVersionsWorkflowVersionSingleRecordAction.tsx index 5c8412876..4ba768465 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeVersionsWorkflowVersionSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeVersionsWorkflowVersionSingleRecordAction.tsx @@ -3,9 +3,9 @@ import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { AppPath } from '@/types/AppPath'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { useRecoilValue } from 'recoil'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export const SeeVersionsWorkflowVersionSingleRecordAction = () => { const recordId = useSelectedRecordIdOrThrow(); diff --git a/packages/twenty-front/src/modules/context-store/utils/__tests__/computeContextStoreFilters.test.ts b/packages/twenty-front/src/modules/context-store/utils/__tests__/computeContextStoreFilters.test.ts index bf0f47e7c..bfffc8b72 100644 --- a/packages/twenty-front/src/modules/context-store/utils/__tests__/computeContextStoreFilters.test.ts +++ b/packages/twenty-front/src/modules/context-store/utils/__tests__/computeContextStoreFilters.test.ts @@ -2,8 +2,8 @@ import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextS import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { expect } from '@storybook/test'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('computeContextStoreFilters', () => { diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent.tsx index 5651ee818..be9570391 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent.tsx @@ -14,7 +14,7 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { MenuItem } from 'twenty-ui/navigation'; type AdvancedFilterRecordFilterOperandSelectContentProps = { diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx index 8b660df4e..3294eb5e3 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownDateInput.tsx @@ -5,7 +5,6 @@ import { selectedOperandInDropdownComponentState } from '@/object-record/object- import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue'; import { DateTimePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { computeVariableDateViewFilterValue } from '@/views/view-filter-value/utils/computeVariableDateViewFilterValue'; import { resolveDateViewFilterValue, @@ -13,6 +12,7 @@ import { VariableDateViewFilterValueUnit, } from '@/views/view-filter-value/utils/resolveDateViewFilterValue'; import { useState } from 'react'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { isDefined } from 'twenty-shared/utils'; import { FieldMetadataType } from '~/generated-metadata/graphql'; 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 295f62142..39253f821 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 @@ -6,7 +6,7 @@ import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter- 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 { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect'; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState.ts index 7ed790b51..d5831012e 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState.ts @@ -1,6 +1,6 @@ import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export const selectedOperandInDropdownComponentState = createComponentStateV2({ diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandLabel.test.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandLabel.test.ts index 2f2a96171..732ec9304 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandLabel.test.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandLabel.test.ts @@ -1,4 +1,4 @@ -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { capitalize } from 'twenty-shared/utils'; import { getOperandLabel, getOperandLabelShort } from '../getOperandLabel'; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/isFilterOperandExpectingValue.test.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/isFilterOperandExpectingValue.test.ts index 7f1e8b26b..1f6b656b9 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/isFilterOperandExpectingValue.test.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/isFilterOperandExpectingValue.test.ts @@ -1,4 +1,4 @@ -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { isFilterOperandExpectingValue } from '../isFilterOperandExpectingValue'; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/configurableViewFilterOperands.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/configurableViewFilterOperands.ts index aaafc9e8d..700d184f0 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/configurableViewFilterOperands.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/configurableViewFilterOperands.ts @@ -1,4 +1,4 @@ -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export const configurableViewFilterOperands = new Set([ ViewFilterOperand.Is, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts index 4f63f9252..80d033396 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts @@ -1,5 +1,5 @@ -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { t } from '@lingui/core/macro'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export const getOperandLabel = ( operand: ViewFilterOperand | null | undefined, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOperandExpectingValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOperandExpectingValue.ts index cdf0623b7..e8a0c64c7 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOperandExpectingValue.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOperandExpectingValue.ts @@ -1,4 +1,4 @@ -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export const isFilterOperandExpectingValue = (operand: ViewFilterOperand) => { switch (operand) { diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx index 663f67862..6e575a6ba 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useRemoveRecordFilter.test.tsx @@ -3,8 +3,8 @@ import { renderHook } from '@testing-library/react'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { act } from 'react'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { useRemoveRecordFilter } from '../useRemoveRecordFilter'; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx index 262641e9e..7320f990c 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/__tests__/useUpsertRecordFilter.test.tsx @@ -4,7 +4,7 @@ import { act } from 'react'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { useUpsertRecordFilter } from '../useUpsertRecordFilter'; 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 93396f314..089f0a586 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,7 +1,7 @@ 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'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export type RecordFilter = { id: string; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilterOperand.ts b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilterOperand.ts index 110cb74f5..9956e56dd 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilterOperand.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilterOperand.ts @@ -1 +1 @@ -export { ViewFilterOperand as RecordFilterOperand } from '@/views/types/ViewFilterOperand'; +export { ViewFilterOperand as RecordFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts index 673a5f770..84b02aeb1 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts @@ -3,7 +3,7 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies'; import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { getCompaniesMock } from '~/testing/mock-data/companies'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/getEmptyRecordGqlOperationFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/getEmptyRecordGqlOperationFilter.ts index 05a6bdb10..e9fa184ea 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/getEmptyRecordGqlOperationFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/getEmptyRecordGqlOperationFilter.ts @@ -19,8 +19,8 @@ import { import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { computeEmptyGqlOperationFilterForEmails } from '@/object-record/record-filter/utils/compute-empty-record-gql-operation-filter/for-composite-field/computeEmptyGqlOperationFilterForEmails'; import { computeEmptyGqlOperationFilterForLinks } from '@/object-record/record-filter/utils/compute-empty-record-gql-operation-filter/for-composite-field/computeEmptyGqlOperationFilterForLinks'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { isNonEmptyString } from '@sniptt/guards'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { Field } from '~/generated/graphql'; import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields'; 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 82af90d34..2e186a296 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 @@ -5,7 +5,7 @@ import { 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 { ViewFilterOperand as RecordFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { FieldMetadataType } from 'twenty-shared/types'; import { assertUnreachable } from 'twenty-shared/utils'; diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts index 60c8cd54b..9ec07081d 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleToggleTrashColumnFilter.ts @@ -9,8 +9,8 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useRecoilCallback } from 'recoil'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { isDefined } from 'twenty-shared/utils'; type UseHandleToggleTrashColumnFilterProps = { diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx index 25756c306..4b9ae77bd 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationSection.tsx @@ -19,8 +19,8 @@ import { AppPath } from '@/types/AppPath'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { useLingui } from '@lingui/react/macro'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { RelationType } from '~/generated-metadata/graphql'; import { getAppPath } from '~/utils/navigation/getAppPath'; diff --git a/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts index cecbb407a..3e0f72ebf 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts @@ -1,8 +1,8 @@ import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem'; import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ColorScheme } from '@/workspace-member/types/WorkspaceMember'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { RelationType } from '~/generated-metadata/graphql'; import { buildValueFromFilter } from './buildRecordInputFromFilter'; 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 924617586..737427114 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 @@ -7,7 +7,7 @@ import { RecordFilterToRecordInputOperand, } from '@/object-record/record-filter/types/RecordFilter'; import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { assertUnreachable, parseJson } from 'twenty-shared/utils'; import { RelationType } from '~/generated-metadata/graphql'; diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx index 6f9aaa00d..3e92ea987 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyCurrentViewFiltersToCurrentRecordFilters.test.tsx @@ -9,10 +9,10 @@ import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { View } from '@/views/types/View'; import { ViewFilter } from '@/views/types/ViewFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { ViewType } from '@/views/types/ViewType'; import { act } from 'react'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { isDefined } from 'twenty-shared/utils'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; diff --git a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyViewFiltersToCurrentRecordFilters.test.tsx b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyViewFiltersToCurrentRecordFilters.test.tsx index a6802d13c..8e3ba641a 100644 --- a/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyViewFiltersToCurrentRecordFilters.test.tsx +++ b/packages/twenty-front/src/modules/views/hooks/__tests__/useApplyViewFiltersToCurrentRecordFilters.test.tsx @@ -5,11 +5,11 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { ViewFilter } from '@/views/types/ViewFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; +import { isDefined } from 'twenty-shared/utils'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { useApplyViewFiltersToCurrentRecordFilters } from '../useApplyViewFiltersToCurrentRecordFilters'; -import { isDefined } from 'twenty-shared/utils'; const mockObjectMetadataItemNameSingular = 'company'; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/useViewFromQueryParams.ts b/packages/twenty-front/src/modules/views/hooks/internal/useViewFromQueryParams.ts index 46d04eeab..dbd219c78 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/useViewFromQueryParams.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/useViewFromQueryParams.ts @@ -15,8 +15,8 @@ import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery'; import { ViewFilter } from '@/views/types/ViewFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { relationFilterValueSchemaObject } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { isDefined } from 'twenty-shared/utils'; const filterQueryParamsSchema = z.object({ diff --git a/packages/twenty-front/src/modules/views/hooks/useGetViewGroupsFilters.ts b/packages/twenty-front/src/modules/views/hooks/useGetViewGroupsFilters.ts index 9e0337f75..07e2e4e12 100644 --- a/packages/twenty-front/src/modules/views/hooks/useGetViewGroupsFilters.ts +++ b/packages/twenty-front/src/modules/views/hooks/useGetViewGroupsFilters.ts @@ -1,7 +1,7 @@ import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; diff --git a/packages/twenty-front/src/modules/views/hooks/useOpenVectorSearchFilter.ts b/packages/twenty-front/src/modules/views/hooks/useOpenVectorSearchFilter.ts index 8eaff6f9a..5be331a4b 100644 --- a/packages/twenty-front/src/modules/views/hooks/useOpenVectorSearchFilter.ts +++ b/packages/twenty-front/src/modules/views/hooks/useOpenVectorSearchFilter.ts @@ -1,7 +1,7 @@ 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'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export const useOpenVectorSearchFilter = (filterDropdownId?: string) => { const setSelectedOperandInDropdown = useSetRecoilComponentStateV2( diff --git a/packages/twenty-front/src/modules/views/hooks/useVectorSearchFilterActions.ts b/packages/twenty-front/src/modules/views/hooks/useVectorSearchFilterActions.ts index bebe8546c..1618fed24 100644 --- a/packages/twenty-front/src/modules/views/hooks/useVectorSearchFilterActions.ts +++ b/packages/twenty-front/src/modules/views/hooks/useVectorSearchFilterActions.ts @@ -3,7 +3,7 @@ import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRe 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 { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { useVectorSearchFilterState } from './useVectorSearchFilterState'; diff --git a/packages/twenty-front/src/modules/views/types/ViewFilter.ts b/packages/twenty-front/src/modules/views/types/ViewFilter.ts index 59f56e9d3..846367af8 100644 --- a/packages/twenty-front/src/modules/views/types/ViewFilter.ts +++ b/packages/twenty-front/src/modules/views/types/ViewFilter.ts @@ -1,5 +1,5 @@ import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName'; -import { ViewFilterOperand } from './ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export type ViewFilter = { __typename: 'ViewFilter'; diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/areViewFiltersEqual.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/areViewFiltersEqual.test.ts index 28db1a0c0..110086cb2 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/areViewFiltersEqual.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/areViewFiltersEqual.test.ts @@ -1,5 +1,5 @@ import { ViewFilter } from '@/views/types/ViewFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { areViewFiltersEqual } from '../areViewFiltersEqual'; describe('areViewFiltersEqual', () => { diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts index 66d3ee231..59cd98f6a 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToCreate.test.ts @@ -1,5 +1,5 @@ import { ViewFilter } from '@/views/types/ViewFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { getViewFiltersToCreate } from '../getViewFiltersToCreate'; describe('getViewFiltersToCreate', () => { diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts index 07d3bee0e..25690d775 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToDelete.test.ts @@ -1,5 +1,5 @@ import { ViewFilter } from '@/views/types/ViewFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { getViewFiltersToDelete } from '../getViewFiltersToDelete'; describe('getViewFiltersToDelete', () => { diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts index 8bdc845b9..31a08acc2 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/getViewFiltersToUpdate.test.ts @@ -1,5 +1,5 @@ import { ViewFilter } from '@/views/types/ViewFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { getViewFiltersToUpdate } from '../getViewFiltersToUpdate'; describe('getViewFiltersToUpdate', () => { diff --git a/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts b/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts index 11fcf1708..2d33e9d25 100644 --- a/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts +++ b/packages/twenty-front/src/modules/views/utils/__tests__/viewMapFunctions.test.ts @@ -4,12 +4,12 @@ import { RecordSort } from '@/object-record/record-sort/types/RecordSort'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ViewField } from '@/views/types/ViewField'; import { ViewFilter } from '@/views/types/ViewFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { ViewSort } from '@/views/types/ViewSort'; import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField'; import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions'; import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters'; import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { FieldMetadataType } from '~/generated-metadata/graphql'; diff --git a/packages/twenty-front/src/modules/views/utils/isVectorSearchFilter.ts b/packages/twenty-front/src/modules/views/utils/isVectorSearchFilter.ts index 29ed05fc4..929f87302 100644 --- a/packages/twenty-front/src/modules/views/utils/isVectorSearchFilter.ts +++ b/packages/twenty-front/src/modules/views/utils/isVectorSearchFilter.ts @@ -1,5 +1,5 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; export const isVectorSearchFilter = (filter: RecordFilter) => { return filter.operand === ViewFilterOperand.VectorSearch; diff --git a/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveDateViewFilterValue.ts b/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveDateViewFilterValue.ts index da9403105..cb34bcefc 100644 --- a/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveDateViewFilterValue.ts +++ b/packages/twenty-front/src/modules/views/view-filter-value/utils/resolveDateViewFilterValue.ts @@ -1,5 +1,4 @@ import { ViewFilter } from '@/views/types/ViewFilter'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; import { addDays, addMonths, @@ -19,6 +18,7 @@ import { subWeeks, subYears, } from 'date-fns'; +import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand'; import { z } from 'zod'; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterAddFilterRuleSelect.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterAddFilterRuleSelect.tsx index 6c64b618e..a3d13aa9b 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterAddFilterRuleSelect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterAddFilterRuleSelect.tsx @@ -10,7 +10,7 @@ import { StepFilter, StepFilterGroup, StepLogicalOperator, - StepOperand, + ViewFilterOperand, } from 'twenty-shared/src/types'; import { isDefined } from 'twenty-shared/utils'; import { IconLibraryPlus, IconPlus } from 'twenty-ui/display'; @@ -21,6 +21,17 @@ type WorkflowStepFilterAddFilterRuleSelectProps = { stepFilterGroup: StepFilterGroup; }; +const BASE_NEW_STEP_FILTER = { + id: v4(), + type: 'unknown', + label: '', + value: '', + operand: ViewFilterOperand.Is, + displayValue: '', + stepFilterGroupId: '', + stepOutputKey: '', +}; + export const WorkflowStepFilterAddFilterRuleSelect = ({ stepFilterGroup, }: WorkflowStepFilterAddFilterRuleSelectProps) => { @@ -42,16 +53,10 @@ export const WorkflowStepFilterAddFilterRuleSelect = ({ closeDropdown(dropdownId); const newStepFilter = { - id: v4(), - type: 'text', - label: 'New Filter', - value: '', - operand: StepOperand.EQ, - displayValue: '', + ...BASE_NEW_STEP_FILTER, stepFilterGroupId: stepFilterGroup.id, - stepOutputKey: '', positionInStepFilterGroup: newPositionInStepFilterGroup, - }; + } satisfies StepFilter; upsertStepFilterSettings({ stepFilterToUpsert: newStepFilter, @@ -71,15 +76,9 @@ export const WorkflowStepFilterAddFilterRuleSelect = ({ }; const newStepFilter: StepFilter = { - id: v4(), - type: 'text', - operand: StepOperand.EQ, - value: '', - displayValue: '', + ...BASE_NEW_STEP_FILTER, stepFilterGroupId: newStepFilterGroupId, positionInStepFilterGroup: 1, - label: 'New Filter', - stepOutputKey: '', }; upsertStepFilterSettings({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx index 18556c494..4e9dec419 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx @@ -8,7 +8,7 @@ import { extractRawVariableNamePart } from '@/workflow/workflow-variables/utils/ import { searchVariableThroughOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchema'; import { useLingui } from '@lingui/react/macro'; import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; +import { useRecoilCallback, useRecoilValue } from 'recoil'; import { StepFilter } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -30,6 +30,7 @@ export const WorkflowStepFilterFieldSelect = ({ rawVariableName: stepFilter.stepOutputKey, part: 'stepId', }); + const stepsOutputSchema = useRecoilValue( stepsOutputSchemaFamilySelector({ workflowVersionId, @@ -37,6 +38,41 @@ export const WorkflowStepFilterFieldSelect = ({ }), ); + const handleChange = useRecoilCallback( + ({ snapshot }) => + (variableName: string) => { + const stepId = extractRawVariableNamePart({ + rawVariableName: variableName, + part: 'stepId', + }); + const currentStepOutputSchema = snapshot + .getLoadable( + stepsOutputSchemaFamilySelector({ + workflowVersionId, + stepIds: [stepId], + }), + ) + .getValue(); + + const { variableLabel, variableType } = + searchVariableThroughOutputSchema({ + stepOutputSchema: currentStepOutputSchema?.[0], + rawVariableName: variableName, + isFullRecord: false, + }); + + upsertStepFilterSettings({ + stepFilterToUpsert: { + ...stepFilter, + stepOutputKey: variableName, + displayValue: variableLabel ?? '', + type: variableType ?? 'unknown', + }, + }); + }, + [upsertStepFilterSettings, stepFilter, workflowVersionId], + ); + if (!isDefined(stepId)) { return null; } @@ -50,16 +86,6 @@ export const WorkflowStepFilterFieldSelect = ({ const isSelectedFieldNotFound = !isDefined(variableLabel); const label = isSelectedFieldNotFound ? t`No Field Selected` : variableLabel; - const handleChange = (variableName: string) => { - upsertStepFilterSettings({ - stepFilterToUpsert: { - ...stepFilter, - stepOutputKey: variableName, - displayValue: label, - }, - }); - }; - return ( { const { readonly } = useContext(WorkflowStepFilterContext); const { upsertStepFilterSettings } = useUpsertStepFilterSettings(); + const operands = getViewFilterOperands({ filterType: stepFilter.type }); - const handleChange = (operand: StepOperand) => { + const options = operands.map((operand) => ({ + value: operand, + label: getOperandLabel(operand), + })); + + const handleChange = (operand: ViewFilterOperand) => { upsertStepFilterSettings({ stepFilterToUpsert: { ...stepFilter, @@ -45,7 +40,7 @@ export const WorkflowStepFilterOperandSelect = ({ dropdownWidth={GenericDropdownContentWidth.Medium} dropdownId={`step-filter-operand-${stepFilter.id}`} value={stepFilter.operand} - options={STEP_OPERAND_OPTIONS} + options={options} onChange={handleChange} disabled={readonly} dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET} diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterColumn.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterColumn.stories.tsx index a74c6efb2..43af4351e 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterColumn.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterColumn.stories.tsx @@ -4,7 +4,7 @@ import { StepFilter, StepFilterGroup, StepLogicalOperator, - StepOperand, + ViewFilterOperand, } from 'twenty-shared/types'; import { ComponentDecorator } from 'twenty-ui/testing'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; @@ -28,7 +28,7 @@ const TEXT_STEP_FILTER: StepFilter = { type: 'text', label: 'Company Name', value: 'Acme', - operand: StepOperand.LIKE, + operand: ViewFilterOperand.Contains, }; const meta: Meta = { diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterFieldSelect.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterFieldSelect.stories.tsx index d01e6d01b..7285dec6a 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterFieldSelect.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterFieldSelect.stories.tsx @@ -1,6 +1,6 @@ import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator'; import { Meta, StoryObj } from '@storybook/react'; -import { StepFilter, StepOperand } from 'twenty-shared/types'; +import { StepFilter, ViewFilterOperand } from 'twenty-shared/types'; import { ComponentDecorator } from 'twenty-ui/testing'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator'; @@ -16,7 +16,7 @@ const DEFAULT_STEP_FILTER: StepFilter = { displayValue: '', type: 'text', label: 'New Filter', - operand: StepOperand.EQ, + operand: ViewFilterOperand.Is, value: '', positionInStepFilterGroup: 0, }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterOperandSelect.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterOperandSelect.stories.tsx index 91f01bb47..cff161666 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterOperandSelect.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterOperandSelect.stories.tsx @@ -1,14 +1,14 @@ import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator'; +import { WorkflowStepFilterOperandSelect } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterOperandSelect'; import { Meta, StoryObj } from '@storybook/react'; import { expect, within } from '@storybook/test'; -import { StepFilter, StepOperand } from 'twenty-shared/types'; +import { StepFilter, ViewFilterOperand } from 'twenty-shared/types'; import { ComponentDecorator } from 'twenty-ui/testing'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator'; import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator'; import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { WorkflowStepFilterOperandSelect } from '../WorkflowStepFilterOperandSelect'; const DEFAULT_STEP_FILTER: StepFilter = { id: 'filter-1', @@ -17,23 +17,11 @@ const DEFAULT_STEP_FILTER: StepFilter = { displayValue: 'Company Name', type: 'text', label: 'Company Name', - operand: StepOperand.EQ, + operand: ViewFilterOperand.Contains, value: '', positionInStepFilterGroup: 0, }; -const LIKE_OPERAND_FILTER: StepFilter = { - id: 'filter-1', - stepFilterGroupId: 'filter-group-1', - stepOutputKey: 'company.name', - displayValue: 'Company Name', - type: 'text', - label: 'Company Name', - operand: StepOperand.LIKE, - value: 'Acme', - positionInStepFilterGroup: 0, -}; - const GREATER_THAN_FILTER: StepFilter = { id: 'filter-1', stepFilterGroupId: 'filter-group-1', @@ -41,7 +29,7 @@ const GREATER_THAN_FILTER: StepFilter = { displayValue: 'Employee Count', type: 'number', label: 'Employee Count', - operand: StepOperand.GT, + operand: ViewFilterOperand.GreaterThanOrEqual, value: '100', positionInStepFilterGroup: 0, }; @@ -69,17 +57,6 @@ export default meta; type Story = StoryObj; export const Default: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - await expect(await canvas.findByText('Equals')).toBeVisible(); - }, -}; - -export const WithLikeOperand: Story = { - args: { - stepFilter: LIKE_OPERAND_FILTER, - }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -94,6 +71,8 @@ export const WithGreaterThanOperand: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - await expect(await canvas.findByText('Greater than')).toBeVisible(); + await expect( + await canvas.findByText('Greater than or equal'), + ).toBeVisible(); }, }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterValueInput.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterValueInput.stories.tsx index 3084e5720..985f168da 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterValueInput.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/__stories__/WorkflowStepFilterValueInput.stories.tsx @@ -1,7 +1,7 @@ import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator'; import { Meta, StoryObj } from '@storybook/react'; import { expect, within } from '@storybook/test'; -import { StepFilter, StepOperand } from 'twenty-shared/types'; +import { StepFilter, ViewFilterOperand } from 'twenty-shared/types'; import { ComponentDecorator } from 'twenty-ui/testing'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator'; @@ -17,7 +17,7 @@ const TEXT_FILTER: StepFilter = { displayValue: 'Company Name', type: 'text', label: 'Company Name', - operand: StepOperand.LIKE, + operand: ViewFilterOperand.Contains, value: 'Acme', positionInStepFilterGroup: 0, }; @@ -29,7 +29,7 @@ const NUMBER_FILTER: StepFilter = { displayValue: 'Employee Count', type: 'number', label: 'Employee Count', - operand: StepOperand.GT, + operand: ViewFilterOperand.GreaterThanOrEqual, value: '100', positionInStepFilterGroup: 0, }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/hooks/useAddRootStepFilter.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/hooks/useAddRootStepFilter.ts index bddae19a1..f1a3ab477 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/hooks/useAddRootStepFilter.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/hooks/useAddRootStepFilter.ts @@ -11,7 +11,7 @@ import { StepFilter, StepFilterGroup, StepLogicalOperator, - StepOperand, + ViewFilterOperand, } from 'twenty-shared/types'; import { v4 } from 'uuid'; @@ -48,10 +48,10 @@ export const useAddRootStepFilter = () => { const newStepFilter: StepFilter = { id: v4(), - type: 'text', + type: 'unknown', label: 'New Filter', value: '', - operand: StepOperand.EQ, + operand: ViewFilterOperand.Is, displayValue: '', stepFilterGroupId: newStepFilterGroup.id, stepOutputKey: '', diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/utils/getStepFilterOperands.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/utils/getStepFilterOperands.ts new file mode 100644 index 000000000..3b9d0ad96 --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/utils/getStepFilterOperands.ts @@ -0,0 +1,115 @@ +import { ViewFilterOperand } from 'twenty-shared/src/types'; + +const emptyOperands = [ + ViewFilterOperand.IsEmpty, + ViewFilterOperand.IsNotEmpty, +] as const; + +const relationOperands = [ + ViewFilterOperand.Is, + ViewFilterOperand.IsNot, +] as const; + +const defaultOperands = [ + ViewFilterOperand.Is, + ViewFilterOperand.IsNot, + ViewFilterOperand.Contains, + ViewFilterOperand.DoesNotContain, + ViewFilterOperand.GreaterThanOrEqual, + ViewFilterOperand.LessThanOrEqual, + ...emptyOperands, +] as const; + +export const FILTER_OPERANDS_MAP = { + TEXT: [ + ViewFilterOperand.Contains, + ViewFilterOperand.DoesNotContain, + ...emptyOperands, + ], + NUMBER: [ + ViewFilterOperand.GreaterThanOrEqual, + ViewFilterOperand.LessThanOrEqual, + ...emptyOperands, + ], + RAW_JSON: [ + ViewFilterOperand.Contains, + ViewFilterOperand.DoesNotContain, + ...emptyOperands, + ], + DATE_TIME: [ + ViewFilterOperand.Is, + ViewFilterOperand.IsRelative, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, + ViewFilterOperand.IsBefore, + ViewFilterOperand.IsAfter, + ...emptyOperands, + ], + DATE: [ + ViewFilterOperand.Is, + ViewFilterOperand.IsRelative, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, + ViewFilterOperand.IsBefore, + ViewFilterOperand.IsAfter, + ...emptyOperands, + ], + RATING: [ViewFilterOperand.Is, ViewFilterOperand.IsNot, ...emptyOperands], + RELATION: [...relationOperands, ...emptyOperands], + MULTI_SELECT: [ + ViewFilterOperand.Contains, + ViewFilterOperand.DoesNotContain, + ...emptyOperands, + ], + SELECT: [ViewFilterOperand.Is, ViewFilterOperand.IsNot, ...emptyOperands], + ARRAY: [ + ViewFilterOperand.Contains, + ViewFilterOperand.DoesNotContain, + ...emptyOperands, + ], + BOOLEAN: [ViewFilterOperand.Is], + UUID: [ViewFilterOperand.Is], + NUMERIC: [ + ViewFilterOperand.GreaterThanOrEqual, + ViewFilterOperand.LessThanOrEqual, + ...emptyOperands, + ], +}; + +export const getViewFilterOperands = ({ + filterType, +}: { + filterType: string; +}): readonly ViewFilterOperand[] => { + switch (filterType) { + case 'TEXT': + return FILTER_OPERANDS_MAP.TEXT; + case 'NUMBER': + return FILTER_OPERANDS_MAP.NUMBER; + case 'RAW_JSON': + return FILTER_OPERANDS_MAP.RAW_JSON; + case 'DATE_TIME': + case 'DATE': + return FILTER_OPERANDS_MAP.DATE_TIME; + case 'RATING': + return FILTER_OPERANDS_MAP.RATING; + case 'RELATION': + return FILTER_OPERANDS_MAP.RELATION; + case 'MULTI_SELECT': + return FILTER_OPERANDS_MAP.MULTI_SELECT; + case 'SELECT': + return FILTER_OPERANDS_MAP.SELECT; + case 'ARRAY': + return FILTER_OPERANDS_MAP.ARRAY; + case 'BOOLEAN': + return FILTER_OPERANDS_MAP.BOOLEAN; + case 'UUID': + return FILTER_OPERANDS_MAP.UUID; + case 'NUMERIC': + return FILTER_OPERANDS_MAP.NUMERIC; + default: + return defaultOperands; + } +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/searchVariableThroughOutputSchema.test.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/searchVariableThroughOutputSchema.test.ts index 5d5c9f214..788572aa3 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/searchVariableThroughOutputSchema.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/searchVariableThroughOutputSchema.test.ts @@ -76,6 +76,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: undefined, variablePathLabel: 'Step 1 > undefined', + variableType: 'unknown', }); }); @@ -89,6 +90,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: 'Name', variablePathLabel: 'Step 1 > Company > Name', + variableType: 'unknown', }); }); @@ -102,6 +104,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: 'Email', variablePathLabel: 'Step 1 > Person > Email', + variableType: 'unknown', }); }); @@ -115,6 +118,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: 'Company', variablePathLabel: 'Step 1 > Company > Company', + variableType: 'unknown', }); }); @@ -128,6 +132,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: 'Person', variablePathLabel: 'Step 1 > Person > Person', + variableType: 'unknown', }); }); @@ -141,6 +146,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: 'Simple Data', variablePathLabel: 'Step 1 > Simple Data', + variableType: 'unknown', }); }); @@ -154,6 +160,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: 'Field 1', variablePathLabel: 'Step 1 > Nested Data > Field 1', + variableType: 'unknown', }); }); @@ -167,6 +174,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: undefined, variablePathLabel: 'Step 1 > undefined', + variableType: 'unknown', }); }); @@ -180,6 +188,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: undefined, variablePathLabel: 'Step 1 > undefined', + variableType: 'unknown', }); }); @@ -208,6 +217,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: 'Field 1', variablePathLabel: 'Step 1 > Complex Field > Field 1', + variableType: 'unknown', }); }); }); @@ -274,6 +284,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: 'Name', variablePathLabel: 'Record is Created > Name', + variableType: FieldMetadataType.TEXT, }); }); @@ -288,6 +299,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: ' Amount Micros', variablePathLabel: 'Record is Created > ARR > Amount Micros', + variableType: FieldMetadataType.NUMERIC, }); }); @@ -301,6 +313,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: 'Company', variablePathLabel: 'Record is Created > Company', + variableType: 'unknown', }); }); @@ -314,6 +327,7 @@ describe('searchVariableThroughOutputSchema', () => { expect(result).toEqual({ variableLabel: undefined, variablePathLabel: 'Record is Created > undefined', + variableType: 'unknown', }); }); }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchema.ts index 016303693..2e4cc1e5a 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughOutputSchema.ts @@ -4,6 +4,7 @@ import { StepOutputSchema, } from '@/workflow/workflow-variables/types/StepOutputSchema'; import { isBaseOutputSchema } from '@/workflow/workflow-variables/utils/isBaseOutputSchema'; +import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema'; import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema'; import { isDefined } from 'twenty-shared/utils'; @@ -30,6 +31,18 @@ const getDisplayedSubStepFieldLabel = ( return; }; +const getVariableType = (key: string, outputSchema: OutputSchema): string => { + if (isRecordOutputSchema(outputSchema)) { + return outputSchema.fields[key]?.type ?? 'unknown'; + } + + if (isLinkOutputSchema(outputSchema)) { + return 'unknown'; + } + + return outputSchema[key]?.type ?? 'unknown'; +}; + const searchCurrentStepOutputSchema = ({ stepOutputSchema, path, @@ -90,6 +103,7 @@ const searchCurrentStepOutputSchema = ({ return { variableLabel: undefined, variablePathLabel: undefined, + variableType: undefined, }; } @@ -102,6 +116,10 @@ const searchCurrentStepOutputSchema = ({ currentSubStep, ), variablePathLabel, + variableType: getVariableType( + isSelectedFieldInNextKey ? nextKey : selectedField, + currentSubStep, + ), }; }; @@ -142,15 +160,17 @@ export const searchVariableThroughOutputSchema = ({ }; } - const { variableLabel, variablePathLabel } = searchCurrentStepOutputSchema({ - stepOutputSchema, - path, - isFullRecord, - selectedField, - }); + const { variableLabel, variablePathLabel, variableType } = + searchCurrentStepOutputSchema({ + stepOutputSchema, + path, + isFullRecord, + selectedField, + }); return { variableLabel, variablePathLabel: `${variablePathLabel} > ${variableLabel}`, + variableType, }; }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/__tests__/evaluate-filter-conditions.util.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/__tests__/evaluate-filter-conditions.util.spec.ts index 945401e04..5838c6f1e 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/__tests__/evaluate-filter-conditions.util.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/__tests__/evaluate-filter-conditions.util.spec.ts @@ -2,12 +2,32 @@ import { StepFilter, StepFilterGroup, StepLogicalOperator, - StepOperand, + ViewFilterOperand, } from 'twenty-shared/types'; import { evaluateFilterConditions } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util'; describe('evaluateFilterConditions', () => { + type ResolvedFilter = Omit & { + rightOperand: unknown; + leftOperand: unknown; + }; + + const createFilter = ( + operand: ViewFilterOperand, + leftOperand: unknown, + rightOperand: unknown, + ): ResolvedFilter => ({ + id: 'filter1', + type: 'text', + label: 'Test Filter', + rightOperand, + operand, + displayValue: String(rightOperand), + stepFilterGroupId: 'group1', + leftOperand, + }); + describe('empty inputs', () => { it('should return true when no filters or groups are provided', () => { const result = evaluateFilterConditions({ @@ -26,190 +46,32 @@ describe('evaluateFilterConditions', () => { }); describe('single filter operands', () => { - type ResolvedFilter = Omit & { - rightOperand: unknown; - leftOperand: unknown; - }; - - const createFilter = ( - operand: StepOperand, - leftOperand: unknown, - rightOperand: unknown, - ): ResolvedFilter => ({ - id: 'filter1', - type: 'text', - label: 'Test Filter', - rightOperand, - operand, - displayValue: String(rightOperand), - stepFilterGroupId: 'group1', - leftOperand, - }); - - describe('eq operand', () => { + describe('Is operand', () => { it('should return true when values are equal', () => { - const filter = createFilter(StepOperand.EQ, 'John', 'John'); + const filter = createFilter(ViewFilterOperand.Is, 'John', 'John'); const result = evaluateFilterConditions({ filters: [filter] }); expect(result).toBe(true); }); it('should return true when values are loosely equal', () => { - const filter = createFilter(StepOperand.EQ, '123', 123); + const filter = createFilter(ViewFilterOperand.Is, '123', 123); const result = evaluateFilterConditions({ filters: [filter] }); expect(result).toBe(true); }); it('should return false when values are not equal', () => { - const filter = createFilter(StepOperand.EQ, 'John', 'Jane'); - const result = evaluateFilterConditions({ filters: [filter] }); - - expect(result).toBe(false); - }); - }); - - describe('ne operand', () => { - it('should return false when values are equal', () => { - const filter = createFilter(StepOperand.NE, 'John', 'John'); + const filter = createFilter(ViewFilterOperand.Is, 'John', 'Jane'); const result = evaluateFilterConditions({ filters: [filter] }); expect(result).toBe(false); }); - it('should return true when values are not equal', () => { - const filter = createFilter(StepOperand.NE, 'John', 'Jane'); - const result = evaluateFilterConditions({ filters: [filter] }); - - expect(result).toBe(true); - }); - }); - - describe('numeric operands', () => { - it('should handle gt operand correctly', () => { - const filter1 = createFilter(StepOperand.GT, 30, 25); - const filter2 = createFilter(StepOperand.GT, 20, 25); - - expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false); - }); - - it('should handle gte operand correctly', () => { - const filter1 = createFilter(StepOperand.GTE, 25, 25); - const filter2 = createFilter(StepOperand.GTE, 30, 25); - const filter3 = createFilter(StepOperand.GTE, 20, 25); - - expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false); - }); - - it('should handle lt operand correctly', () => { - const filter1 = createFilter(StepOperand.LT, 20, 25); - const filter2 = createFilter(StepOperand.LT, 30, 25); - - expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false); - }); - - it('should handle lte operand correctly', () => { - const filter1 = createFilter(StepOperand.LTE, 25, 25); - const filter2 = createFilter(StepOperand.LTE, 20, 25); - const filter3 = createFilter(StepOperand.LTE, 30, 25); - - expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false); - }); - - it('should convert string numbers for numeric comparisons', () => { - const filter1 = createFilter(StepOperand.GT, '30', '25'); - const filter2 = createFilter(StepOperand.LT, '20', '25'); - - expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); - }); - }); - - describe('string operands', () => { - it('should handle like operand correctly', () => { - const filter1 = createFilter(StepOperand.LIKE, 'Hello World', 'World'); - const filter2 = createFilter(StepOperand.LIKE, 'Hello', 'World'); - - expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false); - }); - - it('should handle ilike operand correctly (case insensitive)', () => { - const filter1 = createFilter(StepOperand.ILIKE, 'Hello World', 'WORLD'); - const filter2 = createFilter(StepOperand.ILIKE, 'Hello World', 'world'); - const filter3 = createFilter(StepOperand.ILIKE, 'Hello', 'WORLD'); - - expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false); - }); - }); - - describe('in operand', () => { - it('should handle JSON array values', () => { - const filter1 = createFilter( - StepOperand.IN, - 'apple', - '["apple", "banana", "cherry"]', - ); - const filter2 = createFilter( - StepOperand.IN, - 'grape', - '["apple", "banana", "cherry"]', - ); - - expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false); - }); - - it('should handle comma-separated string values when JSON parsing fails', () => { - const filter1 = createFilter( - StepOperand.IN, - 'apple', - 'apple, banana, cherry', - ); - const filter2 = createFilter( - StepOperand.IN, - 'grape', - 'apple, banana, cherry', - ); - - expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); - expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false); - }); - - it('should handle comma-separated values with whitespace', () => { - const filter = createFilter( - StepOperand.IN, - 'apple', - ' apple , banana , cherry ', - ); - - expect(evaluateFilterConditions({ filters: [filter] })).toBe(true); - }); - - it('should return false for non-array JSON values', () => { - const filter = createFilter( - StepOperand.IN, - 'apple', - '{"key": "value"}', - ); - - expect(evaluateFilterConditions({ filters: [filter] })).toBe(false); - }); - }); - - describe('is operand', () => { it('should handle null checks', () => { - const filter1 = createFilter(StepOperand.IS, null, 'null'); - const filter2 = createFilter(StepOperand.IS, undefined, 'NULL'); - const filter3 = createFilter(StepOperand.IS, 'value', 'null'); + const filter1 = createFilter(ViewFilterOperand.Is, null, 'null'); + const filter2 = createFilter(ViewFilterOperand.Is, undefined, 'NULL'); + const filter3 = createFilter(ViewFilterOperand.Is, 'value', 'null'); expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); @@ -217,43 +79,213 @@ describe('evaluateFilterConditions', () => { }); it('should handle not null checks', () => { - const filter1 = createFilter(StepOperand.IS, 'value', 'not null'); - const filter2 = createFilter(StepOperand.IS, 'value', 'NOT NULL'); - const filter3 = createFilter(StepOperand.IS, null, 'not null'); - const filter4 = createFilter(StepOperand.IS, undefined, 'not null'); + const filter1 = createFilter(ViewFilterOperand.Is, 'value', 'not null'); + const filter2 = createFilter(ViewFilterOperand.Is, 'value', 'NOT NULL'); + const filter3 = createFilter(ViewFilterOperand.Is, null, 'not null'); + const filter4 = createFilter( + ViewFilterOperand.Is, + undefined, + 'not null', + ); expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false); expect(evaluateFilterConditions({ filters: [filter4] })).toBe(false); }); + }); - it('should handle exact value comparisons for non-null/not-null cases', () => { - const filter1 = createFilter(StepOperand.IS, 'exact', 'exact'); - const filter2 = createFilter(StepOperand.IS, 'value', 'different'); + describe('IsNot operand', () => { + it('should return false when values are equal', () => { + const filter = createFilter(ViewFilterOperand.IsNot, 'John', 'John'); + const result = evaluateFilterConditions({ filters: [filter] }); + + expect(result).toBe(false); + }); + + it('should return true when values are not equal', () => { + const filter = createFilter(ViewFilterOperand.IsNot, 'John', 'Jane'); + const result = evaluateFilterConditions({ filters: [filter] }); + + expect(result).toBe(true); + }); + }); + + describe('numeric operands', () => { + it('should handle GreaterThanOrEqual operand correctly', () => { + const filter1 = createFilter( + ViewFilterOperand.GreaterThanOrEqual, + 25, + 25, + ); + const filter2 = createFilter( + ViewFilterOperand.GreaterThanOrEqual, + 30, + 25, + ); + const filter3 = createFilter( + ViewFilterOperand.GreaterThanOrEqual, + 20, + 25, + ); + + expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false); + }); + + it('should handle LessThanOrEqual operand correctly', () => { + const filter1 = createFilter(ViewFilterOperand.LessThanOrEqual, 25, 25); + const filter2 = createFilter(ViewFilterOperand.LessThanOrEqual, 20, 25); + const filter3 = createFilter(ViewFilterOperand.LessThanOrEqual, 30, 25); + + expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false); + }); + }); + + describe('string and array operands', () => { + it('should handle Contains operand with strings', () => { + const filter1 = createFilter( + ViewFilterOperand.Contains, + 'Hello World', + 'World', + ); + const filter2 = createFilter( + ViewFilterOperand.Contains, + 'Hello', + 'World', + ); expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false); }); + + it('should handle DoesNotContain operand with strings', () => { + const filter1 = createFilter( + ViewFilterOperand.DoesNotContain, + 'Hello World', + 'World', + ); + const filter2 = createFilter( + ViewFilterOperand.DoesNotContain, + 'Hello', + 'World', + ); + + expect(evaluateFilterConditions({ filters: [filter1] })).toBe(false); + expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); + }); + + it('should handle Contains operand with arrays', () => { + const filter1 = createFilter( + ViewFilterOperand.Contains, + ['apple', 'banana', 'cherry'], + 'apple', + ); + const filter2 = createFilter( + ViewFilterOperand.Contains, + ['apple', 'banana', 'cherry'], + 'grape', + ); + + expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false); + }); + + it('should handle DoesNotContain operand with arrays', () => { + const filter1 = createFilter( + ViewFilterOperand.DoesNotContain, + ['apple', 'banana', 'cherry'], + 'apple', + ); + const filter2 = createFilter( + ViewFilterOperand.DoesNotContain, + ['apple', 'banana', 'cherry'], + 'grape', + ); + + expect(evaluateFilterConditions({ filters: [filter1] })).toBe(false); + expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); + }); + }); + + describe('empty operands', () => { + it('should handle IsEmpty operand correctly', () => { + const filter1 = createFilter(ViewFilterOperand.IsEmpty, null, ''); + const filter2 = createFilter(ViewFilterOperand.IsEmpty, undefined, ''); + const filter3 = createFilter(ViewFilterOperand.IsEmpty, '', ''); + const filter4 = createFilter(ViewFilterOperand.IsEmpty, [], ''); + const filter5 = createFilter( + ViewFilterOperand.IsEmpty, + 'not empty', + '', + ); + + expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter3] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter4] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter5] })).toBe(false); + }); + + it('should handle IsNotEmpty operand correctly', () => { + const filter1 = createFilter( + ViewFilterOperand.IsNotEmpty, + 'not empty', + '', + ); + const filter2 = createFilter( + ViewFilterOperand.IsNotEmpty, + ['item'], + '', + ); + const filter3 = createFilter(ViewFilterOperand.IsNotEmpty, null, ''); + const filter4 = createFilter(ViewFilterOperand.IsNotEmpty, '', ''); + const filter5 = createFilter(ViewFilterOperand.IsNotEmpty, [], ''); + + expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true); + expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false); + expect(evaluateFilterConditions({ filters: [filter4] })).toBe(false); + expect(evaluateFilterConditions({ filters: [filter5] })).toBe(false); + }); + }); + + describe('date operands', () => { + it('should handle date operands (returning false as placeholder)', () => { + const dateOperands = [ + ViewFilterOperand.IsRelative, + ViewFilterOperand.IsInPast, + ViewFilterOperand.IsInFuture, + ViewFilterOperand.IsToday, + ViewFilterOperand.IsBefore, + ViewFilterOperand.IsAfter, + ]; + + dateOperands.forEach((operand) => { + const filter = createFilter(operand, new Date(), new Date()); + + expect(evaluateFilterConditions({ filters: [filter] })).toBe(false); + }); + }); }); describe('error cases', () => { it('should throw error for unknown operand', () => { - const filter = createFilter('unknown' as StepOperand, 'value', 'value'); - - expect(() => evaluateFilterConditions({ filters: [filter] })).toThrow( - 'Unknown operand: unknown', + const filter = createFilter( + 'unknown' as ViewFilterOperand, + 'value', + 'value', ); + + expect(() => evaluateFilterConditions({ filters: [filter] })).toThrow(); }); }); }); describe('multiple filters without groups', () => { - type ResolvedFilter = Omit & { - rightOperand: unknown; - leftOperand: unknown; - }; - it('should apply AND logic by default for multiple filters', () => { const filters: ResolvedFilter[] = [ { @@ -261,7 +293,7 @@ describe('evaluateFilterConditions', () => { type: 'text', label: 'Name Filter', rightOperand: 'John', - operand: StepOperand.EQ, + operand: ViewFilterOperand.Is, displayValue: 'John', stepFilterGroupId: 'group1', leftOperand: 'John', @@ -271,7 +303,7 @@ describe('evaluateFilterConditions', () => { type: 'number', label: 'Age Filter', rightOperand: 25, - operand: StepOperand.GT, + operand: ViewFilterOperand.GreaterThanOrEqual, displayValue: '25', stepFilterGroupId: 'group1', leftOperand: 30, @@ -290,7 +322,7 @@ describe('evaluateFilterConditions', () => { type: 'text', label: 'Name Filter', rightOperand: 'John', - operand: StepOperand.EQ, + operand: ViewFilterOperand.Is, displayValue: 'John', stepFilterGroupId: 'group1', leftOperand: 'John', @@ -300,7 +332,7 @@ describe('evaluateFilterConditions', () => { type: 'number', label: 'Age Filter', rightOperand: 25, - operand: StepOperand.GT, + operand: ViewFilterOperand.GreaterThanOrEqual, displayValue: '25', stepFilterGroupId: 'group1', leftOperand: 20, // This will fail @@ -314,11 +346,6 @@ describe('evaluateFilterConditions', () => { }); describe('filter groups', () => { - type ResolvedFilter = Omit & { - rightOperand: unknown; - leftOperand: unknown; - }; - describe('single group with AND logic', () => { it('should return true when all filters pass', () => { const filterGroups: StepFilterGroup[] = [ @@ -334,7 +361,7 @@ describe('evaluateFilterConditions', () => { type: 'text', label: 'Name Filter', rightOperand: 'John', - operand: StepOperand.EQ, + operand: ViewFilterOperand.Is, displayValue: 'John', stepFilterGroupId: 'group1', leftOperand: 'John', @@ -344,7 +371,7 @@ describe('evaluateFilterConditions', () => { type: 'number', label: 'Age Filter', rightOperand: 25, - operand: StepOperand.GT, + operand: ViewFilterOperand.GreaterThanOrEqual, displayValue: '25', stepFilterGroupId: 'group1', leftOperand: 30, @@ -370,7 +397,7 @@ describe('evaluateFilterConditions', () => { type: 'text', label: 'Name Filter', rightOperand: 'John', - operand: StepOperand.EQ, + operand: ViewFilterOperand.Is, displayValue: 'John', stepFilterGroupId: 'group1', leftOperand: 'Jane', // This will fail @@ -380,7 +407,7 @@ describe('evaluateFilterConditions', () => { type: 'number', label: 'Age Filter', rightOperand: 25, - operand: StepOperand.GT, + operand: ViewFilterOperand.GreaterThanOrEqual, displayValue: '25', stepFilterGroupId: 'group1', leftOperand: 30, @@ -408,7 +435,7 @@ describe('evaluateFilterConditions', () => { type: 'text', label: 'Name Filter', rightOperand: 'John', - operand: StepOperand.EQ, + operand: ViewFilterOperand.Is, displayValue: 'John', stepFilterGroupId: 'group1', leftOperand: 'Jane', // This will fail @@ -418,7 +445,7 @@ describe('evaluateFilterConditions', () => { type: 'number', label: 'Age Filter', rightOperand: 25, - operand: StepOperand.GT, + operand: ViewFilterOperand.GreaterThanOrEqual, displayValue: '25', stepFilterGroupId: 'group1', leftOperand: 30, // This will pass @@ -444,7 +471,7 @@ describe('evaluateFilterConditions', () => { type: 'text', label: 'Name Filter', rightOperand: 'John', - operand: StepOperand.EQ, + operand: ViewFilterOperand.Is, displayValue: 'John', stepFilterGroupId: 'group1', leftOperand: 'Jane', // This will fail @@ -454,7 +481,7 @@ describe('evaluateFilterConditions', () => { type: 'number', label: 'Age Filter', rightOperand: 25, - operand: StepOperand.GT, + operand: ViewFilterOperand.GreaterThanOrEqual, displayValue: '25', stepFilterGroupId: 'group1', leftOperand: 20, // This will fail @@ -467,8 +494,110 @@ describe('evaluateFilterConditions', () => { }); }); + describe('multiple groups', () => { + it('should handle multiple root groups with AND logic between them', () => { + const filterGroups: StepFilterGroup[] = [ + { + id: 'group1', + logicalOperator: StepLogicalOperator.AND, + }, + { + id: 'group2', + logicalOperator: StepLogicalOperator.OR, + }, + ]; + + const filters: ResolvedFilter[] = [ + { + id: 'filter1', + type: 'text', + label: 'Name Filter', + rightOperand: 'John', + operand: ViewFilterOperand.Is, + displayValue: 'John', + stepFilterGroupId: 'group1', + leftOperand: 'John', + }, + { + id: 'filter2', + type: 'number', + label: 'Age Filter', + rightOperand: 25, + operand: ViewFilterOperand.GreaterThanOrEqual, + displayValue: '25', + stepFilterGroupId: 'group2', + leftOperand: 30, + }, + ]; + + const result = evaluateFilterConditions({ filterGroups, filters }); + + expect(result).toBe(true); + }); + }); + + describe('nested groups', () => { + it('should handle nested filter groups correctly', () => { + const filterGroups: StepFilterGroup[] = [ + { + id: 'root', + logicalOperator: StepLogicalOperator.AND, + }, + { + id: 'child1', + logicalOperator: StepLogicalOperator.OR, + parentStepFilterGroupId: 'root', + positionInStepFilterGroup: 1, + }, + { + id: 'child2', + logicalOperator: StepLogicalOperator.AND, + parentStepFilterGroupId: 'root', + positionInStepFilterGroup: 2, + }, + ]; + + const filters: ResolvedFilter[] = [ + { + id: 'filter1', + type: 'text', + label: 'Filter 1', + rightOperand: 'John', + operand: ViewFilterOperand.Is, + displayValue: 'John', + stepFilterGroupId: 'child1', + leftOperand: 'Jane', // This will fail + }, + { + id: 'filter2', + type: 'text', + label: 'Filter 2', + rightOperand: 'Smith', + operand: ViewFilterOperand.Is, + displayValue: 'Smith', + stepFilterGroupId: 'child1', + leftOperand: 'Smith', // This will pass (OR group passes) + }, + { + id: 'filter3', + type: 'number', + label: 'Filter 3', + rightOperand: 25, + operand: ViewFilterOperand.GreaterThanOrEqual, + displayValue: '25', + stepFilterGroupId: 'child2', + leftOperand: 30, // This will pass (AND group passes) + }, + ]; + + const result = evaluateFilterConditions({ filterGroups, filters }); + + expect(result).toBe(true); // child1 (OR) passes, child2 (AND) passes, root (AND) passes + }); + }); + describe('empty groups', () => { - it('should return true for empty group', () => { + it('should return true for empty filter groups', () => { const filterGroups: StepFilterGroup[] = [ { id: 'group1', @@ -482,254 +611,8 @@ describe('evaluateFilterConditions', () => { }); }); - describe('nested groups', () => { - it('should handle nested groups correctly', () => { - const filterGroups: StepFilterGroup[] = [ - { - id: 'group1', - logicalOperator: StepLogicalOperator.AND, - }, - { - id: 'group2', - logicalOperator: StepLogicalOperator.OR, - parentStepFilterGroupId: 'group1', - positionInStepFilterGroup: 1, - }, - ]; - - const filters: ResolvedFilter[] = [ - // Filter in parent group - { - id: 'filter1', - type: 'text', - label: 'Name Filter', - rightOperand: 'John', - operand: StepOperand.EQ, - displayValue: 'John', - stepFilterGroupId: 'group1', - leftOperand: 'John', // This will pass - }, - // Filters in child group with OR logic - { - id: 'filter2', - type: 'number', - label: 'Age Filter', - rightOperand: 25, - operand: StepOperand.GT, - displayValue: '25', - stepFilterGroupId: 'group2', - leftOperand: 20, // This will fail - }, - { - id: 'filter3', - type: 'text', - label: 'City Filter', - rightOperand: 'NYC', - operand: StepOperand.EQ, - displayValue: 'NYC', - stepFilterGroupId: 'group2', - leftOperand: 'NYC', // This will pass - }, - ]; - - // Parent group uses AND: filter1 (pass) AND group2 (pass because filter3 passes) - const result = evaluateFilterConditions({ filterGroups, filters }); - - expect(result).toBe(true); - }); - - it('should handle multiple child groups with correct positioning', () => { - const filterGroups: StepFilterGroup[] = [ - { - id: 'group1', - logicalOperator: StepLogicalOperator.AND, - }, - { - id: 'group2', - logicalOperator: StepLogicalOperator.OR, - parentStepFilterGroupId: 'group1', - positionInStepFilterGroup: 1, - }, - { - id: 'group3', - logicalOperator: StepLogicalOperator.AND, - parentStepFilterGroupId: 'group1', - positionInStepFilterGroup: 2, - }, - ]; - - const filters: ResolvedFilter[] = [ - // Group2 filters (OR logic) - { - id: 'filter1', - type: 'text', - label: 'Name Filter', - rightOperand: 'John', - operand: StepOperand.EQ, - displayValue: 'John', - stepFilterGroupId: 'group2', - leftOperand: 'Jane', // This will fail - }, - { - id: 'filter2', - type: 'text', - label: 'Status Filter', - rightOperand: 'active', - operand: StepOperand.EQ, - displayValue: 'active', - stepFilterGroupId: 'group2', - leftOperand: 'active', // This will pass - }, - // Group3 filters (AND logic) - { - id: 'filter3', - type: 'number', - label: 'Age Filter', - rightOperand: 18, - operand: StepOperand.GTE, - displayValue: '18', - stepFilterGroupId: 'group3', - leftOperand: 25, // This will pass - }, - { - id: 'filter4', - type: 'number', - label: 'Score Filter', - rightOperand: 80, - operand: StepOperand.GT, - displayValue: '80', - stepFilterGroupId: 'group3', - leftOperand: 85, // This will pass - }, - ]; - - // Group1 uses AND: group2 (pass) AND group3 (pass) - const result = evaluateFilterConditions({ filterGroups, filters }); - - expect(result).toBe(true); - }); - }); - - describe('multiple root groups', () => { - it('should combine multiple root groups with AND logic', () => { - const filterGroups: StepFilterGroup[] = [ - { - id: 'group1', - logicalOperator: StepLogicalOperator.OR, - }, - { - id: 'group2', - logicalOperator: StepLogicalOperator.AND, - }, - ]; - - const filters: ResolvedFilter[] = [ - // Group1 filters (OR logic) - { - id: 'filter1', - type: 'text', - label: 'Name Filter', - rightOperand: 'John', - operand: StepOperand.EQ, - displayValue: 'John', - stepFilterGroupId: 'group1', - leftOperand: 'John', // This will pass - }, - { - id: 'filter2', - type: 'text', - label: 'Status Filter', - rightOperand: 'inactive', - operand: StepOperand.EQ, - displayValue: 'inactive', - stepFilterGroupId: 'group1', - leftOperand: 'active', // This will fail - }, - // Group2 filters (AND logic) - { - id: 'filter3', - type: 'number', - label: 'Age Filter', - rightOperand: 18, - operand: StepOperand.GTE, - displayValue: '18', - stepFilterGroupId: 'group2', - leftOperand: 25, // This will pass - }, - { - id: 'filter4', - type: 'number', - label: 'Score Filter', - rightOperand: 80, - operand: StepOperand.GT, - displayValue: '80', - stepFilterGroupId: 'group2', - leftOperand: 85, // This will pass - }, - ]; - - // Root groups combined with AND: group1 (pass) AND group2 (pass) - const result = evaluateFilterConditions({ filterGroups, filters }); - - expect(result).toBe(true); - }); - - it('should return false when one root group fails', () => { - const filterGroups: StepFilterGroup[] = [ - { - id: 'group1', - logicalOperator: StepLogicalOperator.OR, - }, - { - id: 'group2', - logicalOperator: StepLogicalOperator.AND, - }, - ]; - - const filters: ResolvedFilter[] = [ - // Group1 filters (OR logic) - will pass - { - id: 'filter1', - type: 'text', - label: 'Name Filter', - rightOperand: 'John', - operand: StepOperand.EQ, - displayValue: 'John', - stepFilterGroupId: 'group1', - leftOperand: 'John', // This will pass - }, - // Group2 filters (AND logic) - will fail - { - id: 'filter2', - type: 'number', - label: 'Age Filter', - rightOperand: 30, - operand: StepOperand.GT, - displayValue: '30', - stepFilterGroupId: 'group2', - leftOperand: 25, // This will fail - }, - { - id: 'filter3', - type: 'number', - label: 'Score Filter', - rightOperand: 80, - operand: StepOperand.GT, - displayValue: '80', - stepFilterGroupId: 'group2', - leftOperand: 85, // This will pass - }, - ]; - - // Root groups combined with AND: group1 (pass) AND group2 (fail) - const result = evaluateFilterConditions({ filterGroups, filters }); - - expect(result).toBe(false); - }); - }); - describe('error cases', () => { - it('should throw error when filter group is not found', () => { + it('should throw error when filter references non-existent group', () => { const filterGroups: StepFilterGroup[] = [ { id: 'group1', @@ -743,42 +626,16 @@ describe('evaluateFilterConditions', () => { type: 'text', label: 'Name Filter', rightOperand: 'John', - operand: StepOperand.EQ, + operand: ViewFilterOperand.Is, displayValue: 'John', - stepFilterGroupId: 'nonexistent-group', + stepFilterGroupId: 'nonexistent', leftOperand: 'John', }, ]; expect(() => evaluateFilterConditions({ filterGroups, filters }), - ).toThrow('Filter group with id nonexistent-group not found'); - }); - - it('should throw error for unknown logical operator', () => { - const filterGroups: StepFilterGroup[] = [ - { - id: 'group1', - logicalOperator: 'UNKNOWN' as StepLogicalOperator, - }, - ]; - - const filters: ResolvedFilter[] = [ - { - id: 'filter1', - type: 'text', - label: 'Name Filter', - rightOperand: 'John', - operand: StepOperand.EQ, - displayValue: 'John', - stepFilterGroupId: 'group1', - leftOperand: 'John', - }, - ]; - - expect(() => - evaluateFilterConditions({ filterGroups, filters }), - ).toThrow('Unknown logical operator: UNKNOWN'); + ).toThrow('Filter group with id nonexistent not found'); }); }); }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts index 758cc5762..04aaa455a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util.ts @@ -1,4 +1,9 @@ -import { StepFilter, StepFilterGroup } from 'twenty-shared/types'; +import { + StepFilter, + StepFilterGroup, + ViewFilterOperand, +} from 'twenty-shared/types'; +import { assertUnreachable } from 'twenty-shared/utils'; type ResolvedFilter = Omit & { rightOperand: unknown; @@ -10,46 +15,7 @@ function evaluateFilter(filter: ResolvedFilter): boolean { const rightValue = filter.rightOperand; switch (filter.operand) { - case 'eq': - return leftValue == rightValue; - - case 'ne': - return leftValue != rightValue; - - case 'gt': - return Number(leftValue) > Number(rightValue); - - case 'gte': - return Number(leftValue) >= Number(rightValue); - - case 'lt': - return Number(leftValue) < Number(rightValue); - - case 'lte': - return Number(leftValue) <= Number(rightValue); - - case 'like': - return String(leftValue).includes(String(rightValue)); - - case 'ilike': - return String(leftValue) - .toLowerCase() - .includes(String(rightValue).toLowerCase()); - - case 'in': - try { - const values = JSON.parse(String(rightValue)); - - return Array.isArray(values) && values.includes(leftValue); - } catch { - const values = String(rightValue) - .split(',') - .map((v) => v.trim()); - - return values.includes(String(leftValue)); - } - - case 'is': + case ViewFilterOperand.Is: if (String(rightValue).toLowerCase() === 'null') { return leftValue === null || leftValue === undefined; } @@ -57,16 +23,68 @@ function evaluateFilter(filter: ResolvedFilter): boolean { return leftValue !== null && leftValue !== undefined; } - return leftValue === rightValue; + return leftValue == rightValue; + + case ViewFilterOperand.IsNot: + return leftValue != rightValue; + + case ViewFilterOperand.GreaterThanOrEqual: + return Number(leftValue) >= Number(rightValue); + + case ViewFilterOperand.LessThanOrEqual: + return Number(leftValue) <= Number(rightValue); + + case ViewFilterOperand.Contains: + if (Array.isArray(leftValue)) { + return leftValue.includes(rightValue); + } + + return String(leftValue).includes(String(rightValue)); + + case ViewFilterOperand.DoesNotContain: + if (Array.isArray(leftValue)) { + return !leftValue.includes(rightValue); + } + + return !String(leftValue).includes(String(rightValue)); + + case ViewFilterOperand.IsEmpty: + return ( + leftValue === null || + leftValue === undefined || + leftValue === '' || + (Array.isArray(leftValue) && leftValue.length === 0) + ); + + case ViewFilterOperand.IsNotEmpty: + return ( + leftValue !== null && + leftValue !== undefined && + leftValue !== '' && + (!Array.isArray(leftValue) || leftValue.length > 0) + ); + + case ViewFilterOperand.IsNotNull: + return leftValue !== null && leftValue !== undefined; + + case ViewFilterOperand.IsRelative: + case ViewFilterOperand.IsInPast: + case ViewFilterOperand.IsInFuture: + case ViewFilterOperand.IsToday: + case ViewFilterOperand.IsBefore: + case ViewFilterOperand.IsAfter: + // Date/time operands - for now, return false as placeholder + // These would need proper date logic implementation + return false; + + case ViewFilterOperand.VectorSearch: + return false; default: - throw new Error(`Unknown operand: ${filter.operand}`); + assertUnreachable(filter.operand); } } -/** - * Recursively evaluates a filter group and its children - */ function evaluateFilterGroup( groupId: string, filterGroups: StepFilterGroup[], @@ -78,7 +96,6 @@ function evaluateFilterGroup( throw new Error(`Filter group with id ${groupId} not found`); } - // Get all direct child groups const childGroups = filterGroups .filter((g) => g.parentStepFilterGroupId === groupId) .sort( diff --git a/packages/twenty-shared/src/types/StepFilters.ts b/packages/twenty-shared/src/types/StepFilters.ts index 31f7282a6..2e5078950 100644 --- a/packages/twenty-shared/src/types/StepFilters.ts +++ b/packages/twenty-shared/src/types/StepFilters.ts @@ -1,21 +1,10 @@ +import { ViewFilterOperand } from './ViewFilterOperand'; + export enum StepLogicalOperator { AND = 'AND', OR = 'OR', } -export enum StepOperand { - EQ = 'eq', - NE = 'ne', - GT = 'gt', - GTE = 'gte', - LT = 'lt', - LTE = 'lte', - LIKE = 'like', - ILIKE = 'ilike', - IN = 'in', - IS = 'is', -} - export type StepFilterGroup = { id: string; logicalOperator: StepLogicalOperator; @@ -28,7 +17,7 @@ export type StepFilter = { type: string; label: string; value: string; - operand: StepOperand; + operand: ViewFilterOperand; displayValue: string; stepFilterGroupId: string; stepOutputKey: string; diff --git a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts b/packages/twenty-shared/src/types/ViewFilterOperand.ts similarity index 100% rename from packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts rename to packages/twenty-shared/src/types/ViewFilterOperand.ts diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index 9aa815785..e99378854 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -14,4 +14,5 @@ export type { IsExactly } from './IsExactly'; export type { ObjectRecordsPermissions } from './ObjectRecordsPermissions'; export type { ObjectRecordsPermissionsByRoleId } from './ObjectRecordsPermissionsByRoleId'; export type { StepFilterGroup, StepFilter } from './StepFilters'; -export { StepLogicalOperator, StepOperand } from './StepFilters'; +export { StepLogicalOperator } from './StepFilters'; +export { ViewFilterOperand } from './ViewFilterOperand';