mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-01 17:27:14 +02:00
fix(core): Return insights when only one day is selected (#20543)
This commit is contained in:
parent
d7a70d643e
commit
dc72c23d6a
|
|
@ -229,6 +229,7 @@ describe('InsightsService', () => {
|
|||
|
||||
test('mixed period data are summarized correctly', async () => {
|
||||
// ARRANGE
|
||||
|
||||
// current period
|
||||
await createCompactedInsightsEvent(workflow, {
|
||||
type: 'success',
|
||||
|
|
@ -338,11 +339,11 @@ describe('InsightsService', () => {
|
|||
|
||||
// ASSERT
|
||||
expect(summary).toEqual({
|
||||
averageRunTime: { value: 0, unit: 'millisecond', deviation: -7157.8 },
|
||||
averageRunTime: { value: 0, unit: 'millisecond', deviation: -8947.25 },
|
||||
failed: { value: 20, unit: 'count', deviation: 18 },
|
||||
failureRate: { value: 0.909, unit: 'ratio', deviation: 0.509 },
|
||||
failureRate: { value: 0.87, unit: 'ratio', deviation: 0.37 },
|
||||
timeSaved: { value: 0, unit: 'minute', deviation: -15 },
|
||||
total: { value: 22, unit: 'count', deviation: 17 },
|
||||
total: { value: 23, unit: 'count', deviation: 19 },
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -687,7 +688,7 @@ describe('InsightsService', () => {
|
|||
type: 'success',
|
||||
value: 1,
|
||||
periodUnit: 'hour',
|
||||
periodStart: now.minus({ days: 13, hours: 23 }),
|
||||
periodStart: now.minus({ days: 14 }).startOf('day'),
|
||||
});
|
||||
|
||||
// Out of date range insight (should not be included)
|
||||
|
|
@ -696,7 +697,7 @@ describe('InsightsService', () => {
|
|||
type: 'success',
|
||||
value: 1,
|
||||
periodUnit: 'day',
|
||||
periodStart: now.minus({ days: 14 }),
|
||||
periodStart: now.minus({ days: 15 }),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -843,21 +844,20 @@ describe('InsightsService', () => {
|
|||
});
|
||||
|
||||
// Barely in range insight (should be included)
|
||||
// 1 hour before 14 days ago
|
||||
await createCompactedInsightsEvent(workflow, {
|
||||
type: workflow === workflow1 ? 'success' : 'failure',
|
||||
value: 1,
|
||||
periodUnit: 'hour',
|
||||
periodStart: now.minus({ days: 13, hours: 23 }),
|
||||
periodStart: now.minus({ days: 14 }).startOf('day'),
|
||||
});
|
||||
|
||||
// Out of date range insight (should not be included)
|
||||
// 14 days ago
|
||||
// 15 days ago
|
||||
await createCompactedInsightsEvent(workflow, {
|
||||
type: 'success',
|
||||
value: 1,
|
||||
periodUnit: 'day',
|
||||
periodStart: now.minus({ days: 14 }),
|
||||
periodStart: now.minus({ days: 15 }),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1001,12 +1001,11 @@ describe('InsightsService', () => {
|
|||
});
|
||||
|
||||
// Barely in range insight (should be included)
|
||||
// 1 hour before 14 days ago
|
||||
await createCompactedInsightsEvent(workflow, {
|
||||
type: workflow === workflow1 ? 'success' : 'failure',
|
||||
value: 1,
|
||||
periodUnit: 'hour',
|
||||
periodStart: DateTime.utc().minus({ days: 13, hours: 23 }),
|
||||
periodStart: DateTime.utc().minus({ days: 14 }).startOf('day'),
|
||||
});
|
||||
|
||||
// Out of date range insight (should not be included)
|
||||
|
|
@ -1015,7 +1014,7 @@ describe('InsightsService', () => {
|
|||
type: 'success',
|
||||
value: 1,
|
||||
periodUnit: 'day',
|
||||
periodStart: DateTime.utc().minus({ days: 14 }),
|
||||
periodStart: DateTime.utc().minus({ days: 15 }),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1199,25 +1198,29 @@ describe('InsightsService', () => {
|
|||
licenseStateMock.isInsightsHourlyDataLicensed.mockReturnValue(false);
|
||||
licenseStateMock.getInsightsMaxHistory.mockReturnValue(30);
|
||||
|
||||
const today = DateTime.now().startOf('day');
|
||||
const startDate = today.minus({ hours: 12 }).toJSDate();
|
||||
const endDate = today.toJSDate();
|
||||
const startDate = DateTime.now().minus({ days: 3 }).startOf('day');
|
||||
const endDate = startDate.plus({ hours: 10 });
|
||||
|
||||
expect(() => insightsService.validateDateFiltersLicense({ startDate, endDate })).toThrowError(
|
||||
new UserError('Hourly data is not available with your current license'),
|
||||
);
|
||||
expect(() =>
|
||||
insightsService.validateDateFiltersLicense({
|
||||
startDate: startDate.toJSDate(),
|
||||
endDate: endDate.toJSDate(),
|
||||
}),
|
||||
).toThrowError(new UserError('Hourly data is not available with your current license'));
|
||||
});
|
||||
|
||||
test('does not throw if granularity is hour and hourly data is licensed', () => {
|
||||
licenseStateMock.isInsightsHourlyDataLicensed.mockReturnValue(true);
|
||||
licenseStateMock.getInsightsMaxHistory.mockReturnValue(30);
|
||||
|
||||
const today = DateTime.now().startOf('day');
|
||||
const startDate = today.minus({ hours: 12 }).toJSDate();
|
||||
const endDate = today.toJSDate();
|
||||
const startDate = DateTime.now().minus({ days: 3 }).startOf('day');
|
||||
const endDate = startDate.endOf('day');
|
||||
|
||||
expect(() =>
|
||||
insightsService.validateDateFiltersLicense({ startDate, endDate }),
|
||||
insightsService.validateDateFiltersLicense({
|
||||
startDate: startDate.toJSDate(),
|
||||
endDate: endDate.toJSDate(),
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,502 @@
|
|||
import type { DatabaseConfig } from '@n8n/config';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { getDateRangesCommonTableExpressionQuery } from '../insights-by-period-query.helper';
|
||||
|
||||
describe('getDateRangesCommonTableExpressionQuery', () => {
|
||||
const now = DateTime.utc(2025, 10, 8, 8, 51, 27);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(now.toJSDate());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe.each([
|
||||
['sqlite', 'SQLite'],
|
||||
['postgresdb', 'PostgreSQL'],
|
||||
['mysqldb', 'MySQL'],
|
||||
['mariadb', 'MariaDB'],
|
||||
])('%s', (dbType: DatabaseConfig['type']) => {
|
||||
describe('hour periodicity (1 day - startDate == endDate)', () => {
|
||||
test('last 24 hours (endDate is today)', () => {
|
||||
const startDate = now.startOf('day').toJSDate();
|
||||
const endDate = now.startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-2 days')"); // prev_start_date
|
||||
expect(result).toContain("datetime('now', '-1 days')"); // start_date
|
||||
expect(result).toContain("datetime('now')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("NOW() - INTERVAL '2 days'"); // prev_start_date
|
||||
expect(result).toContain("NOW() - INTERVAL '1 days'"); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE_SUB(NOW(), INTERVAL 2 DAY)'); // prev_start_date
|
||||
expect(result).toContain('DATE_SUB(NOW(), INTERVAL 1 DAY)'); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('yesterday (specific day)', () => {
|
||||
const startDate = now.minus({ days: 1 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 1 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-2 days', 'start of day')"); // prev_start_date
|
||||
expect(result).toContain("datetime('now', '-1 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '2 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '1 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW())"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 2 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 1 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(NOW())'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('7 days ago (specific day)', () => {
|
||||
const startDate = now.minus({ days: 7 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 7 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-8 days', 'start of day')"); // prev_start_date
|
||||
expect(result).toContain("datetime('now', '-7 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', '-6 days', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '8 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '7 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '6 days')"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 8 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 7 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 6 DAY))'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('14 days ago (specific day)', () => {
|
||||
const startDate = now.minus({ days: 14 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 14 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-15 days', 'start of day')"); // prev_start_date
|
||||
expect(result).toContain("datetime('now', '-14 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', '-13 days', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '15 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '14 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '13 days')"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 15 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 14 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 13 DAY))'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('X days ago (specific day far in the past)', () => {
|
||||
// 109 days ago (2025-06-21)
|
||||
const startDate = now.minus({ days: 109 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 109 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-110 days', 'start of day')"); // prev_start_date
|
||||
expect(result).toContain("datetime('now', '-109 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', '-108 days', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '110 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '109 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '108 days')"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 110 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 109 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 108 DAY))'); // end_date
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('day periodicity (2-30 days)', () => {
|
||||
test('last 7 days (endDate is today)', () => {
|
||||
const startDate = now.minus({ days: 6 }).startOf('day').toJSDate();
|
||||
const endDate = now.startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-12 days', 'start of day')"); // prev_start_date (6 + 6)
|
||||
expect(result).toContain("datetime('now', '-6 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '12 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '6 days')"); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 12 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 6 DAY))'); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('last 14 days (endDate is today)', () => {
|
||||
const startDate = now.minus({ days: 13 }).startOf('day').toJSDate();
|
||||
const endDate = now.startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-26 days', 'start of day')"); // prev_start_date (13 + 13)
|
||||
expect(result).toContain("datetime('now', '-13 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '26 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '13 days')"); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 26 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 13 DAY))'); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('last 30 days (endDate is today)', () => {
|
||||
const startDate = now.minus({ days: 29 }).startOf('day').toJSDate();
|
||||
const endDate = now.startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-58 days', 'start of day')"); // prev_start_date (29 + 29)
|
||||
expect(result).toContain("datetime('now', '-29 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '58 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '29 days')"); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 58 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 29 DAY))'); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('2 days range (specific historical range)', () => {
|
||||
const startDate = now.minus({ days: 2 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 1 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-3 days', 'start of day')"); // prev_start_date (2 + 1)
|
||||
expect(result).toContain("datetime('now', '-2 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '3 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '2 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW())"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 3 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 2 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(NOW())'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('5 days range (specific historical range)', () => {
|
||||
const startDate = now.minus({ days: 10 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 6 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-14 days', 'start of day')"); // prev_start_date (10 + 4)
|
||||
expect(result).toContain("datetime('now', '-10 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', '-5 days', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '14 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '10 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '5 days')"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 14 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 10 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 5 DAY))'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('7 days range (specific historical range)', () => {
|
||||
const startDate = now.minus({ days: 12 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 6 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-18 days', 'start of day')"); // prev_start_date (12 + 6)
|
||||
expect(result).toContain("datetime('now', '-12 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', '-5 days', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '18 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '12 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '5 days')"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 18 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 12 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 5 DAY))'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('14 days range (specific historical range)', () => {
|
||||
const startDate = now.minus({ days: 14 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 1 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-27 days', 'start of day')"); // prev_start_date (14 + 13)
|
||||
expect(result).toContain("datetime('now', '-14 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '27 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '14 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW())"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 27 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 14 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(NOW())'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('30 days range (specific historical range)', () => {
|
||||
const startDate = now.minus({ days: 30 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 1 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-59 days', 'start of day')"); // prev_start_date (30 + 29)
|
||||
expect(result).toContain("datetime('now', '-30 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '59 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '30 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW())"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 59 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 30 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(NOW())'); // end_date
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('week periodicity (31+ days)', () => {
|
||||
test('last 90 days (endDate is today)', () => {
|
||||
const startDate = now.minus({ days: 89 }).startOf('day').toJSDate();
|
||||
const endDate = now.startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-178 days', 'start of day')"); // prev_start_date (89 + 89)
|
||||
expect(result).toContain("datetime('now', '-89 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '178 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '89 days')"); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 178 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 89 DAY))'); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('last 6 months (endDate is today)', () => {
|
||||
const startDate = now.minus({ months: 6 }).startOf('day').toJSDate();
|
||||
const endDate = now.startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
const daysBack = Math.floor(now.diff(DateTime.fromJSDate(startDate), 'days').days);
|
||||
const prevDaysBack = daysBack * 2;
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain(`datetime('now', '-${prevDaysBack} days', 'start of day')`); // prev_start_date
|
||||
expect(result).toContain(`datetime('now', '-${daysBack} days', 'start of day')`); // start_date
|
||||
expect(result).toContain("datetime('now')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain(`DATE_TRUNC('day', NOW() - INTERVAL '${prevDaysBack} days')`); // prev_start_date
|
||||
expect(result).toContain(`DATE_TRUNC('day', NOW() - INTERVAL '${daysBack} days')`); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
} else {
|
||||
expect(result).toContain(`DATE(DATE_SUB(NOW(), INTERVAL ${prevDaysBack} DAY))`); // prev_start_date
|
||||
expect(result).toContain(`DATE(DATE_SUB(NOW(), INTERVAL ${daysBack} DAY))`); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('last year (endDate is today)', () => {
|
||||
const startDate = now.minus({ years: 1 }).startOf('day').toJSDate();
|
||||
const endDate = now.startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
const daysBack = Math.floor(now.diff(DateTime.fromJSDate(startDate), 'days').days);
|
||||
const prevDaysBack = daysBack * 2;
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain(`datetime('now', '-${prevDaysBack} days', 'start of day')`); // prev_start_date
|
||||
expect(result).toContain(`datetime('now', '-${daysBack} days', 'start of day')`); // start_date
|
||||
expect(result).toContain("datetime('now')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain(`DATE_TRUNC('day', NOW() - INTERVAL '${prevDaysBack} days')`); // prev_start_date
|
||||
expect(result).toContain(`DATE_TRUNC('day', NOW() - INTERVAL '${daysBack} days')`); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
} else {
|
||||
expect(result).toContain(`DATE(DATE_SUB(NOW(), INTERVAL ${prevDaysBack} DAY))`); // prev_start_date
|
||||
expect(result).toContain(`DATE(DATE_SUB(NOW(), INTERVAL ${daysBack} DAY))`); // start_date
|
||||
expect(result).toContain('NOW()'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('31 days range (specific historical range)', () => {
|
||||
const startDate = now.minus({ days: 31 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 1 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-61 days', 'start of day')"); // prev_start_date (31 + 30)
|
||||
expect(result).toContain("datetime('now', '-31 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '61 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '31 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW())"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 61 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 31 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(NOW())'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('90 days range (specific historical range)', () => {
|
||||
const startDate = now.minus({ days: 90 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 1 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-179 days', 'start of day')"); // prev_start_date (90 + 89)
|
||||
expect(result).toContain("datetime('now', '-90 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '179 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '90 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW())"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 179 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 90 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(NOW())'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('180 days range (specific historical range)', () => {
|
||||
const startDate = now.minus({ days: 180 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 1 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-359 days', 'start of day')"); // prev_start_date (180 + 179)
|
||||
expect(result).toContain("datetime('now', '-180 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '359 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '180 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW())"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 359 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 180 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(NOW())'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('360 days range (specific historical range)', () => {
|
||||
const startDate = now.minus({ days: 360 }).startOf('day').toJSDate();
|
||||
const endDate = now.minus({ days: 1 }).startOf('day').toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-719 days', 'start of day')"); // prev_start_date (360 + 359)
|
||||
expect(result).toContain("datetime('now', '-360 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '719 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '360 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW())"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 719 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 360 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(NOW())'); // end_date
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
test('handles date with time component correctly', () => {
|
||||
// Date with time should be treated as start of day
|
||||
const startDate = DateTime.utc(2025, 10, 6, 14, 30, 0).toJSDate();
|
||||
const endDate = DateTime.utc(2025, 10, 7, 18, 45, 30).toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
// Should calculate based on start of day values (2-day range)
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-3 days', 'start of day')"); // prev_start_date (2 + 1)
|
||||
expect(result).toContain("datetime('now', '-2 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '3 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '2 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW())"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 3 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 2 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(NOW())'); // end_date
|
||||
}
|
||||
});
|
||||
|
||||
test('handles same day with different times correctly (hour periodicity)', () => {
|
||||
const startDate = DateTime.utc(2025, 10, 7, 9, 0, 0).toJSDate();
|
||||
const endDate = DateTime.utc(2025, 10, 7, 17, 0, 0).toJSDate();
|
||||
|
||||
const result = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
// Should treat as single day (yesterday) - hour periodicity
|
||||
if (dbType === 'sqlite') {
|
||||
expect(result).toContain("datetime('now', '-2 days', 'start of day')"); // prev_start_date (1 + 1)
|
||||
expect(result).toContain("datetime('now', '-1 days', 'start of day')"); // start_date
|
||||
expect(result).toContain("datetime('now', 'start of day')"); // end_date
|
||||
} else if (dbType === 'postgresdb') {
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '2 days')"); // prev_start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW() - INTERVAL '1 days')"); // start_date
|
||||
expect(result).toContain("DATE_TRUNC('day', NOW())"); // end_date
|
||||
} else {
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 2 DAY))'); // prev_start_date
|
||||
expect(result).toContain('DATE(DATE_SUB(NOW(), INTERVAL 1 DAY))'); // start_date
|
||||
expect(result).toContain('DATE(NOW())'); // end_date
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
import type { DatabaseConfig } from '@n8n/config';
|
||||
import { sql } from '@n8n/db';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
/**
|
||||
* Generates database-specific SQL for a datetime value relative to now
|
||||
* @param dbType - The database type
|
||||
* @param daysFromToday - Number of days back from today (0 = now)
|
||||
* @param useStartOfDay - Whether to truncate to start of day (00:00:00)
|
||||
*/
|
||||
const getDatetimeSql = ({
|
||||
dbType,
|
||||
daysFromToday,
|
||||
useStartOfDay = false,
|
||||
}: {
|
||||
dbType: DatabaseConfig['type'];
|
||||
daysFromToday: number;
|
||||
useStartOfDay?: boolean;
|
||||
}): string => {
|
||||
// Handle "now" case
|
||||
if (daysFromToday === 0 && !useStartOfDay) {
|
||||
return dbType === 'sqlite' ? "datetime('now')" : 'NOW()';
|
||||
}
|
||||
|
||||
// SQLite
|
||||
if (dbType === 'sqlite') {
|
||||
if (daysFromToday === 0 && useStartOfDay) {
|
||||
return "datetime('now', 'start of day')";
|
||||
}
|
||||
if (useStartOfDay) {
|
||||
return `datetime('now', '-${daysFromToday} days', 'start of day')`;
|
||||
}
|
||||
return `datetime('now', '-${daysFromToday} days')`;
|
||||
}
|
||||
|
||||
// PostgreSQL
|
||||
if (dbType === 'postgresdb') {
|
||||
if (daysFromToday === 0 && useStartOfDay) {
|
||||
return "DATE_TRUNC('day', NOW())";
|
||||
}
|
||||
if (useStartOfDay) {
|
||||
return `DATE_TRUNC('day', NOW() - INTERVAL '${daysFromToday} days')`;
|
||||
}
|
||||
return `NOW() - INTERVAL '${daysFromToday} days'`;
|
||||
}
|
||||
|
||||
// MySQL/MariaDB
|
||||
if (daysFromToday === 0 && useStartOfDay) {
|
||||
return 'DATE(NOW())';
|
||||
}
|
||||
if (useStartOfDay) {
|
||||
return `DATE(DATE_SUB(NOW(), INTERVAL ${daysFromToday} DAY))`;
|
||||
}
|
||||
return `DATE_SUB(NOW(), INTERVAL ${daysFromToday} DAY)`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a SQL Common Table Expression (CTE) query that provides three date boundaries for insights queries
|
||||
*
|
||||
* Behavior:
|
||||
* - If startDate and endDate are the same and today
|
||||
* - returns the last 24 hours: prev_start_date (2 days ago), start_date (1 day ago), end_date (now).
|
||||
* - Otherwise:
|
||||
* - prev_start_date: start of the day before the range
|
||||
* - start_date: start of the current range
|
||||
* - end_date: "now" if endDate is today, else start of the day after endDate
|
||||
*
|
||||
* The SQL CTE can be joined with the insights table for filtering/aggregation.
|
||||
*
|
||||
* @param dbType - The database type ('sqlite', 'postgresdb', 'mysqldb', 'mariadb')
|
||||
* @param startDate - The start date of the range (inclusive)
|
||||
* @param endDate - The end date of the range (inclusive, or "now" if today)
|
||||
* @returns SQL CTE query with `prev_start_date`, `start_date`, and `end_date` columns
|
||||
* - `prev_start_date`: The start of the previous period (used for comparison)
|
||||
* - `start_date`: The start of the current period (inclusive)
|
||||
* - `end_date`: The end of the current period (exclusive)
|
||||
*/
|
||||
export const getDateRangesCommonTableExpressionQuery = ({
|
||||
dbType,
|
||||
startDate,
|
||||
endDate,
|
||||
}: {
|
||||
dbType: DatabaseConfig['type'];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
}) => {
|
||||
const today = DateTime.now().startOf('day');
|
||||
const startDateStartOfDay = DateTime.fromJSDate(startDate).startOf('day');
|
||||
const endDateStartOfDay = DateTime.fromJSDate(endDate).startOf('day');
|
||||
|
||||
const daysFromEndDateToToday = Math.floor(today.diff(endDateStartOfDay, 'days').days);
|
||||
const daysDiff = Math.floor(endDateStartOfDay.diff(startDateStartOfDay, 'days').days);
|
||||
|
||||
const isEndDateToday = daysFromEndDateToToday === 0;
|
||||
|
||||
let prevStartDateSql: string;
|
||||
let startDateSql: string;
|
||||
let endDateSql: string;
|
||||
|
||||
if (daysDiff === 0 && isEndDateToday) {
|
||||
// Last 24 hours
|
||||
prevStartDateSql = getDatetimeSql({ dbType, daysFromToday: 2, useStartOfDay: false });
|
||||
startDateSql = getDatetimeSql({ dbType, daysFromToday: 1, useStartOfDay: false });
|
||||
endDateSql = getDatetimeSql({ dbType, daysFromToday: 0, useStartOfDay: false });
|
||||
} else {
|
||||
// Calculate the date range (minimum 1 day) for previous period
|
||||
const dateRangeInDays = Math.max(1, daysDiff);
|
||||
const daysFromStartDateToToday = Math.floor(today.diff(startDateStartOfDay, 'days').days);
|
||||
const prevStartDaysFromToday = daysFromStartDateToToday + dateRangeInDays;
|
||||
|
||||
prevStartDateSql = getDatetimeSql({
|
||||
dbType,
|
||||
daysFromToday: prevStartDaysFromToday,
|
||||
useStartOfDay: true,
|
||||
});
|
||||
|
||||
startDateSql = getDatetimeSql({
|
||||
dbType,
|
||||
daysFromToday: daysFromStartDateToToday,
|
||||
useStartOfDay: true,
|
||||
});
|
||||
|
||||
endDateSql = isEndDateToday
|
||||
? getDatetimeSql({ dbType, daysFromToday: 0, useStartOfDay: false })
|
||||
: getDatetimeSql({ dbType, daysFromToday: daysFromEndDateToToday - 1, useStartOfDay: true });
|
||||
}
|
||||
|
||||
return sql`SELECT
|
||||
${prevStartDateSql} AS prev_start_date,
|
||||
${startDateSql} AS start_date,
|
||||
${endDateSql} AS end_date
|
||||
`;
|
||||
};
|
||||
|
|
@ -6,6 +6,7 @@ import { DataSource, LessThanOrEqual, Repository } from '@n8n/typeorm';
|
|||
import { DateTime } from 'luxon';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getDateRangesCommonTableExpressionQuery } from './insights-by-period-query.helper';
|
||||
import { InsightsByPeriod } from '../entities/insights-by-period';
|
||||
import type { PeriodUnit, TypeUnit } from '../entities/insights-shared';
|
||||
import { PeriodUnitToNumber, TypeToNumber } from '../entities/insights-shared';
|
||||
|
|
@ -140,7 +141,7 @@ export class InsightsByPeriodRepository extends Repository<InsightsByPeriod> {
|
|||
}>;
|
||||
}
|
||||
|
||||
getAggregationQuery(periodUnit: PeriodUnit) {
|
||||
private getAggregationQuery(periodUnit: PeriodUnit) {
|
||||
// Get the start period expression depending on the period unit and database type
|
||||
const periodStartExpr = this.getPeriodStartExpr(periodUnit);
|
||||
|
||||
|
|
@ -268,18 +269,6 @@ export class InsightsByPeriodRepository extends Repository<InsightsByPeriod> {
|
|||
}
|
||||
}
|
||||
|
||||
private getAgeLimitQuery(maxAgeInDays: number) {
|
||||
if (maxAgeInDays === 0) {
|
||||
return dbType === 'sqlite' ? "datetime('now')" : 'NOW()';
|
||||
}
|
||||
|
||||
return dbType === 'sqlite'
|
||||
? `datetime('now', '-${maxAgeInDays} days')`
|
||||
: dbType === 'postgresdb'
|
||||
? `NOW() - INTERVAL '${maxAgeInDays} days'`
|
||||
: `DATE_SUB(NOW(), INTERVAL ${maxAgeInDays} DAY)`;
|
||||
}
|
||||
|
||||
async getPreviousAndCurrentPeriodTypeAggregates({
|
||||
startDate,
|
||||
endDate,
|
||||
|
|
@ -291,25 +280,14 @@ export class InsightsByPeriodRepository extends Repository<InsightsByPeriod> {
|
|||
total_value: string | number;
|
||||
}>
|
||||
> {
|
||||
const { daysFromStartDateToToday, daysFromEndDateToToday, dateRangeInDays } =
|
||||
this.getDateRangesDaysLimits({
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
|
||||
const cte = sql`
|
||||
SELECT
|
||||
${this.getAgeLimitQuery(daysFromStartDateToToday)} AS current_start,
|
||||
${this.getAgeLimitQuery(daysFromEndDateToToday)} AS current_end,
|
||||
${this.getAgeLimitQuery(daysFromStartDateToToday + dateRangeInDays)} AS previous_start
|
||||
`;
|
||||
const cte = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
const rawRowsQuery = this.createQueryBuilder('insights')
|
||||
.addCommonTableExpression(cte, 'date_ranges')
|
||||
.select(
|
||||
sql`
|
||||
CASE
|
||||
WHEN insights.periodStart >= date_ranges.current_start AND insights.periodStart <= date_ranges.current_end
|
||||
WHEN insights.periodStart >= date_ranges.start_date AND insights.periodStart < date_ranges.end_date
|
||||
THEN 'current'
|
||||
ELSE 'previous'
|
||||
END
|
||||
|
|
@ -320,8 +298,8 @@ export class InsightsByPeriodRepository extends Repository<InsightsByPeriod> {
|
|||
.addSelect('SUM(value)', 'total_value')
|
||||
// Use a cross join with the CTE
|
||||
.innerJoin('date_ranges', 'date_ranges', '1=1')
|
||||
.where('insights.periodStart >= date_ranges.previous_start')
|
||||
.andWhere('insights.periodStart <= date_ranges.current_end')
|
||||
.where('insights.periodStart >= date_ranges.prev_start_date')
|
||||
.andWhere('insights.periodStart < date_ranges.end_date')
|
||||
// Group by both period and type
|
||||
.groupBy('period')
|
||||
.addGroupBy('insights.type');
|
||||
|
|
@ -360,16 +338,7 @@ export class InsightsByPeriodRepository extends Repository<InsightsByPeriod> {
|
|||
const [sortField, sortOrder] = this.parseSortingParams(sortBy);
|
||||
const sumOfExecutions = sql`SUM(CASE WHEN insights.type IN (${TypeToNumber.success.toString()}, ${TypeToNumber.failure.toString()}) THEN value ELSE 0 END)`;
|
||||
|
||||
const { daysFromStartDateToToday, daysFromEndDateToToday } = this.getDateRangesDaysLimits({
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
|
||||
const cte = sql`
|
||||
SELECT
|
||||
${this.getAgeLimitQuery(daysFromStartDateToToday)} AS start_date,
|
||||
${this.getAgeLimitQuery(daysFromEndDateToToday)} AS end_date
|
||||
`;
|
||||
const cte = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
const rawRowsQuery = this.createQueryBuilder('insights')
|
||||
.addCommonTableExpression(cte, 'date_ranges')
|
||||
|
|
@ -396,7 +365,7 @@ export class InsightsByPeriodRepository extends Repository<InsightsByPeriod> {
|
|||
// Use a cross join with the CTE
|
||||
.innerJoin('date_ranges', 'date_ranges', '1=1')
|
||||
.where('insights.periodStart >= date_ranges.start_date')
|
||||
.andWhere('insights.periodStart <= date_ranges.end_date')
|
||||
.andWhere('insights.periodStart < date_ranges.end_date')
|
||||
.groupBy('metadata.workflowId')
|
||||
.addGroupBy('metadata.workflowName')
|
||||
.addGroupBy('metadata.projectId')
|
||||
|
|
@ -426,16 +395,7 @@ export class InsightsByPeriodRepository extends Repository<InsightsByPeriod> {
|
|||
startDate: Date;
|
||||
endDate: Date;
|
||||
}) {
|
||||
const { daysFromStartDateToToday, daysFromEndDateToToday } = this.getDateRangesDaysLimits({
|
||||
startDate,
|
||||
endDate,
|
||||
});
|
||||
|
||||
const cte = sql`
|
||||
SELECT
|
||||
${this.getAgeLimitQuery(daysFromStartDateToToday)} AS start_date,
|
||||
${this.getAgeLimitQuery(daysFromEndDateToToday)} AS end_date
|
||||
`;
|
||||
const cte = getDateRangesCommonTableExpressionQuery({ dbType, startDate, endDate });
|
||||
|
||||
const typesAggregation = insightTypes.map((type) => {
|
||||
return `SUM(CASE WHEN insights.type = ${TypeToNumber[type]} THEN value ELSE 0 END) AS "${displayTypeName[TypeToNumber[type]]}"`;
|
||||
|
|
@ -461,27 +421,6 @@ export class InsightsByPeriodRepository extends Repository<InsightsByPeriod> {
|
|||
return aggregatedInsightsByTimeParser.parse(rawRows);
|
||||
}
|
||||
|
||||
private getDateRangesDaysLimits({ startDate, endDate }: { startDate: Date; endDate: Date }) {
|
||||
const today = DateTime.now().startOf('day');
|
||||
const startDateStartOfDay = DateTime.fromJSDate(startDate).startOf('day');
|
||||
const endDateStartOfDay = DateTime.fromJSDate(endDate).startOf('day');
|
||||
|
||||
let daysFromStartDateToToday = today.diff(startDateStartOfDay, 'days').days;
|
||||
// ensure that at least one day is covered
|
||||
if (daysFromStartDateToToday < 1) {
|
||||
daysFromStartDateToToday = 1;
|
||||
}
|
||||
const daysFromEndDateToToday = today.diff(endDateStartOfDay, 'days').days;
|
||||
|
||||
const dateRangeInDays = daysFromStartDateToToday - daysFromEndDateToToday;
|
||||
|
||||
return {
|
||||
daysFromStartDateToToday,
|
||||
daysFromEndDateToToday,
|
||||
dateRangeInDays,
|
||||
};
|
||||
}
|
||||
|
||||
async pruneOldData(maxAgeInDays: number): Promise<{ affected: number | null | undefined }> {
|
||||
const thresholdDate = DateTime.now().minus({ days: maxAgeInDays }).startOf('day').toJSDate();
|
||||
const result = await this.delete({
|
||||
|
|
|
|||
|
|
@ -161,7 +161,10 @@ export class InsightsController {
|
|||
if (query.dateRange) {
|
||||
const maxAgeInDays = keyRangeToDays[query.dateRange];
|
||||
return {
|
||||
startDate: DateTime.now().minus({ days: maxAgeInDays }).toJSDate(),
|
||||
startDate:
|
||||
maxAgeInDays === 1
|
||||
? DateTime.now().startOf('day').toJSDate()
|
||||
: DateTime.now().minus({ days: maxAgeInDays }).toJSDate(),
|
||||
endDate: today,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ export class InsightsService {
|
|||
const endDateTime = DateTime.fromJSDate(endDate);
|
||||
const differenceInDays = endDateTime.diff(startDateTime, 'days').days;
|
||||
|
||||
if (differenceInDays <= 1) {
|
||||
if (differenceInDays < 1) {
|
||||
return 'hour';
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user