Add first filter step version (#13093)
I rebuilt the advanced filters used in views and workflow search for a specific filter step. Components structure remains the same, using `stepFilterGroups` and `stepFilters`. But those filters are directly sent to backend. Also re-using the same kind of states we use for advanced filters to share the current filters used. And a context to share what's coming from workflow props (function to update step settings and readonly) ⚠️ this PR only focusses on the content of the step. There is still a lot to do on the filter icon behavior in the workflow https://github.com/user-attachments/assets/8a6a76f0-11fa-444a-82b9-71fc96b18af4
This commit is contained in:
@ -592,8 +592,8 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
settings: {
|
||||
...BASE_STEP_DEFINITION,
|
||||
input: {
|
||||
filterGroups: [],
|
||||
filters: [],
|
||||
stepFilterGroups: [],
|
||||
stepFilters: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -2,4 +2,5 @@ export type WorkflowActionOutput = {
|
||||
result?: object;
|
||||
error?: string;
|
||||
pendingEvent?: boolean;
|
||||
shouldEndWorkflowRun?: boolean;
|
||||
};
|
||||
|
||||
@ -33,23 +33,24 @@ export class FilterWorkflowAction implements WorkflowAction {
|
||||
);
|
||||
}
|
||||
|
||||
const { filterGroups, filters } = step.settings.input;
|
||||
const { stepFilterGroups, stepFilters } = step.settings.input;
|
||||
|
||||
if (!filterGroups || !filters) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Filter is not defined',
|
||||
WorkflowStepExecutorExceptionCode.INVALID_STEP_SETTINGS,
|
||||
);
|
||||
if (!stepFilterGroups || !stepFilters) {
|
||||
return {
|
||||
result: {
|
||||
shouldEndWorkflowRun: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const resolvedFilters = filters.map((filter) => ({
|
||||
const resolvedFilters = stepFilters.map((filter) => ({
|
||||
...filter,
|
||||
rightOperand: resolveInput(filter.value, context),
|
||||
leftOperand: resolveInput(filter.stepOutputKey, context),
|
||||
}));
|
||||
|
||||
const matchesFilter = evaluateFilterConditions({
|
||||
filterGroups,
|
||||
filterGroups: stepFilterGroups,
|
||||
filters: resolvedFilters,
|
||||
});
|
||||
|
||||
@ -57,6 +58,7 @@ export class FilterWorkflowAction implements WorkflowAction {
|
||||
result: {
|
||||
matchesFilter,
|
||||
},
|
||||
shouldEndWorkflowRun: !matchesFilter,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,44 +1,10 @@
|
||||
import { StepFilter, StepFilterGroup } from 'twenty-shared/types';
|
||||
|
||||
import { BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
|
||||
|
||||
export enum LogicalOperator {
|
||||
AND = 'AND',
|
||||
OR = 'OR',
|
||||
}
|
||||
|
||||
export enum Operand {
|
||||
EQ = 'eq',
|
||||
NE = 'ne',
|
||||
GT = 'gt',
|
||||
GTE = 'gte',
|
||||
LT = 'lt',
|
||||
LTE = 'lte',
|
||||
LIKE = 'like',
|
||||
ILIKE = 'ilike',
|
||||
IN = 'in',
|
||||
IS = 'is',
|
||||
}
|
||||
|
||||
export type FilterGroup = {
|
||||
id: string;
|
||||
logicalOperator: LogicalOperator;
|
||||
parentRecordFilterGroupId?: string;
|
||||
positionInRecordFilterGroup?: number;
|
||||
};
|
||||
|
||||
export type Filter = {
|
||||
id: string;
|
||||
type: string;
|
||||
label: string;
|
||||
value: string;
|
||||
operand: Operand;
|
||||
displayValue: string;
|
||||
recordFilterGroupId: string;
|
||||
stepOutputKey: string;
|
||||
};
|
||||
|
||||
export type WorkflowFilterActionSettings = BaseWorkflowActionSettings & {
|
||||
input: {
|
||||
filterGroups?: FilterGroup[];
|
||||
filters?: Filter[];
|
||||
stepFilterGroups?: StepFilterGroup[];
|
||||
stepFilters?: StepFilter[];
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,21 +1,11 @@
|
||||
import {
|
||||
FilterGroup,
|
||||
LogicalOperator,
|
||||
Operand,
|
||||
} from 'src/modules/workflow/workflow-executor/workflow-actions/filter/types/workflow-filter-action-settings.type';
|
||||
import { evaluateFilterConditions } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util';
|
||||
StepFilter,
|
||||
StepFilterGroup,
|
||||
StepLogicalOperator,
|
||||
StepOperand,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
type ResolvedFilter = {
|
||||
id: string;
|
||||
type: string;
|
||||
label: string;
|
||||
rightOperand: unknown;
|
||||
operand: Operand;
|
||||
displayValue: string;
|
||||
fieldMetadataId: string;
|
||||
recordFilterGroupId: string;
|
||||
leftOperand: unknown;
|
||||
};
|
||||
import { evaluateFilterConditions } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util';
|
||||
|
||||
describe('evaluateFilterConditions', () => {
|
||||
describe('empty inputs', () => {
|
||||
@ -36,8 +26,13 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
describe('single filter operands', () => {
|
||||
type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
|
||||
rightOperand: unknown;
|
||||
leftOperand: unknown;
|
||||
};
|
||||
|
||||
const createFilter = (
|
||||
operand: Operand,
|
||||
operand: StepOperand,
|
||||
leftOperand: unknown,
|
||||
rightOperand: unknown,
|
||||
): ResolvedFilter => ({
|
||||
@ -47,28 +42,27 @@ describe('evaluateFilterConditions', () => {
|
||||
rightOperand,
|
||||
operand,
|
||||
displayValue: String(rightOperand),
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand,
|
||||
});
|
||||
|
||||
describe('eq operand', () => {
|
||||
it('should return true when values are equal', () => {
|
||||
const filter = createFilter(Operand.EQ, 'John', 'John');
|
||||
const filter = createFilter(StepOperand.EQ, 'John', 'John');
|
||||
const result = evaluateFilterConditions({ filters: [filter] });
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when values are loosely equal', () => {
|
||||
const filter = createFilter(Operand.EQ, '123', 123);
|
||||
const filter = createFilter(StepOperand.EQ, '123', 123);
|
||||
const result = evaluateFilterConditions({ filters: [filter] });
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when values are not equal', () => {
|
||||
const filter = createFilter(Operand.EQ, 'John', 'Jane');
|
||||
const filter = createFilter(StepOperand.EQ, 'John', 'Jane');
|
||||
const result = evaluateFilterConditions({ filters: [filter] });
|
||||
|
||||
expect(result).toBe(false);
|
||||
@ -77,14 +71,14 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
describe('ne operand', () => {
|
||||
it('should return false when values are equal', () => {
|
||||
const filter = createFilter(Operand.NE, 'John', 'John');
|
||||
const filter = createFilter(StepOperand.NE, 'John', 'John');
|
||||
const result = evaluateFilterConditions({ filters: [filter] });
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when values are not equal', () => {
|
||||
const filter = createFilter(Operand.NE, 'John', 'Jane');
|
||||
const filter = createFilter(StepOperand.NE, 'John', 'Jane');
|
||||
const result = evaluateFilterConditions({ filters: [filter] });
|
||||
|
||||
expect(result).toBe(true);
|
||||
@ -93,17 +87,17 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
describe('numeric operands', () => {
|
||||
it('should handle gt operand correctly', () => {
|
||||
const filter1 = createFilter(Operand.GT, 30, 25);
|
||||
const filter2 = createFilter(Operand.GT, 20, 25);
|
||||
const filter1 = createFilter(StepOperand.GT, 30, 25);
|
||||
const filter2 = createFilter(StepOperand.GT, 20, 25);
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle gte operand correctly', () => {
|
||||
const filter1 = createFilter(Operand.GTE, 25, 25);
|
||||
const filter2 = createFilter(Operand.GTE, 30, 25);
|
||||
const filter3 = createFilter(Operand.GTE, 20, 25);
|
||||
const filter1 = createFilter(StepOperand.GTE, 25, 25);
|
||||
const filter2 = createFilter(StepOperand.GTE, 30, 25);
|
||||
const filter3 = createFilter(StepOperand.GTE, 20, 25);
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
|
||||
@ -111,17 +105,17 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
it('should handle lt operand correctly', () => {
|
||||
const filter1 = createFilter(Operand.LT, 20, 25);
|
||||
const filter2 = createFilter(Operand.LT, 30, 25);
|
||||
const filter1 = createFilter(StepOperand.LT, 20, 25);
|
||||
const filter2 = createFilter(StepOperand.LT, 30, 25);
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle lte operand correctly', () => {
|
||||
const filter1 = createFilter(Operand.LTE, 25, 25);
|
||||
const filter2 = createFilter(Operand.LTE, 20, 25);
|
||||
const filter3 = createFilter(Operand.LTE, 30, 25);
|
||||
const filter1 = createFilter(StepOperand.LTE, 25, 25);
|
||||
const filter2 = createFilter(StepOperand.LTE, 20, 25);
|
||||
const filter3 = createFilter(StepOperand.LTE, 30, 25);
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
|
||||
@ -129,8 +123,8 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
it('should convert string numbers for numeric comparisons', () => {
|
||||
const filter1 = createFilter(Operand.GT, '30', '25');
|
||||
const filter2 = createFilter(Operand.LT, '20', '25');
|
||||
const filter1 = createFilter(StepOperand.GT, '30', '25');
|
||||
const filter2 = createFilter(StepOperand.LT, '20', '25');
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
|
||||
@ -139,17 +133,17 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
describe('string operands', () => {
|
||||
it('should handle like operand correctly', () => {
|
||||
const filter1 = createFilter(Operand.LIKE, 'Hello World', 'World');
|
||||
const filter2 = createFilter(Operand.LIKE, 'Hello', 'World');
|
||||
const filter1 = createFilter(StepOperand.LIKE, 'Hello World', 'World');
|
||||
const filter2 = createFilter(StepOperand.LIKE, 'Hello', 'World');
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle ilike operand correctly (case insensitive)', () => {
|
||||
const filter1 = createFilter(Operand.ILIKE, 'Hello World', 'WORLD');
|
||||
const filter2 = createFilter(Operand.ILIKE, 'Hello World', 'world');
|
||||
const filter3 = createFilter(Operand.ILIKE, 'Hello', 'WORLD');
|
||||
const filter1 = createFilter(StepOperand.ILIKE, 'Hello World', 'WORLD');
|
||||
const filter2 = createFilter(StepOperand.ILIKE, 'Hello World', 'world');
|
||||
const filter3 = createFilter(StepOperand.ILIKE, 'Hello', 'WORLD');
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
|
||||
@ -160,12 +154,12 @@ describe('evaluateFilterConditions', () => {
|
||||
describe('in operand', () => {
|
||||
it('should handle JSON array values', () => {
|
||||
const filter1 = createFilter(
|
||||
Operand.IN,
|
||||
StepOperand.IN,
|
||||
'apple',
|
||||
'["apple", "banana", "cherry"]',
|
||||
);
|
||||
const filter2 = createFilter(
|
||||
Operand.IN,
|
||||
StepOperand.IN,
|
||||
'grape',
|
||||
'["apple", "banana", "cherry"]',
|
||||
);
|
||||
@ -176,12 +170,12 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
it('should handle comma-separated string values when JSON parsing fails', () => {
|
||||
const filter1 = createFilter(
|
||||
Operand.IN,
|
||||
StepOperand.IN,
|
||||
'apple',
|
||||
'apple, banana, cherry',
|
||||
);
|
||||
const filter2 = createFilter(
|
||||
Operand.IN,
|
||||
StepOperand.IN,
|
||||
'grape',
|
||||
'apple, banana, cherry',
|
||||
);
|
||||
@ -192,7 +186,7 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
it('should handle comma-separated values with whitespace', () => {
|
||||
const filter = createFilter(
|
||||
Operand.IN,
|
||||
StepOperand.IN,
|
||||
'apple',
|
||||
' apple , banana , cherry ',
|
||||
);
|
||||
@ -201,7 +195,11 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
it('should return false for non-array JSON values', () => {
|
||||
const filter = createFilter(Operand.IN, 'apple', '{"key": "value"}');
|
||||
const filter = createFilter(
|
||||
StepOperand.IN,
|
||||
'apple',
|
||||
'{"key": "value"}',
|
||||
);
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter] })).toBe(false);
|
||||
});
|
||||
@ -209,9 +207,9 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
describe('is operand', () => {
|
||||
it('should handle null checks', () => {
|
||||
const filter1 = createFilter(Operand.IS, null, 'null');
|
||||
const filter2 = createFilter(Operand.IS, undefined, 'NULL');
|
||||
const filter3 = createFilter(Operand.IS, 'value', 'null');
|
||||
const filter1 = createFilter(StepOperand.IS, null, 'null');
|
||||
const filter2 = createFilter(StepOperand.IS, undefined, 'NULL');
|
||||
const filter3 = createFilter(StepOperand.IS, 'value', 'null');
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
|
||||
@ -219,10 +217,10 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
it('should handle not null checks', () => {
|
||||
const filter1 = createFilter(Operand.IS, 'value', 'not null');
|
||||
const filter2 = createFilter(Operand.IS, 'value', 'NOT NULL');
|
||||
const filter3 = createFilter(Operand.IS, null, 'not null');
|
||||
const filter4 = createFilter(Operand.IS, undefined, 'not null');
|
||||
const filter1 = createFilter(StepOperand.IS, 'value', 'not null');
|
||||
const filter2 = createFilter(StepOperand.IS, 'value', 'NOT NULL');
|
||||
const filter3 = createFilter(StepOperand.IS, null, 'not null');
|
||||
const filter4 = createFilter(StepOperand.IS, undefined, 'not null');
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(true);
|
||||
@ -231,8 +229,8 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
it('should handle exact value comparisons for non-null/not-null cases', () => {
|
||||
const filter1 = createFilter(Operand.IS, 'exact', 'exact');
|
||||
const filter2 = createFilter(Operand.IS, 'value', 'different');
|
||||
const filter1 = createFilter(StepOperand.IS, 'exact', 'exact');
|
||||
const filter2 = createFilter(StepOperand.IS, 'value', 'different');
|
||||
|
||||
expect(evaluateFilterConditions({ filters: [filter1] })).toBe(true);
|
||||
expect(evaluateFilterConditions({ filters: [filter2] })).toBe(false);
|
||||
@ -241,7 +239,7 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
describe('error cases', () => {
|
||||
it('should throw error for unknown operand', () => {
|
||||
const filter = createFilter('unknown' as Operand, 'value', 'value');
|
||||
const filter = createFilter('unknown' as StepOperand, 'value', 'value');
|
||||
|
||||
expect(() => evaluateFilterConditions({ filters: [filter] })).toThrow(
|
||||
'Unknown operand: unknown',
|
||||
@ -251,6 +249,11 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
describe('multiple filters without groups', () => {
|
||||
type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
|
||||
rightOperand: unknown;
|
||||
leftOperand: unknown;
|
||||
};
|
||||
|
||||
it('should apply AND logic by default for multiple filters', () => {
|
||||
const filters: ResolvedFilter[] = [
|
||||
{
|
||||
@ -258,10 +261,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'John',
|
||||
},
|
||||
{
|
||||
@ -269,10 +271,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Age Filter',
|
||||
rightOperand: 25,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '25',
|
||||
fieldMetadataId: 'field2',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 30,
|
||||
},
|
||||
];
|
||||
@ -289,10 +290,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'John',
|
||||
},
|
||||
{
|
||||
@ -300,10 +300,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Age Filter',
|
||||
rightOperand: 25,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '25',
|
||||
fieldMetadataId: 'field2',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 20, // This will fail
|
||||
},
|
||||
];
|
||||
@ -315,12 +314,17 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
describe('filter groups', () => {
|
||||
type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
|
||||
rightOperand: unknown;
|
||||
leftOperand: unknown;
|
||||
};
|
||||
|
||||
describe('single group with AND logic', () => {
|
||||
it('should return true when all filters pass', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
logicalOperator: StepLogicalOperator.AND,
|
||||
},
|
||||
];
|
||||
|
||||
@ -330,10 +334,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'John',
|
||||
},
|
||||
{
|
||||
@ -341,10 +344,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Age Filter',
|
||||
rightOperand: 25,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '25',
|
||||
fieldMetadataId: 'field2',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 30,
|
||||
},
|
||||
];
|
||||
@ -355,10 +357,10 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
it('should return false when one filter fails', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
logicalOperator: StepLogicalOperator.AND,
|
||||
},
|
||||
];
|
||||
|
||||
@ -368,10 +370,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'Jane', // This will fail
|
||||
},
|
||||
{
|
||||
@ -379,10 +380,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Age Filter',
|
||||
rightOperand: 25,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '25',
|
||||
fieldMetadataId: 'field2',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 30,
|
||||
},
|
||||
];
|
||||
@ -395,10 +395,10 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
describe('single group with OR logic', () => {
|
||||
it('should return true when at least one filter passes', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: LogicalOperator.OR,
|
||||
logicalOperator: StepLogicalOperator.OR,
|
||||
},
|
||||
];
|
||||
|
||||
@ -408,10 +408,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'Jane', // This will fail
|
||||
},
|
||||
{
|
||||
@ -419,10 +418,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Age Filter',
|
||||
rightOperand: 25,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '25',
|
||||
fieldMetadataId: 'field2',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 30, // This will pass
|
||||
},
|
||||
];
|
||||
@ -433,10 +431,10 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
it('should return false when all filters fail', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: LogicalOperator.OR,
|
||||
logicalOperator: StepLogicalOperator.OR,
|
||||
},
|
||||
];
|
||||
|
||||
@ -446,10 +444,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'Jane', // This will fail
|
||||
},
|
||||
{
|
||||
@ -457,10 +454,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Age Filter',
|
||||
rightOperand: 25,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '25',
|
||||
fieldMetadataId: 'field2',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 20, // This will fail
|
||||
},
|
||||
];
|
||||
@ -473,10 +469,10 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
describe('empty groups', () => {
|
||||
it('should return true for empty group', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
logicalOperator: StepLogicalOperator.AND,
|
||||
},
|
||||
];
|
||||
|
||||
@ -488,16 +484,16 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
describe('nested groups', () => {
|
||||
it('should handle nested groups correctly', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
logicalOperator: StepLogicalOperator.AND,
|
||||
},
|
||||
{
|
||||
id: 'group2',
|
||||
logicalOperator: LogicalOperator.OR,
|
||||
parentRecordFilterGroupId: 'group1',
|
||||
positionInRecordFilterGroup: 1,
|
||||
logicalOperator: StepLogicalOperator.OR,
|
||||
parentStepFilterGroupId: 'group1',
|
||||
positionInStepFilterGroup: 1,
|
||||
},
|
||||
];
|
||||
|
||||
@ -508,10 +504,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'John', // This will pass
|
||||
},
|
||||
// Filters in child group with OR logic
|
||||
@ -520,10 +515,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Age Filter',
|
||||
rightOperand: 25,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '25',
|
||||
fieldMetadataId: 'field2',
|
||||
recordFilterGroupId: 'group2',
|
||||
stepFilterGroupId: 'group2',
|
||||
leftOperand: 20, // This will fail
|
||||
},
|
||||
{
|
||||
@ -531,10 +525,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'City Filter',
|
||||
rightOperand: 'NYC',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'NYC',
|
||||
fieldMetadataId: 'field3',
|
||||
recordFilterGroupId: 'group2',
|
||||
stepFilterGroupId: 'group2',
|
||||
leftOperand: 'NYC', // This will pass
|
||||
},
|
||||
];
|
||||
@ -546,22 +539,22 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
it('should handle multiple child groups with correct positioning', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
logicalOperator: StepLogicalOperator.AND,
|
||||
},
|
||||
{
|
||||
id: 'group2',
|
||||
logicalOperator: LogicalOperator.OR,
|
||||
parentRecordFilterGroupId: 'group1',
|
||||
positionInRecordFilterGroup: 1,
|
||||
logicalOperator: StepLogicalOperator.OR,
|
||||
parentStepFilterGroupId: 'group1',
|
||||
positionInStepFilterGroup: 1,
|
||||
},
|
||||
{
|
||||
id: 'group3',
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
parentRecordFilterGroupId: 'group1',
|
||||
positionInRecordFilterGroup: 2,
|
||||
logicalOperator: StepLogicalOperator.AND,
|
||||
parentStepFilterGroupId: 'group1',
|
||||
positionInStepFilterGroup: 2,
|
||||
},
|
||||
];
|
||||
|
||||
@ -572,10 +565,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group2',
|
||||
stepFilterGroupId: 'group2',
|
||||
leftOperand: 'Jane', // This will fail
|
||||
},
|
||||
{
|
||||
@ -583,10 +575,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Status Filter',
|
||||
rightOperand: 'active',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'active',
|
||||
fieldMetadataId: 'field2',
|
||||
recordFilterGroupId: 'group2',
|
||||
stepFilterGroupId: 'group2',
|
||||
leftOperand: 'active', // This will pass
|
||||
},
|
||||
// Group3 filters (AND logic)
|
||||
@ -595,10 +586,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Age Filter',
|
||||
rightOperand: 18,
|
||||
operand: Operand.GTE,
|
||||
operand: StepOperand.GTE,
|
||||
displayValue: '18',
|
||||
fieldMetadataId: 'field3',
|
||||
recordFilterGroupId: 'group3',
|
||||
stepFilterGroupId: 'group3',
|
||||
leftOperand: 25, // This will pass
|
||||
},
|
||||
{
|
||||
@ -606,10 +596,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Score Filter',
|
||||
rightOperand: 80,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '80',
|
||||
fieldMetadataId: 'field4',
|
||||
recordFilterGroupId: 'group3',
|
||||
stepFilterGroupId: 'group3',
|
||||
leftOperand: 85, // This will pass
|
||||
},
|
||||
];
|
||||
@ -623,14 +612,14 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
describe('multiple root groups', () => {
|
||||
it('should combine multiple root groups with AND logic', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: LogicalOperator.OR,
|
||||
logicalOperator: StepLogicalOperator.OR,
|
||||
},
|
||||
{
|
||||
id: 'group2',
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
logicalOperator: StepLogicalOperator.AND,
|
||||
},
|
||||
];
|
||||
|
||||
@ -641,10 +630,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'John', // This will pass
|
||||
},
|
||||
{
|
||||
@ -652,10 +640,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Status Filter',
|
||||
rightOperand: 'inactive',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'inactive',
|
||||
fieldMetadataId: 'field2',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'active', // This will fail
|
||||
},
|
||||
// Group2 filters (AND logic)
|
||||
@ -664,10 +651,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Age Filter',
|
||||
rightOperand: 18,
|
||||
operand: Operand.GTE,
|
||||
operand: StepOperand.GTE,
|
||||
displayValue: '18',
|
||||
fieldMetadataId: 'field3',
|
||||
recordFilterGroupId: 'group2',
|
||||
stepFilterGroupId: 'group2',
|
||||
leftOperand: 25, // This will pass
|
||||
},
|
||||
{
|
||||
@ -675,10 +661,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Score Filter',
|
||||
rightOperand: 80,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '80',
|
||||
fieldMetadataId: 'field4',
|
||||
recordFilterGroupId: 'group2',
|
||||
stepFilterGroupId: 'group2',
|
||||
leftOperand: 85, // This will pass
|
||||
},
|
||||
];
|
||||
@ -690,14 +675,14 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
it('should return false when one root group fails', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: LogicalOperator.OR,
|
||||
logicalOperator: StepLogicalOperator.OR,
|
||||
},
|
||||
{
|
||||
id: 'group2',
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
logicalOperator: StepLogicalOperator.AND,
|
||||
},
|
||||
];
|
||||
|
||||
@ -708,10 +693,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'John', // This will pass
|
||||
},
|
||||
// Group2 filters (AND logic) - will fail
|
||||
@ -720,10 +704,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Age Filter',
|
||||
rightOperand: 30,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '30',
|
||||
fieldMetadataId: 'field2',
|
||||
recordFilterGroupId: 'group2',
|
||||
stepFilterGroupId: 'group2',
|
||||
leftOperand: 25, // This will fail
|
||||
},
|
||||
{
|
||||
@ -731,10 +714,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'number',
|
||||
label: 'Score Filter',
|
||||
rightOperand: 80,
|
||||
operand: Operand.GT,
|
||||
operand: StepOperand.GT,
|
||||
displayValue: '80',
|
||||
fieldMetadataId: 'field3',
|
||||
recordFilterGroupId: 'group2',
|
||||
stepFilterGroupId: 'group2',
|
||||
leftOperand: 85, // This will pass
|
||||
},
|
||||
];
|
||||
@ -748,10 +730,10 @@ describe('evaluateFilterConditions', () => {
|
||||
|
||||
describe('error cases', () => {
|
||||
it('should throw error when filter group is not found', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
logicalOperator: StepLogicalOperator.AND,
|
||||
},
|
||||
];
|
||||
|
||||
@ -761,10 +743,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'nonexistent-group',
|
||||
stepFilterGroupId: 'nonexistent-group',
|
||||
leftOperand: 'John',
|
||||
},
|
||||
];
|
||||
@ -775,10 +756,10 @@ describe('evaluateFilterConditions', () => {
|
||||
});
|
||||
|
||||
it('should throw error for unknown logical operator', () => {
|
||||
const filterGroups: FilterGroup[] = [
|
||||
const filterGroups: StepFilterGroup[] = [
|
||||
{
|
||||
id: 'group1',
|
||||
logicalOperator: 'UNKNOWN' as LogicalOperator,
|
||||
logicalOperator: 'UNKNOWN' as StepLogicalOperator,
|
||||
},
|
||||
];
|
||||
|
||||
@ -788,10 +769,9 @@ describe('evaluateFilterConditions', () => {
|
||||
type: 'text',
|
||||
label: 'Name Filter',
|
||||
rightOperand: 'John',
|
||||
operand: Operand.EQ,
|
||||
operand: StepOperand.EQ,
|
||||
displayValue: 'John',
|
||||
fieldMetadataId: 'field1',
|
||||
recordFilterGroupId: 'group1',
|
||||
stepFilterGroupId: 'group1',
|
||||
leftOperand: 'John',
|
||||
},
|
||||
];
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
import {
|
||||
Filter,
|
||||
FilterGroup,
|
||||
} from 'src/modules/workflow/workflow-executor/workflow-actions/filter/types/workflow-filter-action-settings.type';
|
||||
import { StepFilter, StepFilterGroup } from 'twenty-shared/types';
|
||||
|
||||
type ResolvedFilter = Omit<Filter, 'value' | 'stepOutputKey'> & {
|
||||
type ResolvedFilter = Omit<StepFilter, 'value' | 'stepOutputKey'> & {
|
||||
rightOperand: unknown;
|
||||
leftOperand: unknown;
|
||||
};
|
||||
@ -72,7 +69,7 @@ function evaluateFilter(filter: ResolvedFilter): boolean {
|
||||
*/
|
||||
function evaluateFilterGroup(
|
||||
groupId: string,
|
||||
filterGroups: FilterGroup[],
|
||||
filterGroups: StepFilterGroup[],
|
||||
filters: ResolvedFilter[],
|
||||
): boolean {
|
||||
const group = filterGroups.find((g) => g.id === groupId);
|
||||
@ -83,14 +80,13 @@ function evaluateFilterGroup(
|
||||
|
||||
// Get all direct child groups
|
||||
const childGroups = filterGroups
|
||||
.filter((g) => g.parentRecordFilterGroupId === groupId)
|
||||
.filter((g) => g.parentStepFilterGroupId === groupId)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(a.positionInRecordFilterGroup || 0) -
|
||||
(b.positionInRecordFilterGroup || 0),
|
||||
(a.positionInStepFilterGroup || 0) - (b.positionInStepFilterGroup || 0),
|
||||
);
|
||||
|
||||
const groupFilters = filters.filter((f) => f.recordFilterGroupId === groupId);
|
||||
const groupFilters = filters.filter((f) => f.stepFilterGroupId === groupId);
|
||||
|
||||
const filterResults = groupFilters.map((filter) => evaluateFilter(filter));
|
||||
|
||||
@ -120,7 +116,7 @@ export function evaluateFilterConditions({
|
||||
filterGroups = [],
|
||||
filters = [],
|
||||
}: {
|
||||
filterGroups?: FilterGroup[];
|
||||
filterGroups?: StepFilterGroup[];
|
||||
filters?: ResolvedFilter[];
|
||||
}): boolean {
|
||||
if (filterGroups.length === 0 && filters.length === 0) {
|
||||
@ -131,15 +127,15 @@ export function evaluateFilterConditions({
|
||||
const groupIds = new Set(filterGroups.map((g) => g.id));
|
||||
|
||||
for (const filter of filters) {
|
||||
if (!groupIds.has(filter.recordFilterGroupId)) {
|
||||
if (!groupIds.has(filter.stepFilterGroupId)) {
|
||||
throw new Error(
|
||||
`Filter group with id ${filter.recordFilterGroupId} not found`,
|
||||
`Filter group with id ${filter.stepFilterGroupId} not found`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rootGroups = filterGroups.filter((g) => !g.parentRecordFilterGroupId);
|
||||
const rootGroups = filterGroups.filter((g) => !g.parentStepFilterGroupId);
|
||||
|
||||
if (rootGroups.length === 0 && filters.length > 0) {
|
||||
const filterResults = filters.map((filter) => evaluateFilter(filter));
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import {
|
||||
WorkflowStepExecutorException,
|
||||
WorkflowStepExecutorExceptionCode,
|
||||
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
|
||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
export const getPreviousStepOutput = (
|
||||
steps: WorkflowAction[],
|
||||
currentStepId: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
context: Record<string, any>,
|
||||
) => {
|
||||
const previousSteps = steps.filter((step) =>
|
||||
step?.nextStepIds?.includes(currentStepId),
|
||||
);
|
||||
|
||||
if (previousSteps.length === 0) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Filter action must have a previous step',
|
||||
WorkflowStepExecutorExceptionCode.FAILED_TO_EXECUTE_STEP,
|
||||
{
|
||||
userFriendlyMessage: t`Filter action must have a previous step`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (previousSteps.length > 1) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Filter action must have only one previous step',
|
||||
WorkflowStepExecutorExceptionCode.FAILED_TO_EXECUTE_STEP,
|
||||
{
|
||||
userFriendlyMessage: t`Filter action must have only one previous step`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const previousStep = previousSteps[0];
|
||||
const previousStepOutput = context[previousStep.id];
|
||||
|
||||
if (!previousStepOutput) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Previous step output not found',
|
||||
WorkflowStepExecutorExceptionCode.FAILED_TO_EXECUTE_STEP,
|
||||
);
|
||||
}
|
||||
|
||||
return previousStepOutput;
|
||||
};
|
||||
@ -15,13 +15,13 @@ import {
|
||||
} from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
|
||||
import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-action.factory';
|
||||
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
|
||||
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
|
||||
import { StepStatus } from 'src/modules/workflow/workflow-executor/types/workflow-run-step-info.type';
|
||||
import {
|
||||
WorkflowBranchExecutorInput,
|
||||
WorkflowExecutorInput,
|
||||
} from 'src/modules/workflow/workflow-executor/types/workflow-executor-input';
|
||||
import { StepStatus } from 'src/modules/workflow/workflow-executor/types/workflow-run-step-info.type';
|
||||
import { canExecuteStep } from 'src/modules/workflow/workflow-executor/utils/can-execute-step.utils';
|
||||
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
|
||||
|
||||
const MAX_RETRIES_ON_FAILURE = 3;
|
||||
|
||||
@ -128,11 +128,11 @@ export class WorkflowExecutorWorkspaceService {
|
||||
|
||||
const actionOutputSuccess = isDefined(actionOutput.result);
|
||||
|
||||
const shouldContinue =
|
||||
const isValidActionOutput =
|
||||
actionOutputSuccess ||
|
||||
stepToExecute.settings.errorHandlingOptions.continueOnFailure.value;
|
||||
|
||||
if (shouldContinue) {
|
||||
if (isValidActionOutput) {
|
||||
await this.workflowRunWorkspaceService.saveWorkflowRunState({
|
||||
workflowRunId,
|
||||
stepOutput,
|
||||
@ -144,7 +144,8 @@ export class WorkflowExecutorWorkspaceService {
|
||||
|
||||
if (
|
||||
!isDefined(stepToExecute.nextStepIds) ||
|
||||
stepToExecute.nextStepIds.length === 0
|
||||
stepToExecute.nextStepIds.length === 0 ||
|
||||
actionOutput.shouldEndWorkflowRun === true
|
||||
) {
|
||||
await this.workflowRunWorkspaceService.endWorkflowRun({
|
||||
workflowRunId,
|
||||
|
||||
Reference in New Issue
Block a user