mirror of
https://github.com/n8n-io/n8n.git
synced 2026-07-28 03:24:59 +02:00
fix(API): Only exclude running executions from the public executions list (#32657)
This commit is contained in:
parent
2472df8e6a
commit
ae50852162
|
|
@ -174,9 +174,12 @@ const executionHandlers: ExecutionHandlers = {
|
|||
return res.status(200).json({ data: [], nextCursor: null });
|
||||
}
|
||||
|
||||
// get running executions so we exclude them from the result
|
||||
// Collect genuinely running executions to exclude from the default listing.
|
||||
// The active executions list also retains `waiting` executions (persisted and
|
||||
// resumable); filter by status so waiting executions are still listed.
|
||||
const runningExecutionsIds = Container.get(ActiveExecutions)
|
||||
.getActiveExecutions()
|
||||
.filter(({ status }) => status === 'running')
|
||||
.map(({ id }) => id);
|
||||
|
||||
const filters: Parameters<
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import {
|
|||
import type { ExecutionEntity, User } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { type ExecutionStatus } from 'n8n-workflow';
|
||||
import type { MockInstance } from 'vitest';
|
||||
|
||||
import { ActiveExecutions } from '@/active-executions';
|
||||
import type { ActiveWorkflowManager } from '@/active-workflow-manager';
|
||||
import { AbortedExecutionRetryError } from '@/errors/aborted-execution-retry.error';
|
||||
import { QueuedExecutionRetryError } from '@/errors/queued-execution-retry.error';
|
||||
|
|
@ -532,6 +534,102 @@ describe('GET /executions', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('with executions held in the active-executions map', () => {
|
||||
// Mirrors a running instance: a `waiting` execution is persisted in the DB
|
||||
// and its id is also held in the in-process active-executions map.
|
||||
let activeExecutionsSpy: MockInstance | undefined;
|
||||
|
||||
const holdInActiveExecutions = (
|
||||
stubs: Array<{ executionId: string; workflowId: string; status: ExecutionStatus }>,
|
||||
) => {
|
||||
activeExecutionsSpy = vi
|
||||
.spyOn(Container.get(ActiveExecutions), 'getActiveExecutions')
|
||||
.mockReturnValue(
|
||||
stubs.map(({ executionId, workflowId, status }) => ({
|
||||
id: executionId,
|
||||
retryOf: undefined,
|
||||
startedAt: new Date(),
|
||||
mode: 'manual',
|
||||
workflowId,
|
||||
status,
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
activeExecutionsSpy?.mockRestore();
|
||||
activeExecutionsSpy = undefined;
|
||||
});
|
||||
|
||||
test.each`
|
||||
query | description
|
||||
${undefined} | ${'in the default list'}
|
||||
${{ status: 'waiting' }} | ${'when filtering by status=waiting'}
|
||||
`(
|
||||
'should return a held waiting execution $description',
|
||||
async ({ query }: { query?: { status: string } }) => {
|
||||
const workflow = await createWorkflow({}, owner);
|
||||
const waitingExecution = await createdExecutionWithStatus(workflow, 'waiting');
|
||||
holdInActiveExecutions([
|
||||
{ executionId: waitingExecution.id, workflowId: workflow.id, status: 'waiting' },
|
||||
]);
|
||||
|
||||
const response = await authOwnerAgent.get('/executions').query(query ?? {});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data.map((e: { id: string }) => e.id)).toContain(waitingExecution.id);
|
||||
},
|
||||
);
|
||||
|
||||
test('should return both a held waiting execution and a finished one in the default list', async () => {
|
||||
const workflow = await createWorkflow({}, owner);
|
||||
const finishedExecution = await createdExecutionWithStatus(workflow, 'success');
|
||||
const waitingExecution = await createdExecutionWithStatus(workflow, 'waiting');
|
||||
holdInActiveExecutions([
|
||||
{ executionId: waitingExecution.id, workflowId: workflow.id, status: 'waiting' },
|
||||
]);
|
||||
|
||||
const response = await authOwnerAgent.get('/executions');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const ids = response.body.data.map((e: { id: string }) => e.id);
|
||||
expect(ids).toContain(finishedExecution.id);
|
||||
expect(ids).toContain(waitingExecution.id);
|
||||
});
|
||||
|
||||
test('should still exclude a genuinely running execution from the default list', async () => {
|
||||
const workflow = await createWorkflow({}, owner);
|
||||
const runningExecution = await createdExecutionWithStatus(workflow, 'running');
|
||||
holdInActiveExecutions([
|
||||
{ executionId: runningExecution.id, workflowId: workflow.id, status: 'running' },
|
||||
]);
|
||||
|
||||
const response = await authOwnerAgent.get('/executions');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data.map((e: { id: string }) => e.id)).not.toContain(
|
||||
runningExecution.id,
|
||||
);
|
||||
});
|
||||
|
||||
test('should return a held waiting execution and exclude a held running execution when both are active', async () => {
|
||||
const workflow = await createWorkflow({}, owner);
|
||||
const waitingExecution = await createdExecutionWithStatus(workflow, 'waiting');
|
||||
const runningExecution = await createdExecutionWithStatus(workflow, 'running');
|
||||
holdInActiveExecutions([
|
||||
{ executionId: waitingExecution.id, workflowId: workflow.id, status: 'waiting' },
|
||||
{ executionId: runningExecution.id, workflowId: workflow.id, status: 'running' },
|
||||
]);
|
||||
|
||||
const response = await authOwnerAgent.get('/executions');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const ids = response.body.data.map((e: { id: string }) => e.id);
|
||||
expect(ids).toContain(waitingExecution.id);
|
||||
expect(ids).not.toContain(runningExecution.id);
|
||||
});
|
||||
});
|
||||
|
||||
test('should retrieve all executions of specific workflow', async () => {
|
||||
const [workflow, workflow2] = await createManyWorkflows(2, {}, owner);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user