diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx index fd082749c..53763ec73 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterFieldSelect.tsx @@ -1,3 +1,4 @@ +import { useGetFieldMetadataItemById } from '@/object-metadata/hooks/useGetFieldMetadataItemById'; import { SelectControl } from '@/ui/input/components/SelectControl'; import { useWorkflowStepContextOrThrow } from '@/workflow/states/context/WorkflowStepContext'; import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/stepsOutputSchemaFamilySelector'; @@ -38,6 +39,8 @@ export const WorkflowStepFilterFieldSelect = ({ }), ); + const { getFieldMetadataItemById } = useGetFieldMetadataItemById(); + const handleChange = useRecoilCallback( ({ snapshot }) => (variableName: string) => { @@ -54,24 +57,39 @@ export const WorkflowStepFilterFieldSelect = ({ ) .getValue(); - const { variableLabel, variableType } = - searchVariableThroughOutputSchema({ - stepOutputSchema: currentStepOutputSchema?.[0], - rawVariableName: variableName, - isFullRecord: false, - }); + const { + variableLabel, + variableType, + fieldMetadataId, + compositeFieldSubFieldName, + } = searchVariableThroughOutputSchema({ + stepOutputSchema: currentStepOutputSchema?.[0], + rawVariableName: variableName, + isFullRecord: false, + }); + + const filterType = isDefined(fieldMetadataId) + ? getFieldMetadataItemById(fieldMetadataId).type + : variableType; upsertStepFilterSettings({ stepFilterToUpsert: { ...stepFilter, stepOutputKey: variableName, displayValue: variableLabel ?? '', - type: variableType ?? 'unknown', + type: filterType ?? 'unknown', value: '', + fieldMetadataId, + compositeFieldSubFieldName, }, }); }, - [upsertStepFilterSettings, stepFilter, workflowVersionId], + [ + upsertStepFilterSettings, + stepFilter, + workflowVersionId, + getFieldMetadataItemById, + ], ); if (!isDefined(stepId)) { diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueCompositeInput.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueCompositeInput.tsx new file mode 100644 index 000000000..26ad2913e --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueCompositeInput.tsx @@ -0,0 +1,84 @@ +import { FormCountryMultiSelectInput } from '@/object-record/record-field/form-types/components/FormCountryMultiSelectInput'; +import { FormMultiSelectFieldInput } from '@/object-record/record-field/form-types/components/FormMultiSelectFieldInput'; +import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput'; +import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput'; +import { CURRENCIES } from '@/settings/data-model/constants/Currencies'; +import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext'; +import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; +import { useContext } from 'react'; +import { StepFilter } from 'twenty-shared/types'; +import { JsonValue } from 'type-fest'; + +export const WorkflowStepFilterValueCompositeInput = ({ + stepFilter, + onChange, +}: { + stepFilter: StepFilter; + onChange: (newValue: JsonValue) => void; +}) => { + const { readonly } = useContext(WorkflowStepFilterContext); + const { type: filterType, compositeFieldSubFieldName: subFieldName } = + stepFilter; + + return ( + <> + {filterType === 'ADDRESS' ? ( + subFieldName === 'addressCountry' ? ( + + ) : ( + + ) + ) : filterType === 'CURRENCY' ? ( + subFieldName === 'currencyCode' ? ( + + ) : subFieldName === 'amountMicros' ? ( + + ) : null + ) : filterType === 'PHONES' ? ( + subFieldName === 'primaryPhoneNumber' ? ( + + ) : ( + + ) + ) : ( + + )} + + ); +}; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueInput.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueInput.tsx index e05359fff..a309fde68 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueInput.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueInput.tsx @@ -3,17 +3,13 @@ import { configurableViewFilterOperands } from '@/object-record/object-filter-dr import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput'; 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 { WorkflowStepFilterValueCompositeInput } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueCompositeInput'; 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 { 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 { isObject, isString } from '@sniptt/guards'; import { useContext } from 'react'; -import { useRecoilValue } from 'recoil'; import { FieldMetadataType, StepFilter } from 'twenty-shared/src/types'; import { isDefined } from 'twenty-shared/utils'; import { JsonValue } from 'type-fest'; @@ -22,6 +18,14 @@ type WorkflowStepFilterValueInputProps = { stepFilter: StepFilter; }; +const COMPOSITE_FIELD_METADATA_TYPES = [ + FieldMetadataType.ADDRESS, + FieldMetadataType.PHONES, + FieldMetadataType.EMAILS, + FieldMetadataType.LINKS, + FieldMetadataType.CURRENCY, +]; + const isFilterableFieldMetadataType = ( type: string, ): type is FieldMetadataType => { @@ -37,6 +41,8 @@ const isFilterableFieldMetadataType = ( FieldMetadataType.RAW_JSON, FieldMetadataType.RICH_TEXT_V2, FieldMetadataType.ARRAY, + FieldMetadataType.UUID, + ...COMPOSITE_FIELD_METADATA_TYPES, ].includes(type as FieldMetadataType); }; @@ -47,24 +53,6 @@ export const WorkflowStepFilterValueInput = ({ const { readonly } = useContext(WorkflowStepFilterContext); 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) @@ -92,23 +80,23 @@ export const WorkflowStepFilterValueInput = ({ return null; } - if (isDefined(variableType) && isFilterableFieldMetadataType(variableType)) { - const selectedFieldMetadataItem = isDefined(fieldMetadataId) - ? getFieldMetadataItemById(fieldMetadataId) - : undefined; + const { + fieldMetadataId, + type: variableType, + compositeFieldSubFieldName, + } = stepFilter; - const field = { - type: variableType as FieldMetadataType, - label: '', - metadata: { - fieldName: selectedFieldMetadataItem?.name ?? '', - options: selectedFieldMetadataItem?.options ?? [], - } as FieldMetadata, - }; + const selectedFieldMetadataItem = isDefined(fieldMetadataId) + ? getFieldMetadataItemById(fieldMetadataId) + : undefined; + if ( + !isDefined(variableType) || + !isFilterableFieldMetadataType(variableType) || + !isDefined(selectedFieldMetadataItem) + ) { return ( - + ); + } + + const field = { + type: variableType as FieldMetadataType, + label: '', + metadata: { + fieldName: selectedFieldMetadataItem?.name ?? '', + options: selectedFieldMetadataItem?.options ?? [], + } as FieldMetadata, + }; + return ( - { if (!isRecordOutputSchema(outputSchema)) { return; @@ -54,6 +62,17 @@ const getFieldMetadataId = ( return undefined; }; +const isCompositeSubField = ( + key: string, + outputSchema: OutputSchema, +): boolean => { + if (isBaseOutputSchema(outputSchema) && outputSchema[key]?.isLeaf) { + return outputSchema[key]?.isCompositeSubField ?? false; + } + + return false; +}; + const searchCurrentStepOutputSchema = ({ stepOutputSchema, path, @@ -70,6 +89,7 @@ const searchCurrentStepOutputSchema = ({ let nextKey = path[nextKeyIndex]; let variablePathLabel = stepOutputSchema.name; let isSelectedFieldInNextKey = false; + let parentFieldMetadataId: string | undefined; const handleFieldNotFound = () => { if (nextKeyIndex + 1 < path.length) { @@ -94,6 +114,7 @@ const searchCurrentStepOutputSchema = ({ currentSubStep = currentField.value; nextKey = path[nextKeyIndex + 1]; variablePathLabel = `${variablePathLabel} > ${currentField.label}`; + parentFieldMetadataId = currentField.fieldMetadataId; } else { handleFieldNotFound(); } @@ -103,6 +124,7 @@ const searchCurrentStepOutputSchema = ({ currentSubStep = currentField.value; nextKey = path[nextKeyIndex + 1]; variablePathLabel = `${variablePathLabel} > ${currentField.label}`; + parentFieldMetadataId = currentField.fieldMetadataId; } else { handleFieldNotFound(); } @@ -118,23 +140,24 @@ const searchCurrentStepOutputSchema = ({ }; } + const variableName = isSelectedFieldInNextKey ? nextKey : selectedField; + const variableLabel = + isFullRecord && isRecordOutputSchema(currentSubStep) + ? getDisplayedSubStepObjectLabel(currentSubStep) + : getDisplayedSubStepFieldLabel(variableName, currentSubStep); + return { - variableLabel: - isFullRecord && isRecordOutputSchema(currentSubStep) - ? getDisplayedSubStepObjectLabel(currentSubStep) - : getDisplayedSubStepFieldLabel( - isSelectedFieldInNextKey ? nextKey : selectedField, - currentSubStep, - ), - variablePathLabel, - variableType: getVariableType( - isSelectedFieldInNextKey ? nextKey : selectedField, + variableLabel, + variablePathLabel: `${variablePathLabel} > ${variableLabel}`, + variableType: getVariableType(variableName, currentSubStep), + fieldMetadataId: + getFieldMetadataId(variableName, currentSubStep) ?? parentFieldMetadataId, + compositeFieldSubFieldName: isCompositeSubField( + variableName, currentSubStep, - ), - fieldMetadataId: getFieldMetadataId( - isSelectedFieldInNextKey ? nextKey : selectedField, - currentSubStep, - ), + ) + ? variableName + : undefined, }; }; @@ -146,7 +169,7 @@ export const searchVariableThroughOutputSchema = ({ stepOutputSchema: StepOutputSchema; rawVariableName: string; isFullRecord?: boolean; -}) => { +}): VariableInfo => { if (!isDefined(stepOutputSchema)) { return { variableLabel: undefined, @@ -175,18 +198,10 @@ export const searchVariableThroughOutputSchema = ({ }; } - const { variableLabel, variablePathLabel, variableType, fieldMetadataId } = - searchCurrentStepOutputSchema({ - stepOutputSchema, - path, - isFullRecord, - selectedField, - }); - - return { - variableLabel, - variablePathLabel: `${variablePathLabel} > ${variableLabel}`, - variableType, - fieldMetadataId, - }; + return searchCurrentStepOutputSchema({ + stepOutputSchema, + path, + isFullRecord, + selectedField, + }); }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type.ts index ba5a9a1a7..a52d9db7e 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type.ts @@ -8,6 +8,7 @@ export type Leaf = { description?: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any; + isCompositeSubField?: boolean; }; export type Node = { @@ -29,7 +30,9 @@ type Link = { export type BaseOutputSchema = Record; export type FieldOutputSchema = - | ((Leaf | Node) & { fieldMetadataId?: string }) + | ((Leaf | Node) & { + fieldMetadataId?: string; + }) | RecordOutputSchema; export type RecordOutputSchema = { diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-field.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-field.spec.ts index d3ed8dfe4..c153b21f0 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-field.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-field.spec.ts @@ -158,12 +158,16 @@ describe('generateFakeField', () => { type: FieldMetadataType.LINKS, value: { label: { + fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', + isCompositeSubField: true, isLeaf: true, type: FieldMetadataType.TEXT, label: 'Label', value: 'Fake Label', }, url: { + fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', + isCompositeSubField: true, isLeaf: true, type: FieldMetadataType.TEXT, label: 'Url', @@ -199,12 +203,16 @@ describe('generateFakeField', () => { type: FieldMetadataType.CURRENCY, value: { amount: { + fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', + isCompositeSubField: true, isLeaf: true, type: FieldMetadataType.NUMBER, label: 'Amount', value: 100, }, currencyCode: { + fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000', + isCompositeSubField: true, isLeaf: true, type: FieldMetadataType.TEXT, label: 'Currency Code', diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-form-response.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-form-response.spec.ts index 47462cd05..f49fed0b9 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-form-response.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-form-response.spec.ts @@ -80,18 +80,24 @@ describe('generateFakeFormResponse', () => { "type": "LINKS", "value": { "primaryLinkLabel": { + "fieldMetadataId": "domainNameFieldMetadataId", + "isCompositeSubField": true, "isLeaf": true, "label": "Primary Link Label", "type": "TEXT", "value": "My text", }, "primaryLinkUrl": { + "fieldMetadataId": "domainNameFieldMetadataId", + "isCompositeSubField": true, "isLeaf": true, "label": "Primary Link Url", "type": "TEXT", "value": "My text", }, "secondaryLinks": { + "fieldMetadataId": "domainNameFieldMetadataId", + "isCompositeSubField": true, "isLeaf": true, "label": "Secondary Links", "type": "RAW_JSON", diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field.ts index 480808288..d3a3f2a3c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field.ts @@ -37,6 +37,8 @@ export const generateFakeField = ({ type: property.type, label: camelToTitleCase(property.name), value: value || generateFakeValue(property.type, 'FieldMetadataType'), + fieldMetadataId, + isCompositeSubField: true, }; return acc; diff --git a/packages/twenty-shared/src/types/StepFilters.ts b/packages/twenty-shared/src/types/StepFilters.ts index 6011ac68e..20d534f23 100644 --- a/packages/twenty-shared/src/types/StepFilters.ts +++ b/packages/twenty-shared/src/types/StepFilters.ts @@ -16,10 +16,12 @@ export type StepFilter = { id: string; type: string; label: string; + stepOutputKey: string; operand: ViewFilterOperand; value: string; displayValue: string; stepFilterGroupId: string; - stepOutputKey: string; positionInStepFilterGroup?: number; + fieldMetadataId?: string; + compositeFieldSubFieldName?: string; };