fix(core): Fix sorting of executions not working on postgres and mysql (#15423)

This commit is contained in:
Danny Martini
2025-05-26 16:23:24 +02:00
committed by GitHub
parent 74d8afa52e
commit eca282d09c
3 changed files with 57 additions and 9 deletions

View File

@@ -1048,13 +1048,32 @@ export class ExecutionRepository extends Repository<ExecutionEntity> {
subQuery.leftJoin('execution.annotation', 'annotation'); subQuery.leftJoin('execution.annotation', 'annotation');
} }
return this.manager const qb = this.manager
.createQueryBuilder() .createQueryBuilder()
.select(['e.*', 'ate.id AS "annotation_tags_id"', 'ate.name AS "annotation_tags_name"']) .select(['e.*', 'ate.id AS "annotation_tags_id"', 'ate.name AS "annotation_tags_name"'])
.from(`(${subQuery.getQuery()})`, 'e') .from(`(${subQuery.getQuery()})`, 'e')
.setParameters(subQuery.getParameters()) .setParameters(subQuery.getParameters())
.leftJoin(AnnotationTagMapping, 'atm', 'atm.annotationId = e.annotation_id') .leftJoin(AnnotationTagMapping, 'atm', 'atm.annotationId = e.annotation_id')
.leftJoin(AnnotationTagEntity, 'ate', 'ate.id = atm.tagId'); .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() { async getAllIds() {

View File

@@ -1,5 +1,6 @@
import { ExecutionRepository } from '@n8n/db'; import { ExecutionRepository } from '@n8n/db';
import { Container } from '@n8n/di'; import { Container } from '@n8n/di';
import { DateTime } from 'luxon';
import { createExecution } from '@test-integration/db/executions'; import { createExecution } from '@test-integration/db/executions';
import { createWorkflow } from '@test-integration/db/workflows'; import { createWorkflow } from '@test-integration/db/workflows';
@@ -23,12 +24,30 @@ describe('UserRepository', () => {
}); });
describe('findManyByRangeQuery', () => { describe('findManyByRangeQuery', () => {
// eslint-disable-next-line n8n-local-rules/no-skipped-tests test('sort by `createdAt` if `startedAt` is null', async () => {
test.skip('sort by `createdAt` if `startedAt` is null', async () => { const now = DateTime.utc();
const workflow = await createWorkflow(); const workflow = await createWorkflow();
const execution1 = await createExecution({}, workflow); const execution1 = await createExecution(
const execution2 = await createExecution({ startedAt: null }, workflow); {
const execution3 = await createExecution({}, workflow); 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({ const executions = await executionRepository.findManyByRangeQuery({
workflowId: workflow.id, workflowId: workflow.id,

View File

@@ -32,13 +32,23 @@ export async function createExecution(
>, >,
workflow: IWorkflowBase, workflow: IWorkflowBase,
) { ) {
const { data, finished, mode, startedAt, stoppedAt, waitTill, status, deletedAt, metadata } = const {
attributes; data,
finished,
mode,
startedAt,
stoppedAt,
waitTill,
status,
deletedAt,
metadata,
createdAt,
} = attributes;
const execution = await Container.get(ExecutionRepository).save({ const execution = await Container.get(ExecutionRepository).save({
finished: finished ?? true, finished: finished ?? true,
mode: mode ?? 'manual', mode: mode ?? 'manual',
createdAt: new Date(), createdAt: createdAt ?? new Date(),
startedAt: startedAt === undefined ? new Date() : startedAt, startedAt: startedAt === undefined ? new Date() : startedAt,
...(workflow !== undefined && { workflowId: workflow.id }), ...(workflow !== undefined && { workflowId: workflow.id }),
stoppedAt: stoppedAt ?? new Date(), stoppedAt: stoppedAt ?? new Date(),