diff --git a/packages/@n8n/db/src/repositories/execution.repository.ts b/packages/@n8n/db/src/repositories/execution.repository.ts index c4f1b4392b..8e02b88656 100644 --- a/packages/@n8n/db/src/repositories/execution.repository.ts +++ b/packages/@n8n/db/src/repositories/execution.repository.ts @@ -1048,13 +1048,32 @@ export class ExecutionRepository extends Repository { subQuery.leftJoin('execution.annotation', 'annotation'); } - return this.manager + const qb = this.manager .createQueryBuilder() .select(['e.*', 'ate.id AS "annotation_tags_id"', 'ate.name AS "annotation_tags_name"']) .from(`(${subQuery.getQuery()})`, 'e') .setParameters(subQuery.getParameters()) .leftJoin(AnnotationTagMapping, 'atm', 'atm.annotationId = e.annotation_id') .leftJoin(AnnotationTagEntity, 'ate', 'ate.id = atm.tagId'); + + // Sort the final result after the joins again, because there is no + // guarantee that the order is unchanged after performing joins. Especially + // postgres and MySQL returned to the natural order again, listing + // executions in the order they were created. + if (query.kind === 'range') { + if (query.order?.startedAt === 'DESC') { + const table = qb.escape('e'); + const startedAt = qb.escape('startedAt'); + const createdAt = qb.escape('createdAt'); + qb.orderBy({ [`COALESCE(${table}.${startedAt}, ${table}.${createdAt})`]: 'DESC' }); + } else if (query.order?.top) { + qb.orderBy(`(CASE WHEN e.status = '${query.order.top}' THEN 0 ELSE 1 END)`); + } else { + qb.orderBy({ 'e.id': 'DESC' }); + } + } + + return qb; } async getAllIds() { diff --git a/packages/cli/test/integration/execution.repository.test.ts b/packages/cli/test/integration/execution.repository.test.ts index 1079699d7d..84c03681eb 100644 --- a/packages/cli/test/integration/execution.repository.test.ts +++ b/packages/cli/test/integration/execution.repository.test.ts @@ -1,5 +1,6 @@ import { ExecutionRepository } from '@n8n/db'; import { Container } from '@n8n/di'; +import { DateTime } from 'luxon'; import { createExecution } from '@test-integration/db/executions'; import { createWorkflow } from '@test-integration/db/workflows'; @@ -23,12 +24,30 @@ describe('UserRepository', () => { }); describe('findManyByRangeQuery', () => { - // eslint-disable-next-line n8n-local-rules/no-skipped-tests - test.skip('sort by `createdAt` if `startedAt` is null', async () => { + test('sort by `createdAt` if `startedAt` is null', async () => { + const now = DateTime.utc(); const workflow = await createWorkflow(); - const execution1 = await createExecution({}, workflow); - const execution2 = await createExecution({ startedAt: null }, workflow); - const execution3 = await createExecution({}, workflow); + const execution1 = await createExecution( + { + createdAt: now.plus({ minute: 1 }).toJSDate(), + startedAt: now.plus({ minute: 1 }).toJSDate(), + }, + workflow, + ); + const execution2 = await createExecution( + { + createdAt: now.plus({ minute: 2 }).toJSDate(), + startedAt: null, + }, + workflow, + ); + const execution3 = await createExecution( + { + createdAt: now.plus({ minute: 3 }).toJSDate(), + startedAt: now.plus({ minute: 3 }).toJSDate(), + }, + workflow, + ); const executions = await executionRepository.findManyByRangeQuery({ workflowId: workflow.id, diff --git a/packages/cli/test/integration/shared/db/executions.ts b/packages/cli/test/integration/shared/db/executions.ts index b8bb0e3804..c164757c6b 100644 --- a/packages/cli/test/integration/shared/db/executions.ts +++ b/packages/cli/test/integration/shared/db/executions.ts @@ -32,13 +32,23 @@ export async function createExecution( >, workflow: IWorkflowBase, ) { - const { data, finished, mode, startedAt, stoppedAt, waitTill, status, deletedAt, metadata } = - attributes; + const { + data, + finished, + mode, + startedAt, + stoppedAt, + waitTill, + status, + deletedAt, + metadata, + createdAt, + } = attributes; const execution = await Container.get(ExecutionRepository).save({ finished: finished ?? true, mode: mode ?? 'manual', - createdAt: new Date(), + createdAt: createdAt ?? new Date(), startedAt: startedAt === undefined ? new Date() : startedAt, ...(workflow !== undefined && { workflowId: workflow.id }), stoppedAt: stoppedAt ?? new Date(),