Filter dropdown by types (#13703)

Allow to filter types in variable dropdown. This will avoid unsupported
types for filters (RICH_TEXT, ACTOR)
<img width="579" height="505" alt="Capture d’écran 2025-08-07 à 09 58
42"
src="https://github.com/user-attachments/assets/c3fbeb3f-61ab-4c2c-bb2c-6f2c6292ef9e"
/>
This commit is contained in:
Thomas Trompette
2025-08-07 10:38:46 +02:00
committed by GitHub
parent 6e4e623303
commit 9cfd62f1ef
5 changed files with 96 additions and 5 deletions
@@ -15,11 +15,17 @@ import { useContext } from 'react';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { StepFilter } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
type WorkflowStepFilterFieldSelectProps = {
stepFilter: StepFilter;
};
const NON_SELECTABLE_FIELD_TYPES = [
FieldMetadataType.ACTOR,
FieldMetadataType.RICH_TEXT_V2,
];
export const WorkflowStepFilterFieldSelect = ({
stepFilter,
}: WorkflowStepFilterFieldSelectProps) => {
@@ -163,6 +169,7 @@ export const WorkflowStepFilterFieldSelect = ({
shouldDisplayRecordFields={shouldDisplayRecordFields}
shouldDisplayRecordObjects={shouldDisplayRecordObjects}
shouldEnableSelectRelationObject={true}
fieldTypesToExclude={NON_SELECTABLE_FIELD_TYPES}
/>
);
};
@@ -3,6 +3,7 @@ import { StyledDropdownButtonContainer } from '@/ui/layout/dropdown/components/S
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { InputSchemaPropertyType } from '@/workflow/types/InputSchema';
import { WorkflowVariablesDropdownAllItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownAllItems';
import { WorkflowVariablesDropdownFieldItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownFieldItems';
import { WorkflowVariablesDropdownWorkflowStepItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownWorkflowStepItems';
@@ -37,6 +38,7 @@ export const WorkflowVariablesDropdown = ({
disabled,
shouldDisplayRecordFields,
shouldDisplayRecordObjects,
fieldTypesToExclude,
shouldEnableSelectRelationObject,
multiline,
clickableComponent,
@@ -45,6 +47,7 @@ export const WorkflowVariablesDropdown = ({
onVariableSelect: (variableName: string) => void;
shouldDisplayRecordFields: boolean;
shouldDisplayRecordObjects: boolean;
fieldTypesToExclude?: InputSchemaPropertyType[];
shouldEnableSelectRelationObject?: boolean;
disabled?: boolean;
multiline?: boolean;
@@ -61,6 +64,7 @@ export const WorkflowVariablesDropdown = ({
const availableVariablesInWorkflowStep = useAvailableVariablesInWorkflowStep({
shouldDisplayRecordFields,
shouldDisplayRecordObjects,
fieldTypesToExclude,
});
const noAvailableVariables = availableVariablesInWorkflowStep.length === 0;
@@ -1,5 +1,6 @@
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/stepsOutputSchemaFamilySelector';
import { InputSchemaPropertyType } from '@/workflow/types/InputSchema';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { getPreviousSteps } from '@/workflow/workflow-steps/utils/getWorkflowPreviousSteps';
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
@@ -15,9 +16,11 @@ import { isEmptyObject } from '~/utils/isEmptyObject';
export const useAvailableVariablesInWorkflowStep = ({
shouldDisplayRecordFields,
shouldDisplayRecordObjects,
fieldTypesToExclude,
}: {
shouldDisplayRecordFields: boolean;
shouldDisplayRecordObjects: boolean;
fieldTypesToExclude?: InputSchemaPropertyType[];
}): StepOutputSchema[] => {
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
const flow = useFlowOrThrow();
@@ -41,6 +44,7 @@ export const useAvailableVariablesInWorkflowStep = ({
shouldDisplayRecordFields,
shouldDisplayRecordObjects,
outputSchema: stepOutputSchema.outputSchema,
fieldTypesToExclude,
}) as OutputSchema;
if (!isDefined(outputSchema) || isEmptyObject(outputSchema)) {
@@ -225,4 +225,44 @@ describe('filterOutputSchema', () => {
).toBeUndefined();
});
});
describe('fieldTypesToExclude', () => {
it('should filter out the types', () => {
const inputSchema = createRecordSchema('person', {
name: { isLeaf: true, type: undefined, value: 'toto' },
age: { isLeaf: true, type: FieldMetadataType.NUMBER },
id: { isLeaf: true, type: FieldMetadataType.UUID },
});
const expectedSchema = createRecordSchema('person', {
name: { isLeaf: true, type: undefined, value: 'toto' },
age: { isLeaf: true, type: FieldMetadataType.NUMBER },
});
expect(
filterOutputSchema({
shouldDisplayRecordFields: true,
shouldDisplayRecordObjects: false,
outputSchema: inputSchema,
fieldTypesToExclude: [FieldMetadataType.UUID],
}),
).toEqual(expectedSchema);
});
it('should return the same schema if no types to filter', () => {
const inputSchema = createRecordSchema('person', {
name: { isLeaf: true, value: 'string' },
id: { isLeaf: true, type: FieldMetadataType.UUID },
});
expect(
filterOutputSchema({
shouldDisplayRecordFields: true,
shouldDisplayRecordObjects: false,
outputSchema: inputSchema,
fieldTypesToExclude: [],
}),
).toEqual(inputSchema);
});
});
});
@@ -1,3 +1,4 @@
import { InputSchemaPropertyType } from '@/workflow/types/InputSchema';
import {
BaseOutputSchema,
OutputSchema,
@@ -128,20 +129,55 @@ const filterBaseOutputSchema = ({
return undefined;
};
const filterRecordOutputSchemaFieldsByType = ({
outputSchema,
fieldTypesToExclude,
}: {
outputSchema: RecordOutputSchema;
fieldTypesToExclude: InputSchemaPropertyType[];
}) => {
const filteredFields: BaseOutputSchema = {};
for (const key in outputSchema.fields) {
const field = outputSchema.fields[key];
if (isDefined(field.type) && fieldTypesToExclude.includes(field.type)) {
continue;
}
filteredFields[key] = field;
}
return {
...outputSchema,
// Relations could be filtered recursively but this util requires a global simplification first
fields: filteredFields,
};
};
export const filterOutputSchema = ({
shouldDisplayRecordFields,
shouldDisplayRecordObjects,
outputSchema,
fieldTypesToExclude,
}: {
shouldDisplayRecordFields: boolean;
shouldDisplayRecordObjects: boolean;
outputSchema?: OutputSchema;
fieldTypesToExclude?: InputSchemaPropertyType[];
}): OutputSchema | undefined => {
if (
!shouldDisplayRecordObjects ||
shouldDisplayRecordFields ||
!outputSchema
) {
if (!isDefined(outputSchema)) {
return undefined;
}
if (!shouldDisplayRecordObjects || shouldDisplayRecordFields) {
if (isRecordOutputSchema(outputSchema) && isDefined(fieldTypesToExclude)) {
return filterRecordOutputSchemaFieldsByType({
outputSchema,
fieldTypesToExclude,
});
}
return outputSchema;
}