From 85ae6f3b9544c42b900baab5cf1faf58303d67dd Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Mon, 27 Jan 2025 10:16:36 +0100 Subject: [PATCH] chore(1-3293): add new query for daily data that uses date ranges (#9150) Add new methods to the store behind data usage metrics that accept date ranges instead of a single month. The old data collection methods re-route to the new ones instead, so the new methods are tested implicitly. Also deprecates the new endpoint that's not in use anywhere except in an unused service method in Enterprise yet. ## Discussion point: Accepts from and to params as dates for type safety. You can send unparseable strings, but if you send a date object, you know it'll work. Leaves the use of the old method in `src/lib/features/instance-stats/instance-stats-service.ts` to keep changes small. --- .../fake-traffic-data-usage-store.ts | 43 ++++++++++++++++ .../traffic-data-usage-store-type.ts | 8 +++ .../traffic-data-usage-store.ts | 50 ++++++++++++++----- 3 files changed, 89 insertions(+), 12 deletions(-) diff --git a/src/lib/features/traffic-data-usage/fake-traffic-data-usage-store.ts b/src/lib/features/traffic-data-usage/fake-traffic-data-usage-store.ts index fa2262f7f7..4fd4606a78 100644 --- a/src/lib/features/traffic-data-usage/fake-traffic-data-usage-store.ts +++ b/src/lib/features/traffic-data-usage/fake-traffic-data-usage-store.ts @@ -6,9 +6,11 @@ import type { import type { ITrafficDataUsageStore } from '../../types'; import { differenceInCalendarMonths, + endOfDay, format, isSameMonth, parse, + startOfDay, } from 'date-fns'; export class FakeTrafficDataUsageStore implements ITrafficDataUsageStore { @@ -89,4 +91,45 @@ export class FakeTrafficDataUsageStore implements ITrafficDataUsageStore { return Object.values(data); } + + async getDailyTrafficUsageDataForPeriod( + from: Date, + to: Date, + ): Promise { + return this.trafficData.filter( + (data) => data.day >= startOfDay(from) && data.day <= endOfDay(to), + ); + } + + async getMonthlyTrafficUsageDataForPeriod( + from: Date, + to: Date, + ): Promise { + const data: { [key: string]: IStatMonthlyTrafficUsage } = + this.trafficData + .filter( + (data) => + data.day >= startOfDay(from) && + data.day <= endOfDay(to), + ) + .reduce((acc, entry) => { + const month = format(entry.day, 'yyyy-MM'); + const key = `${month}-${entry.trafficGroup}-${entry.statusCodeSeries}`; + + if (acc[key]) { + acc[key].count += entry.count; + } else { + acc[key] = { + month, + trafficGroup: entry.trafficGroup, + statusCodeSeries: entry.statusCodeSeries, + count: entry.count, + }; + } + + return acc; + }, {}); + + return Object.values(data); + } } diff --git a/src/lib/features/traffic-data-usage/traffic-data-usage-store-type.ts b/src/lib/features/traffic-data-usage/traffic-data-usage-store-type.ts index d24711c756..b695c9c771 100644 --- a/src/lib/features/traffic-data-usage/traffic-data-usage-store-type.ts +++ b/src/lib/features/traffic-data-usage/traffic-data-usage-store-type.ts @@ -27,4 +27,12 @@ export interface ITrafficDataUsageStore getTrafficDataForMonthRange( monthsBack: number, ): Promise; + getDailyTrafficUsageDataForPeriod( + from: Date, + to: Date, + ): Promise; + getMonthlyTrafficUsageDataForPeriod( + from: Date, + to: Date, + ): Promise; } diff --git a/src/lib/features/traffic-data-usage/traffic-data-usage-store.ts b/src/lib/features/traffic-data-usage/traffic-data-usage-store.ts index ba686ea867..c32f3bdf99 100644 --- a/src/lib/features/traffic-data-usage/traffic-data-usage-store.ts +++ b/src/lib/features/traffic-data-usage/traffic-data-usage-store.ts @@ -1,3 +1,10 @@ +import { + endOfDay, + endOfMonth, + startOfDay, + startOfMonth, + subMonths, +} from 'date-fns'; import type { Db } from '../../db/db'; import type { Logger, LogProvider } from '../../logger'; import type { @@ -89,18 +96,20 @@ export class TrafficDataUsageStore implements ITrafficDataUsageStore { }); } - async getTrafficDataUsageForPeriod( - period: string, + async getDailyTrafficUsageDataForPeriod( + from: Date, + to: Date, ): Promise { - const rows = await this.db(TABLE).whereRaw( - `to_char(day, 'YYYY-MM') = ?`, - [period], - ); + const rows = await this.db(TABLE) + .where('day', '>=', startOfDay(from)) + .andWhere('day', '<=', endOfDay(to)); + return rows.map(mapRow); } - async getTrafficDataForMonthRange( - monthsBack: number, + async getMonthlyTrafficUsageDataForPeriod( + from: Date, + to: Date, ): Promise { const rows = await this.db(TABLE) .select( @@ -109,10 +118,8 @@ export class TrafficDataUsageStore implements ITrafficDataUsageStore { this.db.raw(`to_char(day, 'YYYY-MM') AS month`), this.db.raw(`SUM(count) AS count`), ) - .whereRaw( - `day >= date_trunc('month', CURRENT_DATE) - make_interval(months := ?)`, - [monthsBack], - ) + .where('day', '>=', startOfDay(from)) + .andWhere('day', '<=', endOfDay(to)) .groupBy([ 'traffic_group', this.db.raw(`to_char(day, 'YYYY-MM')`), @@ -131,4 +138,23 @@ export class TrafficDataUsageStore implements ITrafficDataUsageStore { }), ); } + + async getTrafficDataUsageForPeriod( + period: string, + ): Promise { + const month = new Date(period); + return this.getDailyTrafficUsageDataForPeriod( + startOfMonth(month), + endOfMonth(month), + ); + } + + // @deprecated: remove with flag `dataUsageMultiMonthView` + async getTrafficDataForMonthRange( + monthsBack: number, + ): Promise { + const to = endOfMonth(new Date()); + const from = startOfMonth(subMonths(to, monthsBack)); + return this.getMonthlyTrafficUsageDataForPeriod(from, to); + } }