fix(core): Make the Insights by time datetime parsing more robust (#15413)

This commit is contained in:
Guillaume Jacquart
2025-05-19 10:03:27 +02:00
committed by GitHub
parent fc17cdece2
commit d9fdef3bf9
2 changed files with 60 additions and 8 deletions

View File

@@ -14,6 +14,52 @@ describe('InsightsByPeriodRepository', () => {
await testDb.init(); await testDb.init();
}); });
describe('getInsightsByTime', () => {
test.each([
'2023-10-01T00:00:00Z',
'2023-10-01T00:00:00.000Z',
'2023-10-01T00:00:00+00:00',
'2023-10-01T00:00:00.000+00:00',
'2023-10-01 00:00:00',
'2023-10-01 00:00:00.000',
'2023-10-01 00:00:00+00',
'2023-10-01 00:00:00-02',
'2023-10-01 00:00:00-00',
])(
'should parse correctly valid date %s when calling insights by time',
async (periodStart) => {
// ARRANGE
const insightsByPeriodRepository = Container.get(InsightsByPeriodRepository);
// Mock the manager.queryBuilder.getRawMany to return a mocked value
const mockResult = [{ periodStart, runTime: 0, succeeded: 0, failed: 0, timeSaved: 0 }];
const queryBuilderMock = {
addCommonTableExpression: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
innerJoin: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
groupBy: jest.fn().mockReturnThis(),
orderBy: jest.fn().mockReturnThis(),
getRawMany: jest.fn().mockResolvedValue(mockResult),
};
jest
.spyOn(insightsByPeriodRepository.manager, 'createQueryBuilder')
.mockReturnValueOnce(queryBuilderMock as any);
const result = await insightsByPeriodRepository.getInsightsByTime({
maxAgeInDays: 1,
periodUnit: 'day',
});
// ASSERT
expect(result[0]?.periodStart).not.toBeNull();
expect(new Date(result[0]?.periodStart).toString()).not.toBe('Invalid Date');
},
);
});
describe('Avoid deadlock error', () => { describe('Avoid deadlock error', () => {
let defaultBatchSize: number; let defaultBatchSize: number;
beforeAll(() => { beforeAll(() => {

View File

@@ -41,13 +41,19 @@ const aggregatedInsightsByWorkflowParser = z
const aggregatedInsightsByTimeParser = z const aggregatedInsightsByTimeParser = z
.object({ .object({
periodStart: z periodStart: z.union([z.date(), z.string()]).transform((value) => {
.union([z.date(), z.string()]) if (value instanceof Date) {
.transform((value) => return value.toISOString();
value instanceof Date }
? value.toISOString()
: DateTime.fromSQL(value.toString(), { zone: 'utc' }).toISO(), const parsedDatetime = DateTime.fromSQL(value.toString(), { zone: 'utc' });
), if (parsedDatetime.isValid) {
return parsedDatetime.toISO();
}
// fallback on native date parsing
return new Date(value).toISOString();
}),
runTime: z.union([z.number(), z.string()]).transform((value) => Number(value)), runTime: z.union([z.number(), z.string()]).transform((value) => Number(value)),
succeeded: z.union([z.number(), z.string()]).transform((value) => Number(value)), succeeded: z.union([z.number(), z.string()]).transform((value) => Number(value)),
failed: z.union([z.number(), z.string()]).transform((value) => Number(value)), failed: z.union([z.number(), z.string()]).transform((value) => Number(value)),
@@ -381,7 +387,7 @@ export class InsightsByPeriodRepository extends Repository<InsightsByPeriod> {
]) ])
.innerJoin('date_range', 'date_range', '1=1') .innerJoin('date_range', 'date_range', '1=1')
.where(`${this.escapeField('periodStart')} >= date_range.start_date`) .where(`${this.escapeField('periodStart')} >= date_range.start_date`)
.addGroupBy(this.getPeriodStartExpr(periodUnit)) .groupBy(this.getPeriodStartExpr(periodUnit))
.orderBy(this.getPeriodStartExpr(periodUnit), 'ASC'); .orderBy(this.getPeriodStartExpr(periodUnit), 'ASC');
const rawRows = await rawRowsQuery.getRawMany(); const rawRows = await rawRowsQuery.getRawMany();