Use view filters operands in step filters + migrate to twenty-shared (#13137)
Step operand will more or less be the same as view filter operand. This PR: - moves `ViewFilterOperand` to twenty-shared - use it as step operand - check what operand should be available based on the selected field type in filter action - rewrite the function that evaluates filters so it uses ViewFilterOperand instead ViewFilterOperand may be renamed in a future PR.
This commit is contained in:
@ -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 { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
export const SeeRunsWorkflowSingleRecordAction = () => {
|
export const SeeRunsWorkflowSingleRecordAction = () => {
|
||||||
const recordId = useSelectedRecordIdOrThrow();
|
const recordId = useSelectedRecordIdOrThrow();
|
||||||
|
|||||||
@ -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 { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
export const SeeVersionsWorkflowSingleRecordAction = () => {
|
export const SeeVersionsWorkflowSingleRecordAction = () => {
|
||||||
const recordId = useSelectedRecordIdOrThrow();
|
const recordId = useSelectedRecordIdOrThrow();
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions
|
|||||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
export const SeeRunsWorkflowVersionSingleRecordAction = () => {
|
export const SeeRunsWorkflowVersionSingleRecordAction = () => {
|
||||||
const recordId = useSelectedRecordIdOrThrow();
|
const recordId = useSelectedRecordIdOrThrow();
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions
|
|||||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
export const SeeVersionsWorkflowVersionSingleRecordAction = () => {
|
export const SeeVersionsWorkflowVersionSingleRecordAction = () => {
|
||||||
const recordId = useSelectedRecordIdOrThrow();
|
const recordId = useSelectedRecordIdOrThrow();
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { ContextStoreTargetedRecordsRule } from '@/context-store/states/contextS
|
|||||||
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
|
||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { expect } from '@storybook/test';
|
import { expect } from '@storybook/test';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
|
|
||||||
describe('computeContextStoreFilters', () => {
|
describe('computeContextStoreFilters', () => {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { SelectableList } from '@/ui/layout/selectable-list/components/Selectabl
|
|||||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
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';
|
import { MenuItem } from 'twenty-ui/navigation';
|
||||||
|
|
||||||
type AdvancedFilterRecordFilterOperandSelectContentProps = {
|
type AdvancedFilterRecordFilterOperandSelectContentProps = {
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { selectedOperandInDropdownComponentState } from '@/object-record/object-
|
|||||||
import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue';
|
import { getRelativeDateDisplayValue } from '@/object-record/object-filter-dropdown/utils/getRelativeDateDisplayValue';
|
||||||
import { DateTimePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
|
import { DateTimePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
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 { computeVariableDateViewFilterValue } from '@/views/view-filter-value/utils/computeVariableDateViewFilterValue';
|
||||||
import {
|
import {
|
||||||
resolveDateViewFilterValue,
|
resolveDateViewFilterValue,
|
||||||
@ -13,6 +12,7 @@ import {
|
|||||||
VariableDateViewFilterValueUnit,
|
VariableDateViewFilterValueUnit,
|
||||||
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
|
} from '@/views/view-filter-value/utils/resolveDateViewFilterValue';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-
|
|||||||
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
|
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
|
||||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||||
import { ViewBarFilterDropdownVectorSearchInput } from '@/views/components/ViewBarFilterDropdownVectorSearchInput';
|
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 { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||||
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
|
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
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 =
|
export const selectedOperandInDropdownComponentState =
|
||||||
createComponentStateV2<ViewFilterOperand | null>({
|
createComponentStateV2<ViewFilterOperand | null>({
|
||||||
|
|||||||
@ -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 { capitalize } from 'twenty-shared/utils';
|
||||||
import { getOperandLabel, getOperandLabelShort } from '../getOperandLabel';
|
import { getOperandLabel, getOperandLabelShort } from '../getOperandLabel';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
import { isFilterOperandExpectingValue } from '../isFilterOperandExpectingValue';
|
import { isFilterOperandExpectingValue } from '../isFilterOperandExpectingValue';
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
export const configurableViewFilterOperands = new Set<ViewFilterOperand>([
|
export const configurableViewFilterOperands = new Set<ViewFilterOperand>([
|
||||||
ViewFilterOperand.Is,
|
ViewFilterOperand.Is,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
export const getOperandLabel = (
|
export const getOperandLabel = (
|
||||||
operand: ViewFilterOperand | null | undefined,
|
operand: ViewFilterOperand | null | undefined,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
export const isFilterOperandExpectingValue = (operand: ViewFilterOperand) => {
|
export const isFilterOperandExpectingValue = (operand: ViewFilterOperand) => {
|
||||||
switch (operand) {
|
switch (operand) {
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import { renderHook } from '@testing-library/react';
|
|||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { act } from 'react';
|
import { act } from 'react';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||||
import { useRemoveRecordFilter } from '../useRemoveRecordFilter';
|
import { useRemoveRecordFilter } from '../useRemoveRecordFilter';
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { act } from 'react';
|
|||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
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 { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||||
import { useUpsertRecordFilter } from '../useUpsertRecordFilter';
|
import { useUpsertRecordFilter } from '../useUpsertRecordFilter';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { FilterableAndTSVectorFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
|
import { FilterableAndTSVectorFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
|
||||||
import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
||||||
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
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 = {
|
export type RecordFilter = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { ViewFilterOperand as RecordFilterOperand } from '@/views/types/ViewFilterOperand';
|
export { ViewFilterOperand as RecordFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
|||||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||||
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
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 { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
|
|||||||
@ -19,8 +19,8 @@ import {
|
|||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
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 { 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 { 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 { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { Field } from '~/generated/graphql';
|
import { Field } from '~/generated/graphql';
|
||||||
import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields';
|
import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields';
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
FilterableFieldType,
|
FilterableFieldType,
|
||||||
} from '@/object-record/record-filter/types/FilterableFieldType';
|
} from '@/object-record/record-filter/types/FilterableFieldType';
|
||||||
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
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 { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import { assertUnreachable } from 'twenty-shared/utils';
|
import { assertUnreachable } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
|||||||
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
|
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
|
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
type UseHandleToggleTrashColumnFilterProps = {
|
type UseHandleToggleTrashColumnFilterProps = {
|
||||||
|
|||||||
@ -19,8 +19,8 @@ import { AppPath } from '@/types/AppPath';
|
|||||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { RelationType } from '~/generated-metadata/graphql';
|
import { RelationType } from '~/generated-metadata/graphql';
|
||||||
import { getAppPath } from '~/utils/navigation/getAppPath';
|
import { getAppPath } from '~/utils/navigation/getAppPath';
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
|
import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
|
||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { RelationType } from '~/generated-metadata/graphql';
|
import { RelationType } from '~/generated-metadata/graphql';
|
||||||
import { buildValueFromFilter } from './buildRecordInputFromFilter';
|
import { buildValueFromFilter } from './buildRecordInputFromFilter';
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
RecordFilterToRecordInputOperand,
|
RecordFilterToRecordInputOperand,
|
||||||
} from '@/object-record/record-filter/types/RecordFilter';
|
} from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
|
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 { assertUnreachable, parseJson } from 'twenty-shared/utils';
|
||||||
import { RelationType } from '~/generated-metadata/graphql';
|
import { RelationType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
|||||||
@ -9,10 +9,10 @@ import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
|
|||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { View } from '@/views/types/View';
|
import { View } from '@/views/types/View';
|
||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { act } from 'react';
|
import { act } from 'react';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
|
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
|
|||||||
@ -5,11 +5,11 @@ import { currentRecordFiltersComponentState } from '@/object-record/record-filte
|
|||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
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 { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
import { useApplyViewFiltersToCurrentRecordFilters } from '../useApplyViewFiltersToCurrentRecordFilters';
|
import { useApplyViewFiltersToCurrentRecordFilters } from '../useApplyViewFiltersToCurrentRecordFilters';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
const mockObjectMetadataItemNameSingular = 'company';
|
const mockObjectMetadataItemNameSingular = 'company';
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,8 @@ import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions
|
|||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { relationFilterValueSchemaObject } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema';
|
import { relationFilterValueSchemaObject } from '@/views/view-filter-value/validation-schemas/jsonRelationFilterValueSchema';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
const filterQueryParamsSchema = z.object({
|
const filterQueryParamsSchema = z.object({
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
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 { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
|
||||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
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) => {
|
export const useOpenVectorSearchFilter = (filterDropdownId?: string) => {
|
||||||
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
const setSelectedOperandInDropdown = useSetRecoilComponentStateV2(
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useRemoveRecordFilter } from '@/object-record/record-filter/hooks/useRe
|
|||||||
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
|
import { useUpsertRecordFilter } from '@/object-record/record-filter/hooks/useUpsertRecordFilter';
|
||||||
import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
|
import { isRecordFilterConsideredEmpty } from '@/object-record/record-filter/utils/isRecordFilterConsideredEmpty';
|
||||||
import { useVectorSearchFieldInRecordIndexContextOrThrow } from '@/views/hooks/useVectorSearchFieldInRecordIndexContextOrThrow';
|
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 { isDefined } from 'twenty-shared/utils';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { useVectorSearchFilterState } from './useVectorSearchFilterState';
|
import { useVectorSearchFilterState } from './useVectorSearchFilterState';
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
||||||
import { ViewFilterOperand } from './ViewFilterOperand';
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
export type ViewFilter = {
|
export type ViewFilter = {
|
||||||
__typename: 'ViewFilter';
|
__typename: 'ViewFilter';
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { areViewFiltersEqual } from '../areViewFiltersEqual';
|
import { areViewFiltersEqual } from '../areViewFiltersEqual';
|
||||||
|
|
||||||
describe('areViewFiltersEqual', () => {
|
describe('areViewFiltersEqual', () => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { getViewFiltersToCreate } from '../getViewFiltersToCreate';
|
import { getViewFiltersToCreate } from '../getViewFiltersToCreate';
|
||||||
|
|
||||||
describe('getViewFiltersToCreate', () => {
|
describe('getViewFiltersToCreate', () => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { getViewFiltersToDelete } from '../getViewFiltersToDelete';
|
import { getViewFiltersToDelete } from '../getViewFiltersToDelete';
|
||||||
|
|
||||||
describe('getViewFiltersToDelete', () => {
|
describe('getViewFiltersToDelete', () => {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
import { getViewFiltersToUpdate } from '../getViewFiltersToUpdate';
|
import { getViewFiltersToUpdate } from '../getViewFiltersToUpdate';
|
||||||
|
|
||||||
describe('getViewFiltersToUpdate', () => {
|
describe('getViewFiltersToUpdate', () => {
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import { RecordSort } from '@/object-record/record-sort/types/RecordSort';
|
|||||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||||
import { ViewField } from '@/views/types/ViewField';
|
import { ViewField } from '@/views/types/ViewField';
|
||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import { ViewSort } from '@/views/types/ViewSort';
|
import { ViewSort } from '@/views/types/ViewSort';
|
||||||
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
|
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
|
||||||
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
|
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
|
||||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||||
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
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) => {
|
export const isVectorSearchFilter = (filter: RecordFilter) => {
|
||||||
return filter.operand === ViewFilterOperand.VectorSearch;
|
return filter.operand === ViewFilterOperand.VectorSearch;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
|
||||||
import {
|
import {
|
||||||
addDays,
|
addDays,
|
||||||
addMonths,
|
addMonths,
|
||||||
@ -19,6 +18,7 @@ import {
|
|||||||
subWeeks,
|
subWeeks,
|
||||||
subYears,
|
subYears,
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
|
import { ViewFilterOperand } from 'twenty-shared/src/types/ViewFilterOperand';
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
StepFilter,
|
StepFilter,
|
||||||
StepFilterGroup,
|
StepFilterGroup,
|
||||||
StepLogicalOperator,
|
StepLogicalOperator,
|
||||||
StepOperand,
|
ViewFilterOperand,
|
||||||
} from 'twenty-shared/src/types';
|
} from 'twenty-shared/src/types';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { IconLibraryPlus, IconPlus } from 'twenty-ui/display';
|
import { IconLibraryPlus, IconPlus } from 'twenty-ui/display';
|
||||||
@ -21,6 +21,17 @@ type WorkflowStepFilterAddFilterRuleSelectProps = {
|
|||||||
stepFilterGroup: StepFilterGroup;
|
stepFilterGroup: StepFilterGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const BASE_NEW_STEP_FILTER = {
|
||||||
|
id: v4(),
|
||||||
|
type: 'unknown',
|
||||||
|
label: '',
|
||||||
|
value: '',
|
||||||
|
operand: ViewFilterOperand.Is,
|
||||||
|
displayValue: '',
|
||||||
|
stepFilterGroupId: '',
|
||||||
|
stepOutputKey: '',
|
||||||
|
};
|
||||||
|
|
||||||
export const WorkflowStepFilterAddFilterRuleSelect = ({
|
export const WorkflowStepFilterAddFilterRuleSelect = ({
|
||||||
stepFilterGroup,
|
stepFilterGroup,
|
||||||
}: WorkflowStepFilterAddFilterRuleSelectProps) => {
|
}: WorkflowStepFilterAddFilterRuleSelectProps) => {
|
||||||
@ -42,16 +53,10 @@ export const WorkflowStepFilterAddFilterRuleSelect = ({
|
|||||||
closeDropdown(dropdownId);
|
closeDropdown(dropdownId);
|
||||||
|
|
||||||
const newStepFilter = {
|
const newStepFilter = {
|
||||||
id: v4(),
|
...BASE_NEW_STEP_FILTER,
|
||||||
type: 'text',
|
|
||||||
label: 'New Filter',
|
|
||||||
value: '',
|
|
||||||
operand: StepOperand.EQ,
|
|
||||||
displayValue: '',
|
|
||||||
stepFilterGroupId: stepFilterGroup.id,
|
stepFilterGroupId: stepFilterGroup.id,
|
||||||
stepOutputKey: '',
|
|
||||||
positionInStepFilterGroup: newPositionInStepFilterGroup,
|
positionInStepFilterGroup: newPositionInStepFilterGroup,
|
||||||
};
|
} satisfies StepFilter;
|
||||||
|
|
||||||
upsertStepFilterSettings({
|
upsertStepFilterSettings({
|
||||||
stepFilterToUpsert: newStepFilter,
|
stepFilterToUpsert: newStepFilter,
|
||||||
@ -71,15 +76,9 @@ export const WorkflowStepFilterAddFilterRuleSelect = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const newStepFilter: StepFilter = {
|
const newStepFilter: StepFilter = {
|
||||||
id: v4(),
|
...BASE_NEW_STEP_FILTER,
|
||||||
type: 'text',
|
|
||||||
operand: StepOperand.EQ,
|
|
||||||
value: '',
|
|
||||||
displayValue: '',
|
|
||||||
stepFilterGroupId: newStepFilterGroupId,
|
stepFilterGroupId: newStepFilterGroupId,
|
||||||
positionInStepFilterGroup: 1,
|
positionInStepFilterGroup: 1,
|
||||||
label: 'New Filter',
|
|
||||||
stepOutputKey: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
upsertStepFilterSettings({
|
upsertStepFilterSettings({
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { extractRawVariableNamePart } from '@/workflow/workflow-variables/utils/
|
|||||||
import { searchVariableThroughOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchema';
|
import { searchVariableThroughOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchema';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||||
import { StepFilter } from 'twenty-shared/types';
|
import { StepFilter } from 'twenty-shared/types';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
@ -30,6 +30,7 @@ export const WorkflowStepFilterFieldSelect = ({
|
|||||||
rawVariableName: stepFilter.stepOutputKey,
|
rawVariableName: stepFilter.stepOutputKey,
|
||||||
part: 'stepId',
|
part: 'stepId',
|
||||||
});
|
});
|
||||||
|
|
||||||
const stepsOutputSchema = useRecoilValue(
|
const stepsOutputSchema = useRecoilValue(
|
||||||
stepsOutputSchemaFamilySelector({
|
stepsOutputSchemaFamilySelector({
|
||||||
workflowVersionId,
|
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)) {
|
if (!isDefined(stepId)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -50,16 +86,6 @@ export const WorkflowStepFilterFieldSelect = ({
|
|||||||
const isSelectedFieldNotFound = !isDefined(variableLabel);
|
const isSelectedFieldNotFound = !isDefined(variableLabel);
|
||||||
const label = isSelectedFieldNotFound ? t`No Field Selected` : variableLabel;
|
const label = isSelectedFieldNotFound ? t`No Field Selected` : variableLabel;
|
||||||
|
|
||||||
const handleChange = (variableName: string) => {
|
|
||||||
upsertStepFilterSettings({
|
|
||||||
stepFilterToUpsert: {
|
|
||||||
...stepFilter,
|
|
||||||
stepOutputKey: variableName,
|
|
||||||
displayValue: label,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkflowVariablesDropdown
|
<WorkflowVariablesDropdown
|
||||||
instanceId={`step-filter-field-${stepFilter.id}`}
|
instanceId={`step-filter-field-${stepFilter.id}`}
|
||||||
|
|||||||
@ -1,36 +1,31 @@
|
|||||||
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
|
import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset';
|
||||||
|
import { getOperandLabel } from '@/object-record/object-filter-dropdown/utils/getOperandLabel';
|
||||||
import { Select } from '@/ui/input/components/Select';
|
import { Select } from '@/ui/input/components/Select';
|
||||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||||
import { useUpsertStepFilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useUpsertStepFilterSettings';
|
import { useUpsertStepFilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useUpsertStepFilterSettings';
|
||||||
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
|
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
|
||||||
|
import { getViewFilterOperands } from '@/workflow/workflow-steps/workflow-actions/filter-action/utils/getStepFilterOperands';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { StepFilter, StepOperand } from 'twenty-shared/src/types';
|
import { StepFilter, ViewFilterOperand } from 'twenty-shared/src/types';
|
||||||
|
|
||||||
type WorkflowStepFilterOperandSelectProps = {
|
type WorkflowStepFilterOperandSelectProps = {
|
||||||
stepFilter: StepFilter;
|
stepFilter: StepFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
const STEP_OPERAND_OPTIONS = [
|
|
||||||
{ value: StepOperand.EQ, label: 'Equals' },
|
|
||||||
{ value: StepOperand.NE, label: 'Not equals' },
|
|
||||||
{ value: StepOperand.GT, label: 'Greater than' },
|
|
||||||
{ value: StepOperand.GTE, label: 'Greater than or equal' },
|
|
||||||
{ value: StepOperand.LT, label: 'Less than' },
|
|
||||||
{ value: StepOperand.LTE, label: 'Less than or equal' },
|
|
||||||
{ value: StepOperand.LIKE, label: 'Contains' },
|
|
||||||
{ value: StepOperand.ILIKE, label: 'Contains (case insensitive)' },
|
|
||||||
{ value: StepOperand.IN, label: 'In' },
|
|
||||||
{ value: StepOperand.IS, label: 'Is' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const WorkflowStepFilterOperandSelect = ({
|
export const WorkflowStepFilterOperandSelect = ({
|
||||||
stepFilter,
|
stepFilter,
|
||||||
}: WorkflowStepFilterOperandSelectProps) => {
|
}: WorkflowStepFilterOperandSelectProps) => {
|
||||||
const { readonly } = useContext(WorkflowStepFilterContext);
|
const { readonly } = useContext(WorkflowStepFilterContext);
|
||||||
|
|
||||||
const { upsertStepFilterSettings } = useUpsertStepFilterSettings();
|
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({
|
upsertStepFilterSettings({
|
||||||
stepFilterToUpsert: {
|
stepFilterToUpsert: {
|
||||||
...stepFilter,
|
...stepFilter,
|
||||||
@ -45,7 +40,7 @@ export const WorkflowStepFilterOperandSelect = ({
|
|||||||
dropdownWidth={GenericDropdownContentWidth.Medium}
|
dropdownWidth={GenericDropdownContentWidth.Medium}
|
||||||
dropdownId={`step-filter-operand-${stepFilter.id}`}
|
dropdownId={`step-filter-operand-${stepFilter.id}`}
|
||||||
value={stepFilter.operand}
|
value={stepFilter.operand}
|
||||||
options={STEP_OPERAND_OPTIONS}
|
options={options}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
|
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
StepFilter,
|
StepFilter,
|
||||||
StepFilterGroup,
|
StepFilterGroup,
|
||||||
StepLogicalOperator,
|
StepLogicalOperator,
|
||||||
StepOperand,
|
ViewFilterOperand,
|
||||||
} from 'twenty-shared/types';
|
} from 'twenty-shared/types';
|
||||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
@ -28,7 +28,7 @@ const TEXT_STEP_FILTER: StepFilter = {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'Company Name',
|
label: 'Company Name',
|
||||||
value: 'Acme',
|
value: 'Acme',
|
||||||
operand: StepOperand.LIKE,
|
operand: ViewFilterOperand.Contains,
|
||||||
};
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof WorkflowStepFilterColumn> = {
|
const meta: Meta<typeof WorkflowStepFilterColumn> = {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator';
|
import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator';
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
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 { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
||||||
@ -16,7 +16,7 @@ const DEFAULT_STEP_FILTER: StepFilter = {
|
|||||||
displayValue: '',
|
displayValue: '',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'New Filter',
|
label: 'New Filter',
|
||||||
operand: StepOperand.EQ,
|
operand: ViewFilterOperand.Is,
|
||||||
value: '',
|
value: '',
|
||||||
positionInStepFilterGroup: 0,
|
positionInStepFilterGroup: 0,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator';
|
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 { Meta, StoryObj } from '@storybook/react';
|
||||||
import { expect, within } from '@storybook/test';
|
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 { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
||||||
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
|
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
|
||||||
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
|
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { WorkflowStepFilterOperandSelect } from '../WorkflowStepFilterOperandSelect';
|
|
||||||
|
|
||||||
const DEFAULT_STEP_FILTER: StepFilter = {
|
const DEFAULT_STEP_FILTER: StepFilter = {
|
||||||
id: 'filter-1',
|
id: 'filter-1',
|
||||||
@ -17,23 +17,11 @@ const DEFAULT_STEP_FILTER: StepFilter = {
|
|||||||
displayValue: 'Company Name',
|
displayValue: 'Company Name',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'Company Name',
|
label: 'Company Name',
|
||||||
operand: StepOperand.EQ,
|
operand: ViewFilterOperand.Contains,
|
||||||
value: '',
|
value: '',
|
||||||
positionInStepFilterGroup: 0,
|
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 = {
|
const GREATER_THAN_FILTER: StepFilter = {
|
||||||
id: 'filter-1',
|
id: 'filter-1',
|
||||||
stepFilterGroupId: 'filter-group-1',
|
stepFilterGroupId: 'filter-group-1',
|
||||||
@ -41,7 +29,7 @@ const GREATER_THAN_FILTER: StepFilter = {
|
|||||||
displayValue: 'Employee Count',
|
displayValue: 'Employee Count',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
label: 'Employee Count',
|
label: 'Employee Count',
|
||||||
operand: StepOperand.GT,
|
operand: ViewFilterOperand.GreaterThanOrEqual,
|
||||||
value: '100',
|
value: '100',
|
||||||
positionInStepFilterGroup: 0,
|
positionInStepFilterGroup: 0,
|
||||||
};
|
};
|
||||||
@ -69,17 +57,6 @@ export default meta;
|
|||||||
type Story = StoryObj<typeof WorkflowStepFilterOperandSelect>;
|
type Story = StoryObj<typeof WorkflowStepFilterOperandSelect>;
|
||||||
|
|
||||||
export const Default: Story = {
|
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 }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
@ -94,6 +71,8 @@ export const WithGreaterThanOperand: Story = {
|
|||||||
play: async ({ canvasElement }) => {
|
play: async ({ canvasElement }) => {
|
||||||
const canvas = within(canvasElement);
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
await expect(await canvas.findByText('Greater than')).toBeVisible();
|
await expect(
|
||||||
|
await canvas.findByText('Greater than or equal'),
|
||||||
|
).toBeVisible();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator';
|
import { WorkflowStepFilterDecorator } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/decorators/WorkflowStepFilterDecorator';
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { expect, within } from '@storybook/test';
|
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 { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
||||||
@ -17,7 +17,7 @@ const TEXT_FILTER: StepFilter = {
|
|||||||
displayValue: 'Company Name',
|
displayValue: 'Company Name',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'Company Name',
|
label: 'Company Name',
|
||||||
operand: StepOperand.LIKE,
|
operand: ViewFilterOperand.Contains,
|
||||||
value: 'Acme',
|
value: 'Acme',
|
||||||
positionInStepFilterGroup: 0,
|
positionInStepFilterGroup: 0,
|
||||||
};
|
};
|
||||||
@ -29,7 +29,7 @@ const NUMBER_FILTER: StepFilter = {
|
|||||||
displayValue: 'Employee Count',
|
displayValue: 'Employee Count',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
label: 'Employee Count',
|
label: 'Employee Count',
|
||||||
operand: StepOperand.GT,
|
operand: ViewFilterOperand.GreaterThanOrEqual,
|
||||||
value: '100',
|
value: '100',
|
||||||
positionInStepFilterGroup: 0,
|
positionInStepFilterGroup: 0,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
StepFilter,
|
StepFilter,
|
||||||
StepFilterGroup,
|
StepFilterGroup,
|
||||||
StepLogicalOperator,
|
StepLogicalOperator,
|
||||||
StepOperand,
|
ViewFilterOperand,
|
||||||
} from 'twenty-shared/types';
|
} from 'twenty-shared/types';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
@ -48,10 +48,10 @@ export const useAddRootStepFilter = () => {
|
|||||||
|
|
||||||
const newStepFilter: StepFilter = {
|
const newStepFilter: StepFilter = {
|
||||||
id: v4(),
|
id: v4(),
|
||||||
type: 'text',
|
type: 'unknown',
|
||||||
label: 'New Filter',
|
label: 'New Filter',
|
||||||
value: '',
|
value: '',
|
||||||
operand: StepOperand.EQ,
|
operand: ViewFilterOperand.Is,
|
||||||
displayValue: '',
|
displayValue: '',
|
||||||
stepFilterGroupId: newStepFilterGroup.id,
|
stepFilterGroupId: newStepFilterGroup.id,
|
||||||
stepOutputKey: '',
|
stepOutputKey: '',
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -76,6 +76,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: undefined,
|
variableLabel: undefined,
|
||||||
variablePathLabel: 'Step 1 > undefined',
|
variablePathLabel: 'Step 1 > undefined',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -89,6 +90,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: 'Name',
|
variableLabel: 'Name',
|
||||||
variablePathLabel: 'Step 1 > Company > Name',
|
variablePathLabel: 'Step 1 > Company > Name',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -102,6 +104,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: 'Email',
|
variableLabel: 'Email',
|
||||||
variablePathLabel: 'Step 1 > Person > Email',
|
variablePathLabel: 'Step 1 > Person > Email',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -115,6 +118,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: 'Company',
|
variableLabel: 'Company',
|
||||||
variablePathLabel: 'Step 1 > Company > Company',
|
variablePathLabel: 'Step 1 > Company > Company',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -128,6 +132,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: 'Person',
|
variableLabel: 'Person',
|
||||||
variablePathLabel: 'Step 1 > Person > Person',
|
variablePathLabel: 'Step 1 > Person > Person',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -141,6 +146,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: 'Simple Data',
|
variableLabel: 'Simple Data',
|
||||||
variablePathLabel: 'Step 1 > Simple Data',
|
variablePathLabel: 'Step 1 > Simple Data',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -154,6 +160,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: 'Field 1',
|
variableLabel: 'Field 1',
|
||||||
variablePathLabel: 'Step 1 > Nested Data > Field 1',
|
variablePathLabel: 'Step 1 > Nested Data > Field 1',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -167,6 +174,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: undefined,
|
variableLabel: undefined,
|
||||||
variablePathLabel: 'Step 1 > undefined',
|
variablePathLabel: 'Step 1 > undefined',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -180,6 +188,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: undefined,
|
variableLabel: undefined,
|
||||||
variablePathLabel: 'Step 1 > undefined',
|
variablePathLabel: 'Step 1 > undefined',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -208,6 +217,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: 'Field 1',
|
variableLabel: 'Field 1',
|
||||||
variablePathLabel: 'Step 1 > Complex Field > Field 1',
|
variablePathLabel: 'Step 1 > Complex Field > Field 1',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -274,6 +284,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: 'Name',
|
variableLabel: 'Name',
|
||||||
variablePathLabel: 'Record is Created > Name',
|
variablePathLabel: 'Record is Created > Name',
|
||||||
|
variableType: FieldMetadataType.TEXT,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -288,6 +299,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: ' Amount Micros',
|
variableLabel: ' Amount Micros',
|
||||||
variablePathLabel: 'Record is Created > ARR > Amount Micros',
|
variablePathLabel: 'Record is Created > ARR > Amount Micros',
|
||||||
|
variableType: FieldMetadataType.NUMERIC,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -301,6 +313,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: 'Company',
|
variableLabel: 'Company',
|
||||||
variablePathLabel: 'Record is Created > Company',
|
variablePathLabel: 'Record is Created > Company',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -314,6 +327,7 @@ describe('searchVariableThroughOutputSchema', () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
variableLabel: undefined,
|
variableLabel: undefined,
|
||||||
variablePathLabel: 'Record is Created > undefined',
|
variablePathLabel: 'Record is Created > undefined',
|
||||||
|
variableType: 'unknown',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
StepOutputSchema,
|
StepOutputSchema,
|
||||||
} from '@/workflow/workflow-variables/types/StepOutputSchema';
|
} from '@/workflow/workflow-variables/types/StepOutputSchema';
|
||||||
import { isBaseOutputSchema } from '@/workflow/workflow-variables/utils/isBaseOutputSchema';
|
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 { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
@ -30,6 +31,18 @@ const getDisplayedSubStepFieldLabel = (
|
|||||||
return;
|
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 = ({
|
const searchCurrentStepOutputSchema = ({
|
||||||
stepOutputSchema,
|
stepOutputSchema,
|
||||||
path,
|
path,
|
||||||
@ -90,6 +103,7 @@ const searchCurrentStepOutputSchema = ({
|
|||||||
return {
|
return {
|
||||||
variableLabel: undefined,
|
variableLabel: undefined,
|
||||||
variablePathLabel: undefined,
|
variablePathLabel: undefined,
|
||||||
|
variableType: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +116,10 @@ const searchCurrentStepOutputSchema = ({
|
|||||||
currentSubStep,
|
currentSubStep,
|
||||||
),
|
),
|
||||||
variablePathLabel,
|
variablePathLabel,
|
||||||
|
variableType: getVariableType(
|
||||||
|
isSelectedFieldInNextKey ? nextKey : selectedField,
|
||||||
|
currentSubStep,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -142,15 +160,17 @@ export const searchVariableThroughOutputSchema = ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { variableLabel, variablePathLabel } = searchCurrentStepOutputSchema({
|
const { variableLabel, variablePathLabel, variableType } =
|
||||||
stepOutputSchema,
|
searchCurrentStepOutputSchema({
|
||||||
path,
|
stepOutputSchema,
|
||||||
isFullRecord,
|
path,
|
||||||
selectedField,
|
isFullRecord,
|
||||||
});
|
selectedField,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
variableLabel,
|
variableLabel,
|
||||||
variablePathLabel: `${variablePathLabel} > ${variableLabel}`,
|
variablePathLabel: `${variablePathLabel} > ${variableLabel}`,
|
||||||
|
variableType,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,12 +2,32 @@ import {
|
|||||||
StepFilter,
|
StepFilter,
|
||||||
StepFilterGroup,
|
StepFilterGroup,
|
||||||
StepLogicalOperator,
|
StepLogicalOperator,
|
||||||
StepOperand,
|
ViewFilterOperand,
|
||||||
} from 'twenty-shared/types';
|
} from 'twenty-shared/types';
|
||||||
|
|
||||||
import { evaluateFilterConditions } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util';
|
import { evaluateFilterConditions } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util';
|
||||||
|
|
||||||
describe('evaluateFilterConditions', () => {
|
describe('evaluateFilterConditions', () => {
|
||||||
|
type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
|
||||||
|
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', () => {
|
describe('empty inputs', () => {
|
||||||
it('should return true when no filters or groups are provided', () => {
|
it('should return true when no filters or groups are provided', () => {
|
||||||
const result = evaluateFilterConditions({
|
const result = evaluateFilterConditions({
|
||||||
@ -26,190 +46,32 @@ describe('evaluateFilterConditions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('single filter operands', () => {
|
describe('single filter operands', () => {
|
||||||
type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
|
describe('Is operand', () => {
|
||||||
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', () => {
|
|
||||||
it('should return true when values are equal', () => {
|
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] });
|
const result = evaluateFilterConditions({ filters: [filter] });
|
||||||
|
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true when values are loosely equal', () => {
|
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] });
|
const result = evaluateFilterConditions({ filters: [filter] });
|
||||||
|
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false when values are not equal', () => {
|
it('should return false when values are not equal', () => {
|
||||||
const filter = createFilter(StepOperand.EQ, 'John', 'Jane');
|
const filter = createFilter(ViewFilterOperand.Is, '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 result = evaluateFilterConditions({ filters: [filter] });
|
const result = evaluateFilterConditions({ filters: [filter] });
|
||||||
|
|
||||||
expect(result).toBe(false);
|
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', () => {
|
it('should handle null checks', () => {
|
||||||
const filter1 = createFilter(StepOperand.IS, null, 'null');
|
const filter1 = createFilter(ViewFilterOperand.Is, null, 'null');
|
||||||
const filter2 = createFilter(StepOperand.IS, undefined, 'NULL');
|
const filter2 = createFilter(ViewFilterOperand.Is, undefined, 'NULL');
|
||||||
const filter3 = createFilter(StepOperand.IS, 'value', 'null');
|
const filter3 = createFilter(ViewFilterOperand.Is, 'value', 'null');
|
||||||
|
|
||||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
|
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
|
||||||
@ -217,43 +79,213 @@ describe('evaluateFilterConditions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle not null checks', () => {
|
it('should handle not null checks', () => {
|
||||||
const filter1 = createFilter(StepOperand.IS, 'value', 'not null');
|
const filter1 = createFilter(ViewFilterOperand.Is, 'value', 'not null');
|
||||||
const filter2 = createFilter(StepOperand.IS, 'value', 'NOT NULL');
|
const filter2 = createFilter(ViewFilterOperand.Is, 'value', 'NOT NULL');
|
||||||
const filter3 = createFilter(StepOperand.IS, null, 'not null');
|
const filter3 = createFilter(ViewFilterOperand.Is, null, 'not null');
|
||||||
const filter4 = createFilter(StepOperand.IS, undefined, 'not null');
|
const filter4 = createFilter(
|
||||||
|
ViewFilterOperand.Is,
|
||||||
|
undefined,
|
||||||
|
'not null',
|
||||||
|
);
|
||||||
|
|
||||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
|
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
|
||||||
expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false);
|
expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false);
|
||||||
expect(evaluateFilterConditions({ filters: [filter4] })).toBe(false);
|
expect(evaluateFilterConditions({ filters: [filter4] })).toBe(false);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle exact value comparisons for non-null/not-null cases', () => {
|
describe('IsNot operand', () => {
|
||||||
const filter1 = createFilter(StepOperand.IS, 'exact', 'exact');
|
it('should return false when values are equal', () => {
|
||||||
const filter2 = createFilter(StepOperand.IS, 'value', 'different');
|
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: [filter1] })).toBe(true);
|
||||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false);
|
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', () => {
|
describe('error cases', () => {
|
||||||
it('should throw error for unknown operand', () => {
|
it('should throw error for unknown operand', () => {
|
||||||
const filter = createFilter('unknown' as StepOperand, 'value', 'value');
|
const filter = createFilter(
|
||||||
|
'unknown' as ViewFilterOperand,
|
||||||
expect(() => evaluateFilterConditions({ filters: [filter] })).toThrow(
|
'value',
|
||||||
'Unknown operand: unknown',
|
'value',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(() => evaluateFilterConditions({ filters: [filter] })).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('multiple filters without groups', () => {
|
describe('multiple filters without groups', () => {
|
||||||
type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
|
|
||||||
rightOperand: unknown;
|
|
||||||
leftOperand: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should apply AND logic by default for multiple filters', () => {
|
it('should apply AND logic by default for multiple filters', () => {
|
||||||
const filters: ResolvedFilter[] = [
|
const filters: ResolvedFilter[] = [
|
||||||
{
|
{
|
||||||
@ -261,7 +293,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'Name Filter',
|
label: 'Name Filter',
|
||||||
rightOperand: 'John',
|
rightOperand: 'John',
|
||||||
operand: StepOperand.EQ,
|
operand: ViewFilterOperand.Is,
|
||||||
displayValue: 'John',
|
displayValue: 'John',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 'John',
|
leftOperand: 'John',
|
||||||
@ -271,7 +303,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
label: 'Age Filter',
|
label: 'Age Filter',
|
||||||
rightOperand: 25,
|
rightOperand: 25,
|
||||||
operand: StepOperand.GT,
|
operand: ViewFilterOperand.GreaterThanOrEqual,
|
||||||
displayValue: '25',
|
displayValue: '25',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 30,
|
leftOperand: 30,
|
||||||
@ -290,7 +322,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'Name Filter',
|
label: 'Name Filter',
|
||||||
rightOperand: 'John',
|
rightOperand: 'John',
|
||||||
operand: StepOperand.EQ,
|
operand: ViewFilterOperand.Is,
|
||||||
displayValue: 'John',
|
displayValue: 'John',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 'John',
|
leftOperand: 'John',
|
||||||
@ -300,7 +332,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
label: 'Age Filter',
|
label: 'Age Filter',
|
||||||
rightOperand: 25,
|
rightOperand: 25,
|
||||||
operand: StepOperand.GT,
|
operand: ViewFilterOperand.GreaterThanOrEqual,
|
||||||
displayValue: '25',
|
displayValue: '25',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 20, // This will fail
|
leftOperand: 20, // This will fail
|
||||||
@ -314,11 +346,6 @@ describe('evaluateFilterConditions', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('filter groups', () => {
|
describe('filter groups', () => {
|
||||||
type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
|
|
||||||
rightOperand: unknown;
|
|
||||||
leftOperand: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('single group with AND logic', () => {
|
describe('single group with AND logic', () => {
|
||||||
it('should return true when all filters pass', () => {
|
it('should return true when all filters pass', () => {
|
||||||
const filterGroups: StepFilterGroup[] = [
|
const filterGroups: StepFilterGroup[] = [
|
||||||
@ -334,7 +361,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'Name Filter',
|
label: 'Name Filter',
|
||||||
rightOperand: 'John',
|
rightOperand: 'John',
|
||||||
operand: StepOperand.EQ,
|
operand: ViewFilterOperand.Is,
|
||||||
displayValue: 'John',
|
displayValue: 'John',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 'John',
|
leftOperand: 'John',
|
||||||
@ -344,7 +371,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
label: 'Age Filter',
|
label: 'Age Filter',
|
||||||
rightOperand: 25,
|
rightOperand: 25,
|
||||||
operand: StepOperand.GT,
|
operand: ViewFilterOperand.GreaterThanOrEqual,
|
||||||
displayValue: '25',
|
displayValue: '25',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 30,
|
leftOperand: 30,
|
||||||
@ -370,7 +397,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'Name Filter',
|
label: 'Name Filter',
|
||||||
rightOperand: 'John',
|
rightOperand: 'John',
|
||||||
operand: StepOperand.EQ,
|
operand: ViewFilterOperand.Is,
|
||||||
displayValue: 'John',
|
displayValue: 'John',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 'Jane', // This will fail
|
leftOperand: 'Jane', // This will fail
|
||||||
@ -380,7 +407,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
label: 'Age Filter',
|
label: 'Age Filter',
|
||||||
rightOperand: 25,
|
rightOperand: 25,
|
||||||
operand: StepOperand.GT,
|
operand: ViewFilterOperand.GreaterThanOrEqual,
|
||||||
displayValue: '25',
|
displayValue: '25',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 30,
|
leftOperand: 30,
|
||||||
@ -408,7 +435,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'Name Filter',
|
label: 'Name Filter',
|
||||||
rightOperand: 'John',
|
rightOperand: 'John',
|
||||||
operand: StepOperand.EQ,
|
operand: ViewFilterOperand.Is,
|
||||||
displayValue: 'John',
|
displayValue: 'John',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 'Jane', // This will fail
|
leftOperand: 'Jane', // This will fail
|
||||||
@ -418,7 +445,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
label: 'Age Filter',
|
label: 'Age Filter',
|
||||||
rightOperand: 25,
|
rightOperand: 25,
|
||||||
operand: StepOperand.GT,
|
operand: ViewFilterOperand.GreaterThanOrEqual,
|
||||||
displayValue: '25',
|
displayValue: '25',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 30, // This will pass
|
leftOperand: 30, // This will pass
|
||||||
@ -444,7 +471,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'Name Filter',
|
label: 'Name Filter',
|
||||||
rightOperand: 'John',
|
rightOperand: 'John',
|
||||||
operand: StepOperand.EQ,
|
operand: ViewFilterOperand.Is,
|
||||||
displayValue: 'John',
|
displayValue: 'John',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 'Jane', // This will fail
|
leftOperand: 'Jane', // This will fail
|
||||||
@ -454,7 +481,7 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
label: 'Age Filter',
|
label: 'Age Filter',
|
||||||
rightOperand: 25,
|
rightOperand: 25,
|
||||||
operand: StepOperand.GT,
|
operand: ViewFilterOperand.GreaterThanOrEqual,
|
||||||
displayValue: '25',
|
displayValue: '25',
|
||||||
stepFilterGroupId: 'group1',
|
stepFilterGroupId: 'group1',
|
||||||
leftOperand: 20, // This will fail
|
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', () => {
|
describe('empty groups', () => {
|
||||||
it('should return true for empty group', () => {
|
it('should return true for empty filter groups', () => {
|
||||||
const filterGroups: StepFilterGroup[] = [
|
const filterGroups: StepFilterGroup[] = [
|
||||||
{
|
{
|
||||||
id: 'group1',
|
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', () => {
|
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[] = [
|
const filterGroups: StepFilterGroup[] = [
|
||||||
{
|
{
|
||||||
id: 'group1',
|
id: 'group1',
|
||||||
@ -743,42 +626,16 @@ describe('evaluateFilterConditions', () => {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
label: 'Name Filter',
|
label: 'Name Filter',
|
||||||
rightOperand: 'John',
|
rightOperand: 'John',
|
||||||
operand: StepOperand.EQ,
|
operand: ViewFilterOperand.Is,
|
||||||
displayValue: 'John',
|
displayValue: 'John',
|
||||||
stepFilterGroupId: 'nonexistent-group',
|
stepFilterGroupId: 'nonexistent',
|
||||||
leftOperand: 'John',
|
leftOperand: 'John',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
evaluateFilterConditions({ filterGroups, filters }),
|
evaluateFilterConditions({ filterGroups, filters }),
|
||||||
).toThrow('Filter group with id nonexistent-group not found');
|
).toThrow('Filter group with id nonexistent 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');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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<StepFilter, 'value' | 'stepOutputKey'> & {
|
type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
|
||||||
rightOperand: unknown;
|
rightOperand: unknown;
|
||||||
@ -10,46 +15,7 @@ function evaluateFilter(filter: ResolvedFilter): boolean {
|
|||||||
const rightValue = filter.rightOperand;
|
const rightValue = filter.rightOperand;
|
||||||
|
|
||||||
switch (filter.operand) {
|
switch (filter.operand) {
|
||||||
case 'eq':
|
case ViewFilterOperand.Is:
|
||||||
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':
|
|
||||||
if (String(rightValue).toLowerCase() === 'null') {
|
if (String(rightValue).toLowerCase() === 'null') {
|
||||||
return leftValue === null || leftValue === undefined;
|
return leftValue === null || leftValue === undefined;
|
||||||
}
|
}
|
||||||
@ -57,16 +23,68 @@ function evaluateFilter(filter: ResolvedFilter): boolean {
|
|||||||
return leftValue !== null && leftValue !== undefined;
|
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:
|
default:
|
||||||
throw new Error(`Unknown operand: ${filter.operand}`);
|
assertUnreachable(filter.operand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively evaluates a filter group and its children
|
|
||||||
*/
|
|
||||||
function evaluateFilterGroup(
|
function evaluateFilterGroup(
|
||||||
groupId: string,
|
groupId: string,
|
||||||
filterGroups: StepFilterGroup[],
|
filterGroups: StepFilterGroup[],
|
||||||
@ -78,7 +96,6 @@ function evaluateFilterGroup(
|
|||||||
throw new Error(`Filter group with id ${groupId} not found`);
|
throw new Error(`Filter group with id ${groupId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all direct child groups
|
|
||||||
const childGroups = filterGroups
|
const childGroups = filterGroups
|
||||||
.filter((g) => g.parentStepFilterGroupId === groupId)
|
.filter((g) => g.parentStepFilterGroupId === groupId)
|
||||||
.sort(
|
.sort(
|
||||||
|
|||||||
@ -1,21 +1,10 @@
|
|||||||
|
import { ViewFilterOperand } from './ViewFilterOperand';
|
||||||
|
|
||||||
export enum StepLogicalOperator {
|
export enum StepLogicalOperator {
|
||||||
AND = 'AND',
|
AND = 'AND',
|
||||||
OR = 'OR',
|
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 = {
|
export type StepFilterGroup = {
|
||||||
id: string;
|
id: string;
|
||||||
logicalOperator: StepLogicalOperator;
|
logicalOperator: StepLogicalOperator;
|
||||||
@ -28,7 +17,7 @@ export type StepFilter = {
|
|||||||
type: string;
|
type: string;
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
operand: StepOperand;
|
operand: ViewFilterOperand;
|
||||||
displayValue: string;
|
displayValue: string;
|
||||||
stepFilterGroupId: string;
|
stepFilterGroupId: string;
|
||||||
stepOutputKey: string;
|
stepOutputKey: string;
|
||||||
|
|||||||
@ -14,4 +14,5 @@ export type { IsExactly } from './IsExactly';
|
|||||||
export type { ObjectRecordsPermissions } from './ObjectRecordsPermissions';
|
export type { ObjectRecordsPermissions } from './ObjectRecordsPermissions';
|
||||||
export type { ObjectRecordsPermissionsByRoleId } from './ObjectRecordsPermissionsByRoleId';
|
export type { ObjectRecordsPermissionsByRoleId } from './ObjectRecordsPermissionsByRoleId';
|
||||||
export type { StepFilterGroup, StepFilter } from './StepFilters';
|
export type { StepFilterGroup, StepFilter } from './StepFilters';
|
||||||
export { StepLogicalOperator, StepOperand } from './StepFilters';
|
export { StepLogicalOperator } from './StepFilters';
|
||||||
|
export { ViewFilterOperand } from './ViewFilterOperand';
|
||||||
|
|||||||
Reference in New Issue
Block a user