Tt filter step input per variable type (#13371)

- add fieldMetadataId to step output schema
- use it to display FormFieldInput in Filter input
- few fixes for a few fields

Next step:
- Handle composite fields
- Design review
This commit is contained in:
Thomas Trompette
2025-07-23 13:54:06 +02:00
committed by GitHub
parent a0a575fa0b
commit 015c4477a7
28 changed files with 347 additions and 79 deletions

View File

@ -47,6 +47,7 @@ import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isF
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect'; import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText'; import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid'; import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUuid';
import { FieldMetadataType } from 'twenty-shared/types';
import { JsonValue } from 'type-fest'; import { JsonValue } from 'type-fest';
type FormFieldInputProps = { type FormFieldInputProps = {
@ -70,7 +71,7 @@ export const FormFieldInput = ({
error, error,
onError, onError,
}: FormFieldInputProps) => { }: FormFieldInputProps) => {
return isFieldNumber(field) ? ( return isFieldNumber(field) || field.type === FieldMetadataType.NUMERIC ? (
<FormNumberFieldInput <FormNumberFieldInput
label={field.label} label={field.label}
defaultValue={defaultValue as string | number | undefined} defaultValue={defaultValue as string | number | undefined}

View File

@ -48,7 +48,7 @@ export const FormBooleanFieldInput = ({
} }
: { : {
type: 'static', type: 'static',
value: defaultValue ?? false, value: Boolean(defaultValue),
}, },
); );

View File

@ -19,6 +19,7 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { ChangeEvent, KeyboardEvent, useId, useRef, useState } from 'react'; import { ChangeEvent, KeyboardEvent, useId, useRef, useState } from 'react';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -106,9 +107,10 @@ export const FormDateTimeFieldInput = ({
}, },
); );
const draftValueAsDate = isDefined(draftValue.value) const draftValueAsDate =
? new Date(draftValue.value) isDefined(draftValue.value) && isNonEmptyString(draftValue.value)
: null; ? new Date(draftValue.value)
: null;
const [pickerDate, setPickerDate] = const [pickerDate, setPickerDate] =
useState<Nullable<Date>>(draftValueAsDate); useState<Nullable<Date>>(draftValueAsDate);

View File

@ -10,7 +10,9 @@ import { useChildStepFiltersAndChildStepFilterGroups } from '@/workflow/workflow
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 { rootLevelStepFilterGroupComponentSelector } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/rootLevelStepFilterGroupComponentSelector'; import { rootLevelStepFilterGroupComponentSelector } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/rootLevelStepFilterGroupComponentSelector';
import { isStepFilterGroupChildAStepFilterGroup } from '@/workflow/workflow-steps/workflow-actions/filter-action/utils/isStepFilterGroupChildAStepFilterGroup'; import { isStepFilterGroupChildAStepFilterGroup } from '@/workflow/workflow-steps/workflow-actions/filter-action/utils/isStepFilterGroupChildAStepFilterGroup';
import { useAvailableVariablesInWorkflowStep } from '@/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
const StyledContainer = styled.div` const StyledContainer = styled.div`
@ -27,6 +29,10 @@ const StyledChildContainer = styled.div`
width: 100%; width: 100%;
`; `;
const StyledDangerContainer = styled.div`
color: ${({ theme }) => theme.font.color.danger};
`;
type WorkflowEditActionFilterBodyProps = { type WorkflowEditActionFilterBodyProps = {
action: WorkflowFilterAction; action: WorkflowFilterAction;
actionOptions: actionOptions:
@ -43,6 +49,8 @@ export const WorkflowEditActionFilterBody = ({
action, action,
actionOptions, actionOptions,
}: WorkflowEditActionFilterBodyProps) => { }: WorkflowEditActionFilterBodyProps) => {
const { t } = useLingui();
const rootStepFilterGroup = useRecoilComponentValueV2( const rootStepFilterGroup = useRecoilComponentValueV2(
rootLevelStepFilterGroupComponentSelector, rootLevelStepFilterGroupComponentSelector,
); );
@ -69,6 +77,22 @@ export const WorkflowEditActionFilterBody = ({
}); });
}; };
const availableVariablesInWorkflowStep = useAvailableVariablesInWorkflowStep(
{},
);
const noAvailableVariables = availableVariablesInWorkflowStep.length === 0;
if (noAvailableVariables) {
return (
<WorkflowStepBody>
<StyledDangerContainer>
{t`No Available Step Outputs`}
</StyledDangerContainer>
</WorkflowStepBody>
);
}
return ( return (
<WorkflowStepFilterContext.Provider <WorkflowStepFilterContext.Provider
value={{ value={{

View File

@ -67,6 +67,7 @@ export const WorkflowStepFilterFieldSelect = ({
stepOutputKey: variableName, stepOutputKey: variableName,
displayValue: variableLabel ?? '', displayValue: variableLabel ?? '',
type: variableType ?? 'unknown', type: variableType ?? 'unknown',
value: '',
}, },
}); });
}, },
@ -85,10 +86,11 @@ 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 dropdownId = `step-filter-field-${stepFilter.id}`;
return ( return (
<WorkflowVariablesDropdown <WorkflowVariablesDropdown
instanceId={`step-filter-field-${stepFilter.id}`} instanceId={dropdownId}
onVariableSelect={handleChange} onVariableSelect={handleChange}
disabled={readonly} disabled={readonly}
clickableComponent={ clickableComponent={

View File

@ -1,15 +1,45 @@
import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
import { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands';
import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput';
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput'; import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useWorkflowStepContextOrThrow } from '@/workflow/states/context/WorkflowStepContext';
import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/stepsOutputSchemaFamilySelector';
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 { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { extractRawVariableNamePart } from '@/workflow/workflow-variables/utils/extractRawVariableNamePart';
import { searchVariableThroughOutputSchema } from '@/workflow/workflow-variables/utils/searchVariableThroughOutputSchema';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { isObject, isString } from '@sniptt/guards';
import { useContext } from 'react'; import { useContext } from 'react';
import { StepFilter } from 'twenty-shared/src/types'; import { useRecoilValue } from 'recoil';
import { FieldMetadataType, StepFilter } from 'twenty-shared/src/types';
import { isDefined } from 'twenty-shared/utils';
import { JsonValue } from 'type-fest';
type WorkflowStepFilterValueInputProps = { type WorkflowStepFilterValueInputProps = {
stepFilter: StepFilter; stepFilter: StepFilter;
}; };
const isFilterableFieldMetadataType = (
type: string,
): type is FieldMetadataType => {
return [
FieldMetadataType.TEXT,
FieldMetadataType.NUMBER,
FieldMetadataType.BOOLEAN,
FieldMetadataType.DATE_TIME,
FieldMetadataType.DATE,
FieldMetadataType.NUMERIC,
FieldMetadataType.SELECT,
FieldMetadataType.MULTI_SELECT,
FieldMetadataType.RAW_JSON,
FieldMetadataType.RICH_TEXT_V2,
FieldMetadataType.ARRAY,
].includes(type as FieldMetadataType);
};
export const WorkflowStepFilterValueInput = ({ export const WorkflowStepFilterValueInput = ({
stepFilter, stepFilter,
}: WorkflowStepFilterValueInputProps) => { }: WorkflowStepFilterValueInputProps) => {
@ -17,15 +47,76 @@ export const WorkflowStepFilterValueInput = ({
const { readonly } = useContext(WorkflowStepFilterContext); const { readonly } = useContext(WorkflowStepFilterContext);
const { upsertStepFilterSettings } = useUpsertStepFilterSettings(); const { upsertStepFilterSettings } = useUpsertStepFilterSettings();
const { workflowVersionId } = useWorkflowStepContextOrThrow();
const stepId = extractRawVariableNamePart({
rawVariableName: stepFilter.stepOutputKey,
part: 'stepId',
});
const stepsOutputSchema = useRecoilValue(
stepsOutputSchemaFamilySelector({
workflowVersionId,
stepIds: [stepId],
}),
);
const { variableType, fieldMetadataId } = searchVariableThroughOutputSchema({
stepOutputSchema: stepsOutputSchema?.[0],
rawVariableName: stepFilter.stepOutputKey,
isFullRecord: false,
});
const handleValueChange = (value: JsonValue) => {
const valueToUpsert = isString(value)
? value
: Array.isArray(value) || isObject(value)
? JSON.stringify(value)
: String(value);
const handleValueChange = (value: string) => {
upsertStepFilterSettings({ upsertStepFilterSettings({
stepFilterToUpsert: { stepFilterToUpsert: {
...stepFilter, ...stepFilter,
value, value: valueToUpsert,
}, },
}); });
}; };
const { getFieldMetadataItemById } = useGetFieldMetadataItemById();
const isDisabled = !stepFilter.operand;
const operandHasNoInput =
(stepFilter && !configurableViewFilterOperands.has(stepFilter.operand)) ??
true;
if (isDisabled || operandHasNoInput) {
return null;
}
if (isDefined(variableType) && isFilterableFieldMetadataType(variableType)) {
const selectedFieldMetadataItem = isDefined(fieldMetadataId)
? getFieldMetadataItemById(fieldMetadataId)
: undefined;
const field = {
type: variableType as FieldMetadataType,
label: '',
metadata: {
fieldName: selectedFieldMetadataItem?.name ?? '',
options: selectedFieldMetadataItem?.options ?? [],
} as FieldMetadata,
};
return (
<FormFieldInput
field={field}
defaultValue={stepFilter.value}
onChange={handleValueChange}
readonly={readonly}
VariablePicker={WorkflowVariablePicker}
placeholder={t`Enter value`}
/>
);
}
return ( return (
<FormTextFieldInput <FormTextFieldInput

View File

@ -38,7 +38,6 @@ export const FILTER_OPERANDS_MAP = {
], ],
DATE_TIME: [ DATE_TIME: [
ViewFilterOperand.Is, ViewFilterOperand.Is,
ViewFilterOperand.IsRelative,
ViewFilterOperand.IsInPast, ViewFilterOperand.IsInPast,
ViewFilterOperand.IsInFuture, ViewFilterOperand.IsInFuture,
ViewFilterOperand.IsToday, ViewFilterOperand.IsToday,
@ -48,7 +47,6 @@ export const FILTER_OPERANDS_MAP = {
], ],
DATE: [ DATE: [
ViewFilterOperand.Is, ViewFilterOperand.Is,
ViewFilterOperand.IsRelative,
ViewFilterOperand.IsInPast, ViewFilterOperand.IsInPast,
ViewFilterOperand.IsInFuture, ViewFilterOperand.IsInFuture,
ViewFilterOperand.IsToday, ViewFilterOperand.IsToday,

View File

@ -18,7 +18,7 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { WorkflowAdvancedFilterValueFormCompositeFieldInput } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterValueFormCompositeFieldInput'; import { WorkflowAdvancedFilterValueFormCompositeFieldInput } from '@/workflow/workflow-steps/workflow-actions/find-records-action/components/WorkflowAdvancedFilterValueFormCompositeFieldInput';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { isObject } from '@sniptt/guards'; import { isObject, isString } from '@sniptt/guards';
import { useContext } from 'react'; import { useContext } from 'react';
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -56,7 +56,7 @@ export const WorkflowAdvancedFilterValueFormInput = ({
useApplyObjectFilterDropdownFilterValue(); useApplyObjectFilterDropdownFilterValue();
const handleChange = (newValue: JsonValue) => { const handleChange = (newValue: JsonValue) => {
if (typeof newValue === 'string') { if (isString(newValue)) {
applyObjectFilterDropdownFilterValue(newValue); applyObjectFilterDropdownFilterValue(newValue);
} else if (Array.isArray(newValue) || isObject(newValue)) { } else if (Array.isArray(newValue) || isObject(newValue)) {
applyObjectFilterDropdownFilterValue(JSON.stringify(newValue)); applyObjectFilterDropdownFilterValue(JSON.stringify(newValue));

View File

@ -7,6 +7,7 @@ type Leaf = {
label?: string; label?: string;
description?: string; description?: string;
value: any; value: any;
fieldMetadataId?: string;
}; };
type Node = { type Node = {
@ -16,6 +17,7 @@ type Node = {
label?: string; label?: string;
value: OutputSchema; value: OutputSchema;
description?: string; description?: string;
fieldMetadataId?: string;
}; };
type Link = { type Link = {
@ -28,7 +30,11 @@ type Link = {
export type BaseOutputSchema = Record<string, Leaf | Node>; export type BaseOutputSchema = Record<string, Leaf | Node>;
export type RecordOutputSchema = { export type RecordOutputSchema = {
object: { nameSingular: string; fieldIdName: string } & Leaf; object: {
nameSingular: string;
fieldIdName: string;
objectMetadataId: string;
} & Leaf;
fields: BaseOutputSchema; fields: BaseOutputSchema;
_outputSchemaType: 'RECORD'; _outputSchemaType: 'RECORD';
}; };

View File

@ -12,6 +12,7 @@ describe('filterOutputSchema', () => {
fieldIdName: 'id', fieldIdName: 'id',
isLeaf: true, isLeaf: true,
value: 'Fake value', value: 'Fake value',
objectMetadataId: '123',
}, },
fields: {}, fields: {},
}; };
@ -35,6 +36,7 @@ describe('filterOutputSchema', () => {
fieldIdName: 'id', fieldIdName: 'id',
isLeaf: true, isLeaf: true,
value: 'Fake value', value: 'Fake value',
objectMetadataId: '123',
}, },
fields, fields,
}); });

View File

@ -16,6 +16,7 @@ const mockStep = {
label: 'Company', label: 'Company',
value: 'John', value: 'John',
isLeaf: true, isLeaf: true,
objectMetadataId: '123',
}, },
fields: { fields: {
name: { label: 'Name', value: 'Twenty', isLeaf: true }, name: { label: 'Name', value: 'Twenty', isLeaf: true },

View File

@ -16,6 +16,7 @@ const mockStep = {
label: 'Company', label: 'Company',
value: 'John', value: 'John',
isLeaf: true, isLeaf: true,
objectMetadataId: '123',
}, },
fields: { fields: {
name: { label: 'Name', value: 'Twenty', isLeaf: true }, name: { label: 'Name', value: 'Twenty', isLeaf: true },

View File

@ -19,6 +19,7 @@ describe('searchVariableThroughOutputSchema', () => {
label: 'Company', label: 'Company',
value: 'John', value: 'John',
isLeaf: true, isLeaf: true,
objectMetadataId: '123',
}, },
fields: { fields: {
name: { label: 'Name', value: 'Twenty', isLeaf: true }, name: { label: 'Name', value: 'Twenty', isLeaf: true },
@ -38,6 +39,7 @@ describe('searchVariableThroughOutputSchema', () => {
label: 'Person', label: 'Person',
value: 'Jane', value: 'Jane',
isLeaf: true, isLeaf: true,
objectMetadataId: '123',
}, },
fields: { fields: {
firstName: { label: 'First Name', value: 'Jane', isLeaf: true }, firstName: { label: 'First Name', value: 'Jane', isLeaf: true },
@ -270,6 +272,7 @@ describe('searchVariableThroughOutputSchema', () => {
isLeaf: true, isLeaf: true,
fieldIdName: 'properties.after.id', fieldIdName: 'properties.after.id',
nameSingular: 'company', nameSingular: 'company',
objectMetadataId: '123',
}, },
_outputSchemaType: 'RECORD', _outputSchemaType: 'RECORD',
}, },

View File

@ -43,6 +43,17 @@ const getVariableType = (key: string, outputSchema: OutputSchema): string => {
return outputSchema[key]?.type ?? 'unknown'; return outputSchema[key]?.type ?? 'unknown';
}; };
const getFieldMetadataId = (
key: string,
outputSchema: OutputSchema,
): string | undefined => {
if (isRecordOutputSchema(outputSchema)) {
return outputSchema.fields[key]?.fieldMetadataId;
}
return undefined;
};
const searchCurrentStepOutputSchema = ({ const searchCurrentStepOutputSchema = ({
stepOutputSchema, stepOutputSchema,
path, path,
@ -120,6 +131,10 @@ const searchCurrentStepOutputSchema = ({
isSelectedFieldInNextKey ? nextKey : selectedField, isSelectedFieldInNextKey ? nextKey : selectedField,
currentSubStep, currentSubStep,
), ),
fieldMetadataId: getFieldMetadataId(
isSelectedFieldInNextKey ? nextKey : selectedField,
currentSubStep,
),
}; };
}; };
@ -160,7 +175,7 @@ export const searchVariableThroughOutputSchema = ({
}; };
} }
const { variableLabel, variablePathLabel, variableType } = const { variableLabel, variablePathLabel, variableType, fieldMetadataId } =
searchCurrentStepOutputSchema({ searchCurrentStepOutputSchema({
stepOutputSchema, stepOutputSchema,
path, path,
@ -172,5 +187,6 @@ export const searchVariableThroughOutputSchema = ({
variableLabel, variableLabel,
variablePathLabel: `${variablePathLabel} > ${variableLabel}`, variablePathLabel: `${variablePathLabel} > ${variableLabel}`,
variableType, variableType,
fieldMetadataId,
}; };
}; };

View File

@ -28,9 +28,17 @@ type Link = {
export type BaseOutputSchema = Record<string, Leaf | Node>; export type BaseOutputSchema = Record<string, Leaf | Node>;
export type FieldOutputSchema =
| ((Leaf | Node) & { fieldMetadataId?: string })
| RecordOutputSchema;
export type RecordOutputSchema = { export type RecordOutputSchema = {
object: { nameSingular: string; fieldIdName: string } & Leaf; object: {
fields: BaseOutputSchema; nameSingular: string;
fieldIdName: string;
objectMetadataId: string;
} & Leaf;
fields: Record<string, FieldOutputSchema>;
_outputSchemaType: 'RECORD'; _outputSchemaType: 'RECORD';
}; };

View File

@ -55,6 +55,7 @@ describe('generateFakeField', () => {
const result = generateFakeField({ const result = generateFakeField({
type: FieldMetadataType.TEXT, type: FieldMetadataType.TEXT,
label: 'Text Field', label: 'Text Field',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(result).toEqual({ expect(result).toEqual({
@ -63,6 +64,7 @@ describe('generateFakeField', () => {
icon: undefined, icon: undefined,
label: 'Text Field', label: 'Text Field',
value: 'Fake Text', value: 'Fake Text',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(generateFakeValueSpy).toHaveBeenCalledWith( expect(generateFakeValueSpy).toHaveBeenCalledWith(
@ -76,6 +78,7 @@ describe('generateFakeField', () => {
type: FieldMetadataType.TEXT, type: FieldMetadataType.TEXT,
label: 'Text Field', label: 'Text Field',
value: 'Test value', value: 'Test value',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(result).toEqual({ expect(result).toEqual({
@ -84,6 +87,7 @@ describe('generateFakeField', () => {
icon: undefined, icon: undefined,
label: 'Text Field', label: 'Text Field',
value: 'Test value', value: 'Test value',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(generateFakeValueSpy).not.toHaveBeenCalled(); expect(generateFakeValueSpy).not.toHaveBeenCalled();
@ -96,6 +100,7 @@ describe('generateFakeField', () => {
type: FieldMetadataType.NUMBER, type: FieldMetadataType.NUMBER,
label: 'Number Field', label: 'Number Field',
icon: 'IconNumber', icon: 'IconNumber',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(result).toEqual({ expect(result).toEqual({
@ -104,6 +109,7 @@ describe('generateFakeField', () => {
icon: 'IconNumber', icon: 'IconNumber',
label: 'Number Field', label: 'Number Field',
value: 42, value: 42,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
}); });
@ -115,6 +121,7 @@ describe('generateFakeField', () => {
const result = generateFakeField({ const result = generateFakeField({
type: FieldMetadataType.DATE, type: FieldMetadataType.DATE,
label: 'Date Field', label: 'Date Field',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(result).toEqual({ expect(result).toEqual({
@ -123,6 +130,7 @@ describe('generateFakeField', () => {
icon: undefined, icon: undefined,
label: 'Date Field', label: 'Date Field',
value: fakeDate, value: fakeDate,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
}); });
}); });
@ -140,6 +148,7 @@ describe('generateFakeField', () => {
const result = generateFakeField({ const result = generateFakeField({
type: FieldMetadataType.LINKS, type: FieldMetadataType.LINKS,
label: 'Links Field', label: 'Links Field',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(result).toEqual({ expect(result).toEqual({
@ -161,6 +170,7 @@ describe('generateFakeField', () => {
value: 'https://example.com', value: 'https://example.com',
}, },
}, },
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(generateFakeValueSpy).toHaveBeenCalledTimes(2); expect(generateFakeValueSpy).toHaveBeenCalledTimes(2);
@ -179,6 +189,7 @@ describe('generateFakeField', () => {
type: FieldMetadataType.CURRENCY, type: FieldMetadataType.CURRENCY,
label: 'Currency Field', label: 'Currency Field',
icon: 'IconCurrency', icon: 'IconCurrency',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(result).toEqual({ expect(result).toEqual({
@ -200,6 +211,7 @@ describe('generateFakeField', () => {
value: 'USD', value: 'USD',
}, },
}, },
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
}); });
}); });
@ -213,6 +225,7 @@ describe('generateFakeField', () => {
const result = generateFakeField({ const result = generateFakeField({
type: unknownType, type: unknownType,
label: 'Unknown Field', label: 'Unknown Field',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(result).toEqual({ expect(result).toEqual({
@ -221,6 +234,7 @@ describe('generateFakeField', () => {
icon: undefined, icon: undefined,
label: 'Unknown Field', label: 'Unknown Field',
value: 'Unknown Value', value: 'Unknown Value',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
}); });
@ -230,6 +244,7 @@ describe('generateFakeField', () => {
const result = generateFakeField({ const result = generateFakeField({
type: FieldMetadataType.BOOLEAN, type: FieldMetadataType.BOOLEAN,
label: '', label: '',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
expect(result).toEqual({ expect(result).toEqual({
@ -238,6 +253,7 @@ describe('generateFakeField', () => {
icon: undefined, icon: undefined,
label: '', label: '',
value: 'Fake Boolean', value: 'Fake Boolean',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}); });
}); });
}); });

View File

@ -59,6 +59,7 @@ describe('generateFakeFormResponse', () => {
expect(result).toMatchInlineSnapshot(` expect(result).toMatchInlineSnapshot(`
{ {
"age": { "age": {
"fieldMetadataId": undefined,
"icon": undefined, "icon": undefined,
"isLeaf": true, "isLeaf": true,
"label": "Age", "label": "Age",
@ -72,6 +73,7 @@ describe('generateFakeFormResponse', () => {
"_outputSchemaType": "RECORD", "_outputSchemaType": "RECORD",
"fields": { "fields": {
"domainName": { "domainName": {
"fieldMetadataId": "domainNameFieldMetadataId",
"icon": "test-field-icon", "icon": "test-field-icon",
"isLeaf": false, "isLeaf": false,
"label": "Domain Name", "label": "Domain Name",
@ -98,6 +100,7 @@ describe('generateFakeFormResponse', () => {
}, },
}, },
"name": { "name": {
"fieldMetadataId": "nameFieldMetadataId",
"icon": "test-field-icon", "icon": "test-field-icon",
"isLeaf": true, "isLeaf": true,
"label": "Name", "label": "Name",
@ -111,11 +114,13 @@ describe('generateFakeFormResponse', () => {
"isLeaf": true, "isLeaf": true,
"label": "Company", "label": "Company",
"nameSingular": "company", "nameSingular": "company",
"objectMetadataId": "20202020-c03c-45d6-a4b0-04afe1357c5c",
"value": "A company", "value": "A company",
}, },
}, },
}, },
"date": { "date": {
"fieldMetadataId": undefined,
"icon": undefined, "icon": undefined,
"isLeaf": true, "isLeaf": true,
"label": "Date", "label": "Date",
@ -123,6 +128,7 @@ describe('generateFakeFormResponse', () => {
"value": "mm/dd/yyyy", "value": "mm/dd/yyyy",
}, },
"name": { "name": {
"fieldMetadataId": undefined,
"icon": undefined, "icon": undefined,
"isLeaf": true, "isLeaf": true,
"label": "Name", "label": "Name",

View File

@ -1,7 +1,7 @@
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
import { generateFakeObjectRecordEvent } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event'; import { generateFakeObjectRecordEvent } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event';
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields'; import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
jest.mock( jest.mock(
'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields', 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields',
@ -13,8 +13,16 @@ describe('generateFakeObjectRecordEvent', () => {
}); });
const mockFields = { const mockFields = {
field1: { type: 'TEXT', value: 'test' }, field1: {
field2: { type: 'NUMBER', value: 123 }, type: 'TEXT',
value: 'test',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
field2: {
type: 'NUMBER',
value: 123,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174001',
},
}; };
const companyMockObjectMetadataItem = const companyMockObjectMetadataItem =
@ -55,10 +63,19 @@ describe('generateFakeObjectRecordEvent', () => {
value: 'A company', value: 'A company',
nameSingular: 'company', nameSingular: 'company',
fieldIdName: 'properties.after.id', fieldIdName: 'properties.after.id',
objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c',
}, },
fields: { fields: {
'properties.after.field1': { type: 'TEXT', value: 'test' }, 'properties.after.field1': {
'properties.after.field2': { type: 'NUMBER', value: 123 }, type: 'TEXT',
value: 'test',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
'properties.after.field2': {
type: 'NUMBER',
value: 123,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174001',
},
}, },
_outputSchemaType: 'RECORD', _outputSchemaType: 'RECORD',
}); });
@ -78,10 +95,19 @@ describe('generateFakeObjectRecordEvent', () => {
value: 'A company', value: 'A company',
nameSingular: 'company', nameSingular: 'company',
fieldIdName: 'properties.after.id', fieldIdName: 'properties.after.id',
objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c',
}, },
fields: { fields: {
'properties.after.field1': { type: 'TEXT', value: 'test' }, 'properties.after.field1': {
'properties.after.field2': { type: 'NUMBER', value: 123 }, type: 'TEXT',
value: 'test',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
'properties.after.field2': {
type: 'NUMBER',
value: 123,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174001',
},
}, },
_outputSchemaType: 'RECORD', _outputSchemaType: 'RECORD',
}); });
@ -101,10 +127,19 @@ describe('generateFakeObjectRecordEvent', () => {
value: 'A company', value: 'A company',
nameSingular: 'company', nameSingular: 'company',
fieldIdName: 'properties.before.id', fieldIdName: 'properties.before.id',
objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c',
}, },
fields: { fields: {
'properties.before.field1': { type: 'TEXT', value: 'test' }, 'properties.before.field1': {
'properties.before.field2': { type: 'NUMBER', value: 123 }, type: 'TEXT',
value: 'test',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
'properties.before.field2': {
type: 'NUMBER',
value: 123,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174001',
},
}, },
_outputSchemaType: 'RECORD', _outputSchemaType: 'RECORD',
}); });
@ -124,10 +159,19 @@ describe('generateFakeObjectRecordEvent', () => {
value: 'A company', value: 'A company',
nameSingular: 'company', nameSingular: 'company',
fieldIdName: 'properties.before.id', fieldIdName: 'properties.before.id',
objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c',
}, },
fields: { fields: {
'properties.before.field1': { type: 'TEXT', value: 'test' }, 'properties.before.field1': {
'properties.before.field2': { type: 'NUMBER', value: 123 }, type: 'TEXT',
value: 'test',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
'properties.before.field2': {
type: 'NUMBER',
value: 123,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174001',
},
}, },
_outputSchemaType: 'RECORD', _outputSchemaType: 'RECORD',
}); });

View File

@ -43,6 +43,7 @@ describe('generateFakeObjectRecord', () => {
value: 'A company', value: 'A company',
nameSingular: 'company', nameSingular: 'company',
fieldIdName: 'id', fieldIdName: 'id',
objectMetadataId: '20202020-c03c-45d6-a4b0-04afe1357c5c',
}, },
fields: { fields: {
field1: { type: 'TEXT', value: 'test' }, field1: { type: 'TEXT', value: 'test' },

View File

@ -13,12 +13,14 @@ export const generateFakeField = ({
label, label,
icon, icon,
value, value,
fieldMetadataId,
}: { }: {
type: FieldMetadataType; type: FieldMetadataType;
label: string; label: string;
fieldMetadataId?: string;
icon?: string; icon?: string;
value?: string; value?: string;
}): Leaf | Node => { }): (Leaf | Node) & { fieldMetadataId?: string } => {
const compositeType = compositeTypeDefinitions.get(type); const compositeType = compositeTypeDefinitions.get(type);
if (compositeType) { if (compositeType) {
@ -27,6 +29,7 @@ export const generateFakeField = ({
type: type, type: type,
icon: icon, icon: icon,
label: label, label: label,
fieldMetadataId,
value: compositeType.properties.reduce((acc, property) => { value: compositeType.properties.reduce((acc, property) => {
// @ts-expect-error legacy noImplicitAny // @ts-expect-error legacy noImplicitAny
acc[property.name] = { acc[property.name] = {
@ -47,5 +50,6 @@ export const generateFakeField = ({
icon: icon, icon: icon,
label: label, label: label,
value: value || generateFakeValue(type, 'FieldMetadataType'), value: value || generateFakeValue(type, 'FieldMetadataType'),
fieldMetadataId,
}; };
}; };

View File

@ -60,7 +60,10 @@ export const generateFakeFormResponse = async ({
}), }),
); );
return result.filter(isDefined).reduce((acc, curr) => { return result.filter(isDefined).reduce(
return { ...acc, ...curr }; (acc, curr) => {
}, {}); return { ...acc, ...curr };
},
{} as Record<string, Leaf | Node>,
);
}; };

View File

@ -1,7 +1,7 @@
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import { import {
BaseOutputSchema, FieldOutputSchema,
RecordOutputSchema, RecordOutputSchema,
} from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type'; } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields'; import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
@ -20,7 +20,7 @@ const generateFakeObjectRecordEventWithPrefix = ({
return acc; return acc;
}, },
{} as BaseOutputSchema, {} as Record<string, FieldOutputSchema>,
); );
return { return {
@ -33,6 +33,7 @@ const generateFakeObjectRecordEventWithPrefix = ({
nameSingular: nameSingular:
objectMetadataInfo.objectMetadataItemWithFieldsMaps.nameSingular, objectMetadataInfo.objectMetadataItemWithFieldsMaps.nameSingular,
fieldIdName: `${prefix}.id`, fieldIdName: `${prefix}.id`,
objectMetadataId: objectMetadataInfo.objectMetadataItemWithFieldsMaps.id,
}, },
fields: prefixedRecordFields, fields: prefixedRecordFields,
_outputSchemaType: 'RECORD', _outputSchemaType: 'RECORD',

View File

@ -19,6 +19,7 @@ export const generateFakeObjectRecord = ({
nameSingular: nameSingular:
objectMetadataInfo.objectMetadataItemWithFieldsMaps.nameSingular, objectMetadataInfo.objectMetadataItemWithFieldsMaps.nameSingular,
fieldIdName: 'id', fieldIdName: 'id',
objectMetadataId: objectMetadataInfo.objectMetadataItemWithFieldsMaps.id,
}, },
fields: generateObjectRecordFields({ fields: generateObjectRecordFields({
objectMetadataInfo, objectMetadataInfo,

View File

@ -2,7 +2,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import { BaseOutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type'; import { FieldOutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field'; import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record'; import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record';
import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value'; import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value';
@ -15,11 +15,11 @@ export const generateObjectRecordFields = ({
}: { }: {
objectMetadataInfo: ObjectMetadataInfo; objectMetadataInfo: ObjectMetadataInfo;
depth?: number; depth?: number;
}): BaseOutputSchema => { }): Record<string, FieldOutputSchema> => {
const objectMetadata = objectMetadataInfo.objectMetadataItemWithFieldsMaps; const objectMetadata = objectMetadataInfo.objectMetadataItemWithFieldsMaps;
return Object.values(objectMetadata.fieldsById).reduce( return Object.values(objectMetadata.fieldsById).reduce(
(acc: BaseOutputSchema, field) => { (acc: Record<string, FieldOutputSchema>, field) => {
if (!shouldGenerateFieldFakeValue(field)) { if (!shouldGenerateFieldFakeValue(field)) {
return acc; return acc;
} }
@ -29,6 +29,7 @@ export const generateObjectRecordFields = ({
type: field.type, type: field.type,
label: field.label, label: field.label,
icon: field.icon ?? undefined, icon: field.icon ?? undefined,
fieldMetadataId: field.id,
}); });
return acc; return acc;
@ -51,6 +52,7 @@ export const generateObjectRecordFields = ({
isLeaf: false, isLeaf: false,
icon: field.icon ?? undefined, icon: field.icon ?? undefined,
label: field.label, label: field.label,
fieldMetadataId: field.id,
value: generateFakeObjectRecord({ value: generateFakeObjectRecord({
objectMetadataInfo: { objectMetadataInfo: {
objectMetadataItemWithFieldsMaps: relationTargetObjectMetadata, objectMetadataItemWithFieldsMaps: relationTargetObjectMetadata,
@ -63,6 +65,6 @@ export const generateObjectRecordFields = ({
return acc; return acc;
}, },
{} as BaseOutputSchema, {} as Record<string, FieldOutputSchema>,
); );
}; };

View File

@ -68,30 +68,11 @@ describe('evaluateFilterConditions', () => {
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should handle null checks', () => { it('should return true when values are equal but different types', () => {
const filter1 = createFilter(ViewFilterOperand.Is, null, 'null'); const filter = createFilter(ViewFilterOperand.Is, '123', 123);
const filter2 = createFilter(ViewFilterOperand.Is, undefined, 'NULL'); const result = evaluateFilterConditions({ filters: [filter] });
const filter3 = createFilter(ViewFilterOperand.Is, 'value', 'null');
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); expect(result).toBe(true);
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false);
});
it('should handle not null checks', () => {
const filter1 = createFilter(ViewFilterOperand.Is, 'value', 'not null');
const filter2 = createFilter(ViewFilterOperand.Is, 'value', 'NOT NULL');
const filter3 = createFilter(ViewFilterOperand.Is, null, 'not null');
const filter4 = createFilter(
ViewFilterOperand.Is,
undefined,
'not null',
);
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
expect(evaluateFilterConditions({ filters: [filter3] })).toBe(false);
expect(evaluateFilterConditions({ filters: [filter4] })).toBe(false);
}); });
}); });
@ -182,12 +163,12 @@ describe('evaluateFilterConditions', () => {
const filter1 = createFilter( const filter1 = createFilter(
ViewFilterOperand.Contains, ViewFilterOperand.Contains,
['apple', 'banana', 'cherry'], ['apple', 'banana', 'cherry'],
'apple', ['apple'],
); );
const filter2 = createFilter( const filter2 = createFilter(
ViewFilterOperand.Contains, ViewFilterOperand.Contains,
['apple', 'banana', 'cherry'], ['apple', 'banana', 'cherry'],
'grape', ['grape'],
); );
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true); expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
@ -198,12 +179,12 @@ describe('evaluateFilterConditions', () => {
const filter1 = createFilter( const filter1 = createFilter(
ViewFilterOperand.DoesNotContain, ViewFilterOperand.DoesNotContain,
['apple', 'banana', 'cherry'], ['apple', 'banana', 'cherry'],
'apple', ['apple'],
); );
const filter2 = createFilter( const filter2 = createFilter(
ViewFilterOperand.DoesNotContain, ViewFilterOperand.DoesNotContain,
['apple', 'banana', 'cherry'], ['apple', 'banana', 'cherry'],
'grape', ['grape'],
); );
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(false); expect(evaluateFilterConditions({ filters: [filter1] })).toBe(false);

View File

@ -16,17 +16,19 @@ function evaluateFilter(filter: ResolvedFilter): boolean {
switch (filter.operand) { switch (filter.operand) {
case ViewFilterOperand.Is: case ViewFilterOperand.Is:
if (String(rightValue).toLowerCase() === 'null') { switch (typeof leftValue) {
return leftValue === null || leftValue === undefined; case 'string':
return (
String(leftValue).toLowerCase() === String(rightValue).toLowerCase()
);
case 'boolean':
return Boolean(leftValue) === Boolean(rightValue);
default:
return leftValue === rightValue;
} }
if (String(rightValue).toLowerCase() === 'not null') {
return leftValue !== null && leftValue !== undefined;
}
return leftValue == rightValue;
case ViewFilterOperand.IsNot: case ViewFilterOperand.IsNot:
return leftValue != rightValue; return String(leftValue) !== String(rightValue);
case ViewFilterOperand.GreaterThanOrEqual: case ViewFilterOperand.GreaterThanOrEqual:
return Number(leftValue) >= Number(rightValue); return Number(leftValue) >= Number(rightValue);
@ -36,14 +38,38 @@ function evaluateFilter(filter: ResolvedFilter): boolean {
case ViewFilterOperand.Contains: case ViewFilterOperand.Contains:
if (Array.isArray(leftValue)) { if (Array.isArray(leftValue)) {
return leftValue.includes(rightValue); try {
const parsedRightValue = Array.isArray(rightValue)
? rightValue
: JSON.parse(rightValue as string);
if (Array.isArray(parsedRightValue)) {
return parsedRightValue.every((item) => leftValue.includes(item));
} else {
return leftValue.includes(parsedRightValue);
}
} catch (error) {
return leftValue.includes(rightValue);
}
} }
return String(leftValue).includes(String(rightValue)); return String(leftValue).includes(String(rightValue));
case ViewFilterOperand.DoesNotContain: case ViewFilterOperand.DoesNotContain:
if (Array.isArray(leftValue)) { if (Array.isArray(leftValue)) {
return !leftValue.includes(rightValue); try {
const parsedRightValue = Array.isArray(rightValue)
? rightValue
: JSON.parse(rightValue as string);
if (Array.isArray(parsedRightValue)) {
return !parsedRightValue.every((item) => leftValue.includes(item));
} else {
return !leftValue.includes(parsedRightValue);
}
} catch (error) {
return !leftValue.includes(rightValue);
}
} }
return !String(leftValue).includes(String(rightValue)); return !String(leftValue).includes(String(rightValue));
@ -67,17 +93,43 @@ function evaluateFilter(filter: ResolvedFilter): boolean {
case ViewFilterOperand.IsNotNull: case ViewFilterOperand.IsNotNull:
return leftValue !== null && leftValue !== undefined; return leftValue !== null && leftValue !== undefined;
case ViewFilterOperand.IsRelative:
case ViewFilterOperand.IsInPast: case ViewFilterOperand.IsInPast:
if (typeof leftValue === 'string') {
return Date.now() - new Date(leftValue).getTime() > 0;
}
return false;
case ViewFilterOperand.IsInFuture: case ViewFilterOperand.IsInFuture:
if (typeof leftValue === 'string') {
return Date.now() - new Date(leftValue).getTime() < 0;
}
return false;
case ViewFilterOperand.IsToday: case ViewFilterOperand.IsToday:
if (typeof leftValue === 'string') {
return new Date(leftValue).toDateString() === new Date().toDateString();
}
return false;
case ViewFilterOperand.IsBefore: case ViewFilterOperand.IsBefore:
if (typeof leftValue === 'string' && typeof rightValue === 'string') {
return new Date(leftValue).getTime() < new Date(rightValue).getTime();
}
return false;
case ViewFilterOperand.IsAfter: case ViewFilterOperand.IsAfter:
// Date/time operands - for now, return false as placeholder if (typeof leftValue === 'string' && typeof rightValue === 'string') {
// These would need proper date logic implementation return new Date(leftValue).getTime() > new Date(rightValue).getTime();
}
return false; return false;
case ViewFilterOperand.VectorSearch: case ViewFilterOperand.VectorSearch:
case ViewFilterOperand.IsRelative:
return false; return false;
default: default:

View File

@ -40,6 +40,7 @@ const settings: WorkflowFormActionSettings = {
label: 'Id', label: 'Id',
value: '123e4567-e89b-12d3-a456-426614174000', value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true, isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}, },
}, },
object: { object: {
@ -49,6 +50,7 @@ const settings: WorkflowFormActionSettings = {
isLeaf: true, isLeaf: true,
fieldIdName: 'id', fieldIdName: 'id',
nameSingular: 'company', nameSingular: 'company',
objectMetadataId: '123e4567-e89b-12d3-a456-426614174000',
}, },
_outputSchemaType: 'RECORD', _outputSchemaType: 'RECORD',
}, },

View File

@ -16,8 +16,8 @@ export type StepFilter = {
id: string; id: string;
type: string; type: string;
label: string; label: string;
value: string;
operand: ViewFilterOperand; operand: ViewFilterOperand;
value: string;
displayValue: string; displayValue: string;
stepFilterGroupId: string; stepFilterGroupId: string;
stepOutputKey: string; stepOutputKey: string;