1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-27 11:02:16 +01:00

chore: last month and month before last

This commit is contained in:
Nuno Góis 2025-10-22 10:32:14 +01:00
parent 2f584b9064
commit a4cc1d8daa
No known key found for this signature in database
GPG Key ID: 71ECC689F1091765
7 changed files with 178 additions and 43 deletions

View File

@ -0,0 +1,132 @@
import {
createGetEdgeInstances,
type GetEdgeInstances,
} from './getEdgeInstances.js';
import dbInit, {
type ITestDb,
} from '../../../test/e2e/helpers/database-init.js';
import getLogger from '../../../test/fixtures/no-logger.js';
let db: ITestDb;
let getEdgeInstances: GetEdgeInstances;
const TABLE = 'edge_node_presence';
const firstDayOfMonth = (d: Date) => new Date(d.getFullYear(), d.getMonth(), 1);
const addMonths = (d: Date, n: number) =>
new Date(d.getFullYear(), d.getMonth() + n, 1);
const monthWindows = () => {
const now = new Date();
const thisMonthStart = firstDayOfMonth(now);
const lastMonthStart = addMonths(thisMonthStart, -1);
const monthBeforeLastStart = addMonths(thisMonthStart, -2);
const lastMonthEnd = thisMonthStart;
const monthBeforeLastEnd = lastMonthStart;
return {
monthBeforeLastStart,
monthBeforeLastEnd,
lastMonthStart,
lastMonthEnd,
thisMonthStart,
};
};
const atMidMonth = (start: Date) =>
new Date(start.getFullYear(), start.getMonth(), 15);
const atLateMonth = (start: Date) =>
new Date(start.getFullYear(), start.getMonth(), 25);
const rowsForBucket = (count: number, when: Date) =>
Array.from({ length: count }, (_, i) => ({
bucket_ts: when,
node_ephem_id: `node-${when.getTime()}-${i}`,
}));
beforeAll(async () => {
db = await dbInit('edge_instances_e2e', getLogger);
getEdgeInstances = createGetEdgeInstances(db.rawDatabase);
});
afterEach(async () => {
await db.rawDatabase(TABLE).delete();
});
afterAll(async () => {
await db.destroy();
});
test('returns 0 for both months when no data', async () => {
await expect(getEdgeInstances()).resolves.toEqual({
lastMonth: 0,
monthBeforeLast: 0,
});
});
test('counts only last full month', async () => {
const { lastMonthStart } = monthWindows();
const mid = atMidMonth(lastMonthStart);
const late = atLateMonth(lastMonthStart);
await db
.rawDatabase(TABLE)
.insert([...rowsForBucket(3, mid), ...rowsForBucket(7, late)]);
await expect(getEdgeInstances()).resolves.toEqual({
lastMonth: 5,
monthBeforeLast: 0,
});
});
test('counts only month before last', async () => {
const { monthBeforeLastStart } = monthWindows();
const mid = atMidMonth(monthBeforeLastStart);
const late = atLateMonth(monthBeforeLastStart);
await db
.rawDatabase(TABLE)
.insert([...rowsForBucket(2, mid), ...rowsForBucket(5, late)]);
await expect(getEdgeInstances()).resolves.toEqual({
lastMonth: 0,
monthBeforeLast: 4,
});
});
test('separates months correctly when both have data', async () => {
const { monthBeforeLastStart, lastMonthStart } = monthWindows();
const pMid = atMidMonth(monthBeforeLastStart);
const pLate = atLateMonth(monthBeforeLastStart);
const lMid = atMidMonth(lastMonthStart);
const lLate = atLateMonth(lastMonthStart);
await db
.rawDatabase(TABLE)
.insert([
...rowsForBucket(4, pMid),
...rowsForBucket(6, pLate),
...rowsForBucket(3, lMid),
...rowsForBucket(7, lLate),
]);
await expect(getEdgeInstances()).resolves.toEqual({
lastMonth: 5,
monthBeforeLast: 5,
});
});
test('ignores current month data', async () => {
const { thisMonthStart, lastMonthStart } = monthWindows();
const lMid = atMidMonth(lastMonthStart);
const lLate = atLateMonth(lastMonthStart);
const tMid = atMidMonth(thisMonthStart);
await db
.rawDatabase(TABLE)
.insert([
...rowsForBucket(10, tMid),
...rowsForBucket(2, lMid),
...rowsForBucket(4, lLate),
]);
await expect(getEdgeInstances()).resolves.toEqual({
lastMonth: 3,
monthBeforeLast: 0,
});
});

View File

