From 0188b662807ced16e1831db0b0e07f5baf130e5f Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Fri, 6 Jun 2025 14:29:53 +0200 Subject: [PATCH] Add filters to search record action (#12481) First PR to add filters to send records. Lot of work left, but I want to split. I mainly want to validate the architecture there. https://github.com/user-attachments/assets/63375a75-ba88-49df-8c12-5e3e58de5342 TODO in next PRs: - fix design - make filters reliable. Some composite fields are not implemented and some fields like datetime do not work well - improve typing --- ...dvancedFilterRecordFilterGroupChildren.tsx | 4 + .../AdvancedFilterRecordFilterGroupRow.tsx | 4 + .../AdvancedFilterRecordFilterRow.tsx | 14 +- .../AdvancedFilterRootRecordFilterGroup.tsx | 2 +- ...ncedFilterValueFormCompositeFieldInput.tsx | 58 ++++++ .../AdvancedFilterValueFormInput.tsx | 67 +++++++ .../states/context/AdvancedFilterContext.ts | 9 + .../hooks/useRemoveRecordFilterGroup.ts | 11 +- .../hooks/useUpsertRecordFilterGroup.ts | 12 +- .../hooks/useRemoveRecordFilter.ts | 15 +- .../hooks/useUpsertRecordFilter.ts | 11 +- .../validation-schemas/workflowSchema.ts | 7 + .../WorkflowEditActionFindRecords.tsx | 73 ++++++- .../components/WorkflowFindRecordFilters.tsx | 188 ++++++++++++++++++ .../WorkflowFindRecordFiltersEffect.tsx | 89 +++++++++ .../find-records.workflow-action.ts | 10 +- .../workflow-record-crud-action-input.type.ts | 9 +- 17 files changed, 568 insertions(+), 15 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueFormCompositeFieldInput.tsx create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueFormInput.tsx create mode 100644 packages/twenty-front/src/modules/object-record/advanced-filter/states/context/AdvancedFilterContext.ts create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowFindRecordFilters.tsx create mode 100644 packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowFindRecordFiltersEffect.tsx diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildren.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildren.tsx index 4079d56a0..291181b9f 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildren.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterGroupChildren.tsx @@ -2,6 +2,7 @@ import { AdvancedFilterAddFilterRuleSelect } from '@/object-record/advanced-filt import { AdvancedFilterRecordFilterRow } from '@/object-record/advanced-filter/components/AdvancedFilterRecordFilterRow'; import { useChildRecordFiltersAndRecordFilterGroups } from '@/object-record/advanced-filter/hooks/useChildRecordFiltersAndRecordFilterGroups'; +import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent'; import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; @@ -21,10 +22,12 @@ const StyledContainer = styled.div<{ isGrayBackground?: boolean }>` type AdvancedFilterRecordFilterGroupChildrenProps = { recordFilterGroupId: string; + VariablePicker?: VariablePickerComponent; }; export const AdvancedFilterRecordFilterGroupChildren = ({ recordFilterGroupId, + VariablePicker, }: AdvancedFilterRecordFilterGroupChildrenProps) => { const { currentRecordFilterGroup, childRecordFilters } = useChildRecordFiltersAndRecordFilterGroups({ @@ -47,6 +50,7 @@ export const AdvancedFilterRecordFilterGroupChildren = ({ recordFilter={childRecordFilter} recordFilterIndex={childRecordFilterIndex} recordFilterGroup={currentRecordFilterGroup} + VariablePicker={VariablePicker} /> ))} { return ( @@ -21,6 +24,7 @@ export const AdvancedFilterRecordFilterGroupRow = ({ /> { return ( - + {isDefined(VariablePicker) ? ( + + ) : ( + + )} diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootRecordFilterGroup.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootRecordFilterGroup.tsx index 4d0b9d3c5..a8a31d601 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootRecordFilterGroup.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRootRecordFilterGroup.tsx @@ -13,7 +13,7 @@ import styled from '@emotion/styled'; import { id } from 'date-fns/locale'; import { isDefined } from 'twenty-shared/utils'; -const StyledContainer = styled.div<{ isGrayBackground?: boolean }>` +const StyledContainer = styled.div` align-items: start; display: flex; flex: 1; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueFormCompositeFieldInput.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueFormCompositeFieldInput.tsx new file mode 100644 index 000000000..631fb7af4 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueFormCompositeFieldInput.tsx @@ -0,0 +1,58 @@ +import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState'; +import { FormCountryCodeSelectInput } from '@/object-record/record-field/form-types/components/FormCountryCodeSelectInput'; +import { FormCountrySelectInput } from '@/object-record/record-field/form-types/components/FormCountrySelectInput'; +import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput'; +import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput'; +import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { JsonValue } from 'type-fest'; + +export const AdvancedFilterValueFormCompositeFieldInput = ({ + recordFilter, + VariablePicker, + onChange, +}: { + recordFilter: RecordFilter; + VariablePicker: VariablePickerComponent; + onChange: (newValue: JsonValue) => void; +}) => { + const subFieldNameUsedInDropdown = useRecoilComponentValueV2( + subFieldNameUsedInDropdownComponentState, + ); + + const filterType = recordFilter.type; + + return ( + <> + {filterType === 'ADDRESS' && + (subFieldNameUsedInDropdown === 'addressCountry' ? ( + + ) : ( + + ))} + {filterType === 'CURRENCY' && + (recordFilter.subFieldName === 'currencyCode' ? ( + + ) : recordFilter.subFieldName === 'amountMicros' ? ( + + ) : null)} + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueFormInput.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueFormInput.tsx new file mode 100644 index 000000000..bbd0f9e81 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueFormInput.tsx @@ -0,0 +1,67 @@ +import { AdvancedFilterValueFormCompositeFieldInput } from '@/object-record/advanced-filter/components/AdvancedFilterValueFormCompositeFieldInput'; +import { useApplyObjectFilterDropdownFilterValue } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownFilterValue'; +import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState'; +import { FormFieldInput } from '@/object-record/record-field/components/FormFieldInput'; +import { VariablePickerComponent } from '@/object-record/record-field/form-types/types/VariablePickerComponent'; +import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { JsonValue } from 'type-fest'; + +export const AdvancedFilterValueFormInput = ({ + recordFilterId, + VariablePicker, +}: { + recordFilterId: string; + VariablePicker: VariablePickerComponent; +}) => { + const currentRecordFilters = useRecoilComponentValueV2( + currentRecordFiltersComponentState, + ); + + const subFieldNameUsedInDropdown = useRecoilComponentValueV2( + subFieldNameUsedInDropdownComponentState, + ); + + const recordFilter = currentRecordFilters.find( + (recordFilter) => recordFilter.id === recordFilterId, + ); + + const isDisabled = !recordFilter?.fieldMetadataId || !recordFilter.operand; + + const { applyObjectFilterDropdownFilterValue } = + useApplyObjectFilterDropdownFilterValue(); + + const handleChange = (newValue: JsonValue) => { + applyObjectFilterDropdownFilterValue(newValue as string); + }; + + if (isDisabled) { + return null; + } + + if (isDefined(subFieldNameUsedInDropdown)) { + return ( + + ); + } + + return ( + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/states/context/AdvancedFilterContext.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/states/context/AdvancedFilterContext.ts new file mode 100644 index 000000000..000feb023 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/states/context/AdvancedFilterContext.ts @@ -0,0 +1,9 @@ +import { createContext } from 'react'; + +type AdvancedFilterContextType = { + onUpdate?: () => void; +}; + +export const AdvancedFilterContext = createContext( + {} as AdvancedFilterContextType, +); diff --git a/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup.ts b/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup.ts index 35de0eb7e..21fdba5f0 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useRemoveRecordFilterGroup.ts @@ -1,13 +1,17 @@ +import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext'; import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { useContext } from 'react'; import { useRecoilCallback } from 'recoil'; export const useRemoveRecordFilterGroup = () => { const currentRecordFilterGroupsCallbackState = useRecoilComponentCallbackStateV2(currentRecordFilterGroupsComponentState); - const removeRecordFilterGroup = useRecoilCallback( + const { onUpdate } = useContext(AdvancedFilterContext); + + const removeRecordFilterGroupCallback = useRecoilCallback( ({ set, snapshot }) => (recordFilterGroupIdToRemove: string) => { const currentRecordFilterGroups = getSnapshotValue( @@ -53,6 +57,11 @@ export const useRemoveRecordFilterGroup = () => { [currentRecordFilterGroupsCallbackState], ); + const removeRecordFilterGroup = (recordFilterGroupIdToRemove: string) => { + removeRecordFilterGroupCallback(recordFilterGroupIdToRemove); + onUpdate?.(); + }; + return { removeRecordFilterGroup, }; diff --git a/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup.ts b/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup.ts index 372972f3e..a293a4f85 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter-group/hooks/useUpsertRecordFilterGroup.ts @@ -1,14 +1,17 @@ +import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext'; import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { useContext } from 'react'; import { useRecoilCallback } from 'recoil'; export const useUpsertRecordFilterGroup = () => { const currentRecordFilterGroupsCallbackState = useRecoilComponentCallbackStateV2(currentRecordFilterGroupsComponentState); + const { onUpdate } = useContext(AdvancedFilterContext); - const upsertRecordFilterGroup = useRecoilCallback( + const upsertRecordFilterGroupCallback = useRecoilCallback( ({ set, snapshot }) => (recordFilterGroupToSet: RecordFilterGroup) => { const currentRecordFilterGroups = getSnapshotValue( @@ -57,6 +60,13 @@ export const useUpsertRecordFilterGroup = () => { [currentRecordFilterGroupsCallbackState], ); + const upsertRecordFilterGroup = ( + recordFilterGroupToSet: RecordFilterGroup, + ) => { + upsertRecordFilterGroupCallback(recordFilterGroupToSet); + onUpdate?.(); + }; + return { upsertRecordFilterGroup, }; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts index 3ac0876c4..afe364f4e 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useRemoveRecordFilter.ts @@ -1,9 +1,11 @@ +import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId'; import { vectorSearchInputComponentState } from '@/views/states/vectorSearchInputComponentState'; import { isVectorSearchFilter } from '@/views/utils/isVectorSearchFilter'; +import { useContext } from 'react'; import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; @@ -12,7 +14,9 @@ export const useRemoveRecordFilter = () => { currentRecordFiltersComponentState, ); - const removeRecordFilter = useRecoilCallback( + const { onUpdate } = useContext(AdvancedFilterContext); + + const removeRecordFilterCallback = useRecoilCallback( ({ set, snapshot }) => ({ recordFilterId }: { recordFilterId: string }) => { const currentRecordFilters = getSnapshotValue( @@ -52,6 +56,15 @@ export const useRemoveRecordFilter = () => { [currentRecordFiltersCallbackState], ); + const removeRecordFilter = ({ + recordFilterId, + }: { + recordFilterId: string; + }) => { + removeRecordFilterCallback({ recordFilterId }); + onUpdate?.(); + }; + return { removeRecordFilter, }; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts index e12a676bb..eff175da0 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/hooks/useUpsertRecordFilter.ts @@ -1,7 +1,9 @@ +import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; +import { useContext } from 'react'; import { useRecoilCallback } from 'recoil'; export const useUpsertRecordFilter = () => { @@ -9,7 +11,9 @@ export const useUpsertRecordFilter = () => { currentRecordFiltersComponentState, ); - const upsertRecordFilter = useRecoilCallback( + const { onUpdate } = useContext(AdvancedFilterContext); + + const upsertRecordFilterCallback = useRecoilCallback( ({ set, snapshot }) => (recordFilterToSet: RecordFilter) => { const currentRecordFilters = getSnapshotValue( @@ -46,6 +50,11 @@ export const useUpsertRecordFilter = () => { [currentRecordFiltersCallbackState], ); + const upsertRecordFilter = (recordFilterToSet: RecordFilter) => { + upsertRecordFilterCallback(recordFilterToSet); + onUpdate?.(); + }; + return { upsertRecordFilter, }; diff --git a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts index 2842c3074..6210c5956 100644 --- a/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts +++ b/packages/twenty-front/src/modules/workflow/validation-schemas/workflowSchema.ts @@ -80,6 +80,13 @@ export const workflowFindRecordsActionSettingsSchema = input: z.object({ objectName: z.string(), limit: z.number().optional(), + filter: z + .object({ + recordFilterGroups: z.array(z.object({})).optional(), + recordFilters: z.array(z.object({})).optional(), + gqlOperationFilter: z.object({}).optional().nullable(), + }) + .optional(), }), }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFindRecords.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFindRecords.tsx index 19666b442..fe03c0245 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFindRecords.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFindRecords.tsx @@ -4,14 +4,23 @@ import { WorkflowFindRecordsAction } from '@/workflow/types/Workflow'; import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader'; import { useEffect, useState } from 'react'; +import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { FormNumberFieldInput } from '@/object-record/record-field/form-types/components/FormNumberFieldInput'; +import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext'; +import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; +import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; +import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; +import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext'; import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody'; +import { WorkflowFindRecordFilters } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowFindRecordFilters'; +import { WorkflowFindRecordFiltersEffect } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowFindRecordFiltersEffect'; import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow'; import { useActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionIconColorOrThrow'; import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon'; import { isDefined } from 'twenty-shared/utils'; import { HorizontalSeparator, useIcons } from 'twenty-ui/display'; import { SelectOption } from 'twenty-ui/input'; +import { JsonValue } from 'type-fest'; import { useDebouncedCallback } from 'use-debounce'; type WorkflowEditActionFindRecordsProps = { @@ -28,9 +37,16 @@ type WorkflowEditActionFindRecordsProps = { type FindRecordsFormData = { objectName: string; + filter?: FindRecordsActionFilter; limit?: number; }; +export type FindRecordsActionFilter = { + recordFilterGroups?: RecordFilterGroup[]; + recordFilters?: RecordFilter[]; + gqlOperationFilter?: JsonValue; +}; + export const WorkflowEditActionFindRecords = ({ action, actionOptions, @@ -50,13 +66,18 @@ export const WorkflowEditActionFindRecords = ({ const [formData, setFormData] = useState({ objectName: action.settings.input.objectName, limit: action.settings.input.limit, + filter: action.settings.input.filter as FindRecordsActionFilter, }); const isFormDisabled = actionOptions.readonly; + const selectedObjectMetadataItem = activeNonSystemObjectMetadataItems.find( + (item) => item.nameSingular === formData.objectName, + ); + const selectedObjectMetadataItemNameSingular = - activeNonSystemObjectMetadataItems.find( - (item) => item.nameSingular === formData.objectName, - )?.nameSingular ?? ''; + selectedObjectMetadataItem?.nameSingular ?? ''; + + const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); const saveAction = useDebouncedCallback( async (formData: FindRecordsFormData) => { @@ -64,7 +85,11 @@ export const WorkflowEditActionFindRecords = ({ return; } - const { objectName: updatedObjectName, limit: updatedLimit } = formData; + const { + objectName: updatedObjectName, + limit: updatedLimit, + filter: updatedFilter, + } = formData; actionOptions.onActionUpdate({ ...action, @@ -73,6 +98,7 @@ export const WorkflowEditActionFindRecords = ({ input: { objectName: updatedObjectName, limit: updatedLimit ?? 1, + filter: updatedFilter, }, }, }); @@ -90,6 +116,7 @@ export const WorkflowEditActionFindRecords = ({ const headerIcon = getActionIcon(action.type); const headerIconColor = useActionIconColorOrThrow(action.type); const headerType = useActionHeaderTypeOrThrow(action.type); + const instanceId = `workflow-edit-action-record-find-records-${action.id}`; return ( <> @@ -133,6 +160,44 @@ export const WorkflowEditActionFindRecords = ({ /> + {isDefined(selectedObjectMetadataItem) && ( + '', + onIndexRecordsLoaded: () => {}, + objectNamePlural: selectedObjectMetadataItem.labelPlural, + objectNameSingular: selectedObjectMetadataItem.nameSingular, + objectMetadataItem: selectedObjectMetadataItem, + recordIndexId: instanceId, + objectPermissionsByObjectMetadataId, + }} + > + + + { + const newFormData: FindRecordsFormData = { + ...formData, + filter, + }; + + setFormData(newFormData); + + saveAction(newFormData); + }} + /> + + + + + )} theme.spacing(2)}; + padding: ${({ theme }) => theme.spacing(2)}; +`; + +export const WorkflowFindRecordFilters = ({ + objectMetadataItem, + onChange, +}: { + objectMetadataItem: ObjectMetadataItem; + onChange: (filter: FindRecordsActionFilter) => void; +}) => { + const rootRecordFilterGroup = useRecoilComponentValueV2( + rootLevelRecordFilterGroupComponentSelector, + ); + + const { childRecordFiltersAndRecordFilterGroups } = + useChildRecordFiltersAndRecordFilterGroups({ + recordFilterGroupId: rootRecordFilterGroup?.id, + }); + + const { upsertRecordFilterGroup } = useUpsertRecordFilterGroup(); + + const { upsertRecordFilter } = useUpsertRecordFilter(); + + const { createEmptyRecordFilterFromFieldMetadataItem } = + useCreateEmptyRecordFilterFromFieldMetadataItem(); + + const availableFieldMetadataItemsForFilter = useRecoilValue( + availableFieldMetadataItemsForFilterFamilySelector({ + objectMetadataItemId: objectMetadataItem.id, + }), + ); + + const defaultFieldMetadataItem = + availableFieldMetadataItemsForFilter.find( + (fieldMetadataItem) => + fieldMetadataItem.id === + objectMetadataItem?.labelIdentifierFieldMetadataId, + ) ?? availableFieldMetadataItemsForFilter[0]; + + const addRootRecordFilterGroup = () => { + const alreadyHasAdvancedFilterGroup = isDefined(rootRecordFilterGroup); + + if (!alreadyHasAdvancedFilterGroup) { + const newRecordFilterGroup = { + id: v4(), + logicalOperator: RecordFilterGroupLogicalOperator.AND, + }; + + upsertRecordFilterGroup(newRecordFilterGroup); + + if (!isDefined(defaultFieldMetadataItem)) { + throw new Error('Missing default filter definition'); + } + + const { newRecordFilter } = createEmptyRecordFilterFromFieldMetadataItem( + defaultFieldMetadataItem, + ); + + newRecordFilter.recordFilterGroupId = newRecordFilterGroup.id; + + upsertRecordFilter(newRecordFilter); + } + }; + + const currentRecordFilterGroupsCallbackState = + useRecoilComponentCallbackStateV2(currentRecordFilterGroupsComponentState); + + const currentRecordFiltersCallbackState = useRecoilComponentCallbackStateV2( + currentRecordFiltersComponentState, + ); + + const onUpdate = useRecoilCallback( + ({ snapshot }) => + () => { + const currentRecordFilterGroups = getSnapshotValue( + snapshot, + currentRecordFilterGroupsCallbackState, + ); + + const currentRecordFilters = getSnapshotValue( + snapshot, + currentRecordFiltersCallbackState, + ); + + const gqlOperationFilter = computeRecordGqlOperationFilter({ + fields: objectMetadataItem.fields, + filterValueDependencies: {}, + recordFilters: currentRecordFilters, + recordFilterGroups: currentRecordFilterGroups, + }); + + const newFilter = { + recordFilterGroups: currentRecordFilterGroups, + recordFilters: currentRecordFilters, + gqlOperationFilter, + } as FindRecordsActionFilter; + + onChange(newFilter); + }, + [ + currentRecordFilterGroupsCallbackState, + currentRecordFiltersCallbackState, + objectMetadataItem.fields, + onChange, + ], + ); + + return ( + + {isDefined(rootRecordFilterGroup) ? ( + + {childRecordFiltersAndRecordFilterGroups.map( + (recordFilterGroupChild, recordFilterGroupChildIndex) => + isRecordFilterGroupChildARecordFilterGroup( + recordFilterGroupChild, + ) ? ( + + ) : ( + + ), + )} + + + ) : ( +