mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-01 01:07:04 +02:00
fix(editor): Eval trigger node with data table works when underlying data changes (#22389)
This commit is contained in:
parent
8336b9e5ee
commit
6f1b09eeda
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user