Skip to content

Commit

Permalink
feat(1-3260): store support for data traffic from a range (#9127)
Browse files Browse the repository at this point in the history
Add support for querying the traffic data usage store for the aggregated data for an arbitrary number of months back.

Adds a new `getTrafficDataForMonthRange(monthsBack: number)` method to the store that aggregates data on a monthly basis by status code and traffic group. Returns a new type with month data instead of day data.
  • Loading branch information
thomasheartman authored Jan 22, 2025
1 parent 293b1ea commit 4bbff0c
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import type {
IStatTrafficUsageKey,
IStatTrafficUsage,
IStatMonthlyTrafficUsage,
} from './traffic-data-usage-store-type';
import type { ITrafficDataUsageStore } from '../../types';
import { isSameMonth, parse } from 'date-fns';
import {
differenceInCalendarMonths,
format,
isSameMonth,
parse,
} from 'date-fns';

export class FakeTrafficDataUsageStore implements ITrafficDataUsageStore {
private trafficData: IStatTrafficUsage[] = [];
Expand Down Expand Up @@ -50,4 +56,37 @@ export class FakeTrafficDataUsageStore implements ITrafficDataUsageStore {
isSameMonth(data.day, periodDate),
);
}

async getTrafficDataForMonthRange(
monthsBack: number,
): Promise<IStatMonthlyTrafficUsage[]> {
const now = new Date();

const data: { [key: string]: IStatMonthlyTrafficUsage } =
this.trafficData
.filter(
(entry) =>
differenceInCalendarMonths(now, entry.day) <=
monthsBack,
)
.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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ export type IStatTrafficUsage = {
count: number;
};

export type IStatMonthlyTrafficUsage = {
month: string;
trafficGroup: string;
statusCodeSeries: number;
count: number;
};

export interface IStatTrafficUsageKey {
day: Date;
trafficGroup: string;
Expand All @@ -17,4 +24,7 @@ export interface ITrafficDataUsageStore
extends Store<IStatTrafficUsage, IStatTrafficUsageKey> {
upsert(trafficDataUsage: IStatTrafficUsage): Promise<void>;
getTrafficDataUsageForPeriod(period: string): Promise<IStatTrafficUsage[]>;
getTrafficDataForMonthRange(
monthsBack: number,
): Promise<IStatMonthlyTrafficUsage[]>;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { differenceInCalendarMonths, subMonths } from 'date-fns';
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
import getLogger from '../../../test/fixtures/no-logger';
import type { ITrafficDataUsageStore, IUnleashStores } from '../../types';
Expand All @@ -20,6 +21,10 @@ afterAll(async () => {
await db.destroy();
});

beforeEach(async () => {
await trafficDataUsageStore.deleteAll();
});

test('upsert stores new entries', async () => {
const data = {
day: new Date(),
Expand Down Expand Up @@ -62,7 +67,6 @@ test('upsert upserts', async () => {
});

test('getAll returns all', async () => {
await trafficDataUsageStore.deleteAll();
const data1 = {
day: new Date(),
trafficGroup: 'default3',
Expand All @@ -83,7 +87,6 @@ test('getAll returns all', async () => {
});

test('delete deletes the specified item', async () => {
await trafficDataUsageStore.deleteAll();
const data1 = {
day: new Date(),
trafficGroup: 'default3',
Expand All @@ -110,7 +113,6 @@ test('delete deletes the specified item', async () => {
});

test('can query for specific items', async () => {
await trafficDataUsageStore.deleteAll();
const data1 = {
day: new Date(),
trafficGroup: 'default3',
Expand Down Expand Up @@ -154,7 +156,6 @@ test('can query for specific items', async () => {
});

test('can query for data from specific periods', async () => {
await trafficDataUsageStore.deleteAll();
const data1 = {
day: new Date(2024, 2, 12),
trafficGroup: 'default-period-query',
Expand Down Expand Up @@ -195,3 +196,57 @@ test('can query for data from specific periods', async () => {
expect(traffic_period_usage_older.length).toBe(1);
expect(traffic_period_usage_older[0].count).toBe(12);
});

test('can query for monthly aggregation of data for a specified range', async () => {
const now = new Date();

const expectedValues: { groupA: number; groupB: number }[] = [];

// fill in with data for the last 13 months
for (let i = 0; i <= 12; i++) {
const then = subMonths(now, i);
let monthAggregateA = 0;
let monthAggregateB = 0;
for (let day = 1; day <= 5; day++) {
const dayValue = i + day;
const dayValueB = dayValue * 2;
monthAggregateA += dayValue;
monthAggregateB += dayValueB;
const dataA = {
day: new Date(then.getFullYear(), then.getMonth(), day),
trafficGroup: 'groupA',
statusCodeSeries: 200,
count: dayValue,
};
await trafficDataUsageStore.upsert(dataA);
const dataB = {
day: new Date(then.getFullYear(), then.getMonth(), day),
trafficGroup: 'groupB',
statusCodeSeries: 200,
count: dayValueB,
};
await trafficDataUsageStore.upsert(dataB);
}
expectedValues.push({
groupA: monthAggregateA,
groupB: monthAggregateB,
});
}

for (const monthsBack of [3, 6, 12]) {
const result =
await trafficDataUsageStore.getTrafficDataForMonthRange(monthsBack);

// should have the current month and the preceding n months (one entry per group)
expect(result.length).toBe((monthsBack + 1) * 2);

for (const entry of result) {
const index = differenceInCalendarMonths(
now,
new Date(entry.month),
);
const expectedCount = expectedValues[index];
expect(entry.count).toBe(expectedCount[entry.trafficGroup]);
}
}
});
36 changes: 35 additions & 1 deletion src/lib/features/traffic-data-usage/traffic-data-usage-store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Db } from '../../db/db';
import type { Logger, LogProvider } from '../../logger';
import type {
IStatMonthlyTrafficUsage,
IStatTrafficUsage,
IStatTrafficUsageKey,
ITrafficDataUsageStore,
Expand Down Expand Up @@ -55,7 +56,7 @@ export class TrafficDataUsageStore implements ITrafficDataUsageStore {
}
async exists(key: IStatTrafficUsageKey): Promise<boolean> {
const result = await this.db.raw(
`SELECT EXISTS (SELECT 1 FROM ${TABLE} WHERE
`SELECT EXISTS (SELECT 1 FROM ${TABLE} WHERE
day = ? AND
traffic_group = ? AND
status_code_series ?) AS present`,
Expand Down Expand Up @@ -97,4 +98,37 @@ export class TrafficDataUsageStore implements ITrafficDataUsageStore {
);
return rows.map(mapRow);
}

async getTrafficDataForMonthRange(
monthsBack: number,
): Promise<IStatMonthlyTrafficUsage[]> {
const rows = await this.db(TABLE)
.select(
'traffic_group',
'status_code_series',
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],
)
.groupBy([
'traffic_group',
this.db.raw(`to_char(day, 'YYYY-MM')`),
'status_code_series',
])
.orderBy([
{ column: 'month', order: 'desc' },
{ column: 'traffic_group', order: 'asc' },
]);
return rows.map(
({ traffic_group, status_code_series, month, count }) => ({
trafficGroup: traffic_group,
statusCodeSeries: status_code_series,
month,
count: Number.parseInt(count),
}),
);
}
}

0 comments on commit 4bbff0c

Please sign in to comment.