fix(editor): Eval trigger node with data table works when underlying data changes (#22389)

This commit is contained in:
Nikhil Kuriakose 2025-11-27 14:29:26 +01:00 committed by GitHub
parent 8336b9e5ee
commit 6f1b09eeda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 155 additions and 3 deletions

View File

@ -208,11 +208,27 @@ export class EvaluationTrigger implements INodeType {
const dataTableProxy = await this.helpers.getDataTableProxy(dataTableId);
const filter = await getDataTableFilter(this, 0);
const previousRunRowId = inputData?.[0]?.json?.row_id;
let effectiveFilter = filter;
if (typeof previousRunRowId === 'number' && previousRunRowsLeft !== 0) {
effectiveFilter = {
type: 'and',
filters: [
...filter.filters,
{
columnName: 'id',
condition: 'gt',
value: previousRunRowId,
},
],
};
}
const { data, count } = await dataTableProxy.getManyRowsAndCount({
skip: currentIndex,
skip: 0,
take: 1,
filter,
filter: effectiveFilter,
});
if (data.length === 0) {
@ -220,7 +236,7 @@ export class EvaluationTrigger implements INodeType {
}
const effectiveTotal = Math.min(count, maxRows);
const rowsLeft = Math.max(0, effectiveTotal - (currentIndex + 1));
const rowsLeft = Math.max(0, effectiveTotal - 1);
const currentRow = {
json: {

View File

@ -316,6 +316,142 @@ describe('Evaluation Trigger Node', () => {
]);
});
});
describe('Data tables with filters', () => {
beforeEach(() => {
jest.resetAllMocks();
mockDataTable = {
getManyRowsAndCount: jest.fn(),
getColumns: jest.fn().mockResolvedValue([{ name: 'processed', type: 'number' }]),
};
mockExecuteFunctions = mockDeep<IExecuteFunctions>({
getNode: jest.fn().mockReturnValue({ typeVersion: 4.7 }),
helpers: {
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTable),
},
});
});
test('should process rows sequentially with filters when dataset changes', async () => {
// Simulate the user's scenario: 5 rows with processed=1, updating to processed=2 after each execution
// With each execution, one row is processed and thus no longer matches the filter
mockDataTable.getManyRowsAndCount
.mockResolvedValueOnce({
data: [{ id: 1, processed: 1 }],
count: 5,
})
.mockResolvedValueOnce({
data: [{ id: 2, processed: 1 }],
count: 4,
})
.mockResolvedValueOnce({
data: [{ id: 3, processed: 1 }],
count: 3,
});
mockExecuteFunctions.getNodeParameter.mockImplementation(
(key: string, _: number, fallbackValue?: string | number | boolean | object) => {
const mockParams: { [key: string]: unknown } = {
source: 'dataTable',
limitRows: false,
dataTableId: 'mockDataTableId',
'filters.conditions': [
{
keyName: 'processed',
condition: 'eq',
keyValue: '1',
},
],
matchType: 'anyCondition',
};
return (mockParams[key] ?? fallbackValue) as NodeParameterValueType;
},
);
const evaluationTrigger = new EvaluationTrigger();
// First execution - no previous data
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]);
const result1 = await evaluationTrigger.execute.call(mockExecuteFunctions);
expect(result1[0][0].json.row_id).toBe(1);
expect(result1[0][0].json.row_number).toBe(0);
expect(result1[0][0].json._rowsLeft).toBe(4);
// Verify first call used user filter only (no id filter yet)
expect(mockDataTable.getManyRowsAndCount).toHaveBeenNthCalledWith(1, {
skip: 0,
take: 1,
filter: {
type: 'or',
filters: [
{
columnName: 'processed',
condition: 'eq',
value: '1',
},
],
},
});
// Second execution - previous row was id=1
mockExecuteFunctions.getInputData.mockReturnValue(result1[0]);
const result2 = await evaluationTrigger.execute.call(mockExecuteFunctions);
expect(result2[0][0].json.row_id).toBe(2);
expect(result2[0][0].json.row_number).toBe(1);
// Verify second call includes id > 1 filter
expect(mockDataTable.getManyRowsAndCount).toHaveBeenNthCalledWith(2, {
skip: 0,
take: 1,
filter: {
type: 'and',
filters: [
{
columnName: 'processed',
condition: 'eq',
value: '1',
},
{
columnName: 'id',
condition: 'gt',
value: 1,
},
],
},
});
// Third execution - previous row was id=2
mockExecuteFunctions.getInputData.mockReturnValue(result2[0]);
const result3 = await evaluationTrigger.execute.call(mockExecuteFunctions);
expect(result3[0][0].json.row_id).toBe(3);
expect(result3[0][0].json.row_number).toBe(2);
// Verify third call includes id > 2 filter
expect(mockDataTable.getManyRowsAndCount).toHaveBeenNthCalledWith(3, {
skip: 0,
take: 1,
filter: {
type: 'and',
filters: [
{
columnName: 'processed',
condition: 'eq',
value: '1',
},
{
columnName: 'id',
condition: 'gt',
value: 2,
},
],
},
});
});
});
});
describe('customOperations.dataset.getRows', () => {