@ -3,9 +3,8 @@ import type { Db } from '../../types/index.js';
const TABLE = 'edge_node_presence';
export type GetEdgeInstances = () => Promise<{
last30: number;
last60: number;
last90: number;
lastMonth: number;
monthBeforeLast: number;
}>;
export const createGetEdgeInstances =
@ -15,7 +14,9 @@ export const createGetEdgeInstances =
.with('buckets', (qb) =>
qb
.from(TABLE)
.whereRaw("bucket_ts >= NOW() - INTERVAL '90 days'")
.whereRaw(
"bucket_ts >= date_trunc('month', NOW()) - INTERVAL '2 months'",
)
.groupBy('bucket_ts')
.select(
db.raw('bucket_ts'),
@ -24,31 +25,44 @@ export const createGetEdgeInstances =
)
.from('buckets')
.select({
last30: db.raw(
`COALESCE(CEIL(AVG(active_nodes) FILTER (WHERE bucket_ts >= NOW() - INTERVAL '30 days'))::int, 0)`,
),
last60: db.raw(
`COALESCE(CEIL(AVG(active_nodes) FILTER (WHERE bucket_ts >= NOW() - INTERVAL '60 days'))::int, 0)`,
),
last90: db.raw(
`COALESCE(CEIL(AVG(active_nodes) FILTER (WHERE bucket_ts >= NOW() - INTERVAL '90 days'))::int, 0)`,
),
lastMonth: db.raw(`
COALESCE(
CEIL(
AVG(active_nodes)
FILTER (
WHERE bucket_ts >= date_trunc('month', NOW()) - INTERVAL '1 month'
AND bucket_ts < date_trunc('month', NOW())
)
)::int,
0
)
`),
monthBeforeLast: db.raw(`
COALESCE(
CEIL(
AVG(active_nodes)
FILTER (
WHERE bucket_ts >= date_trunc('month', NOW()) - INTERVAL '2 months'
AND bucket_ts < date_trunc('month', NOW()) - INTERVAL '1 month'
)
)::int,
0
)
`),
})
.first();
return {
last30: Number(result?.last30 ?? 0),
last60: Number(result?.last60 ?? 0),
last90: Number(result?.last90 ?? 0),
lastMonth: Number(result?.lastMonth ?? 0),
monthBeforeLast: Number(result?.monthBeforeLast ?? 0),
};
};
export const createFakeGetEdgeInstances =
(
edgeInstances: Awaited<ReturnType<GetEdgeInstances>> = {
last30: 0,
last60: 0,
last90: 0,
lastMonth: 0,
monthBeforeLast: 0,
},
): GetEdgeInstances =>
() =>

View File

@ -545,9 +545,8 @@ export class InstanceStatsService {
hostedBy,
releaseTemplates,
releasePlans,
edgeInstances30: edgeInstances.last30,
edgeInstances60: edgeInstances.last60,
edgeInstances90: edgeInstances.last90,
edgeInstancesLastMonth: edgeInstances.lastMonth,
edgeInstancesMonthBeforeLast: edgeInstances.monthBeforeLast,
};
return featureInfo;
}

View File

@ -287,29 +287,22 @@ export const instanceAdminStatsSchema = {
edgeInstances: {
type: 'object',
description:
'The billable number of edge instances in the last 30, 60 and 90 days',
'The rounded up average number of edge instances in the last month and month before last',
properties: {
last30: {
lastMonth: {
type: 'integer',
description:
'The billable number of edge instances in the last 30 days',
'The rounded up average number of edge instances in the last month',
example: 10,
minimum: 0,
},
last60: {
monthBeforeLast: {
type: 'integer',
description:
'The billable number of edge instances in the last 60 days',
'The rounded up average number of edge instances in the month before last',
example: 12,
minimum: 0,
},
last90: {
type: 'integer',
description:
'The billable number of edge instances in the last 90 days',
example: 15,
minimum: 0,
},
},
},
sum: {

View File

@ -134,9 +134,8 @@ class InstanceAdminController extends Controller {
releaseTemplates: 3,
releasePlans: 5,
edgeInstances: {
last30: 10,
last60: 15,
last90: 20,
lastMonth: 10,
monthBeforeLast: 15,
},
};
}

View File

@ -45,9 +45,8 @@ const fakeTelemetryData = {
hostedBy: 'self-hosted',
releaseTemplates: 2,
releasePlans: 4,
edgeInstances30: 0,
edgeInstances60: 0,
edgeInstances90: 0,
edgeInstancesLastMonth: 0,
edgeInstancesMonthBeforeLast: 0,
};
test('yields current versions', async () => {

View File

@ -54,9 +54,8 @@ export interface IFeatureUsageInfo {
hostedBy: string;
releaseTemplates: number;
releasePlans: number;
edgeInstances30?: number;
edgeInstances60?: number;
edgeInstances90?: number;
edgeInstancesLastMonth?: number;
edgeInstancesMonthBeforeLast?: number;
}
export default class VersionService {