fix(core): Allow insights breakdown by workflow to be sorted by workflow name (#17184)

This commit is contained in:
Csaba Tuncsik
2025-07-14 10:10:03 +02:00
committed by GitHub
parent 232b8f6cb1
commit d002cc3f7d
5 changed files with 530 additions and 11 deletions

View File

@@ -1,7 +1,9 @@
import type { InsightsDateRange } from '@n8n/api-types';
import { mockInstance } from '@n8n/backend-test-utils';
import { mockInstance, createWorkflow, createTeamProject, testDb } from '@n8n/backend-test-utils';
import { DateTime } from 'luxon';
import { Telemetry } from '@/telemetry';
import { createCompactedInsightsEvent } from '@/modules/insights/database/entities/__tests__/db-utils';
import { createUser } from '../shared/db/users';
import type { SuperAgentTest } from '../shared/types';
@@ -116,11 +118,20 @@ describe('GET /insights/by-workflow', () => {
features: ['feat:insights:viewSummary', 'feat:insights:viewDashboard'],
});
});
test('Call should work with valid query parameters', async () => {
await agents.owner
.get('/insights/by-workflow')
.query({ skip: '10', take: '20', sortBy: 'total:desc' })
.expect(200);
test.each([
{
skip: '10',
take: '20',
sortBy: 'total:desc',
},
{
skip: '1',
take: '25',
sortBy: 'workflowName:asc',
},
])('Call should work with valid query parameters: %s', async (queryParams) => {
await agents.owner.get('/insights/by-workflow').query(queryParams).expect(200);
});
test.each<{ skip: string; take?: string; sortBy?: string }>([
@@ -149,4 +160,168 @@ describe('GET /insights/by-workflow', () => {
})
.expect(400);
});
test.each([
'total:asc',
'total:desc',
'succeeded:asc',
'succeeded:desc',
'failed:asc',
'failed:desc',
'failureRate:asc',
'failureRate:desc',
'timeSaved:asc',
'timeSaved:desc',
'runTime:asc',
'runTime:desc',
'averageRunTime:asc',
'averageRunTime:desc',
'workflowName:asc',
'workflowName:desc',
])('Call should return 200 with valid sortBy option: %s', async (sortBy) => {
const response = await agents.owner.get('/insights/by-workflow').query({ sortBy }).expect(200);
expect(response.body).toHaveProperty('data');
expect(response.body.data).toHaveProperty('count');
expect(Array.isArray(response.body.data.data)).toBe(true);
});
describe('sorting order verification', () => {
afterEach(async () => {
await testDb.truncate([
'InsightsRaw',
'InsightsByPeriod',
'InsightsMetadata',
'SharedWorkflow',
'WorkflowEntity',
'Project',
]);
});
test('should return workflows sorted by total:desc', async () => {
const project = await createTeamProject('Test Project 1');
const testData = [
{ name: 'Workflow A', total: 10 },
{ name: 'Workflow B', total: 5 },
{ name: 'Workflow C', total: 15 },
];
const workflows = await Promise.all(
testData.map(async (data) => await createWorkflow({ name: data.name }, project)),
);
const periodStart = DateTime.utc().startOf('day');
await Promise.all(
workflows.map(
async (workflow, index) =>
await createCompactedInsightsEvent(workflow, {
type: 'success',
value: testData[index].total,
periodUnit: 'day',
periodStart,
}),
),
);
const response = await agents.owner
.get('/insights/by-workflow')
.query({ sortBy: 'total:desc' })
.expect(200);
expect(response.body.data.count).toBe(3);
expect(response.body.data.data).toHaveLength(3);
// Verify descending order by total
const expectedOrder = [15, 10, 5];
response.body.data.data.forEach((item: any, index: number) => {
expect(item.total).toBe(expectedOrder[index]);
});
});
test('should return workflows sorted by workflowName:asc', async () => {
const project = await createTeamProject('Test Project A');
const workflowNames = ['Zebra Workflow', 'Alpha Workflow', 'Beta Workflow'];
const workflows = await Promise.all(
workflowNames.map(async (name) => await createWorkflow({ name }, project)),
);
const periodStart = DateTime.utc().startOf('day');
await Promise.all(
workflows.map(
async (workflow) =>
await createCompactedInsightsEvent(workflow, {
type: 'success',
value: 5,
periodUnit: 'day',
periodStart,
}),
),
);
const response = await agents.owner
.get('/insights/by-workflow')
.query({ sortBy: 'workflowName:asc' })
.expect(200);
expect(response.body.data.count).toBe(3);
expect(response.body.data.data).toHaveLength(3);
// Verify ascending order by workflow name
const expectedOrder = ['Alpha Workflow', 'Beta Workflow', 'Zebra Workflow'];
response.body.data.data.forEach((item: any, index: number) => {
expect(item.workflowName).toBe(expectedOrder[index]);
});
});
test('should return workflows sorted by failureRate:asc', async () => {
const project = await createTeamProject('Another Test Project');
const testData = [
{ name: 'Low Failure', success: 9, failure: 1 }, // 10% failure rate
{ name: 'High Failure', success: 2, failure: 8 }, // 80% failure rate
{ name: 'Medium Failure', success: 5, failure: 5 }, // 50% failure rate
];
const workflows = await Promise.all(
testData.map(async (data) => await createWorkflow({ name: data.name }, project)),
);
const periodStart = DateTime.utc().startOf('day');
// Create insights events for each workflow individually to avoid race conditions
for (const [index, workflow] of workflows.entries()) {
const data = testData[index];
await createCompactedInsightsEvent(workflow, {
type: 'success',
value: data.success,
periodUnit: 'day',
periodStart,
});
await createCompactedInsightsEvent(workflow, {
type: 'failure',
value: data.failure,
periodUnit: 'day',
periodStart,
});
}
const response = await agents.owner
.get('/insights/by-workflow')
.query({ sortBy: 'failureRate:asc' })
.expect(200);
expect(response.body.data.count).toBe(3);
expect(response.body.data.data).toHaveLength(3);
// Verify ascending order by failure rate
const expectedOrder = [0.1, 0.5, 0.8]; // 10%, 50%, 80%
response.body.data.data.forEach((item: any, index: number) => {
expect(item.failureRate).toBe(expectedOrder[index]);
});
});
});
});