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:
parent
2f584b9064
commit
a4cc1d8daa
132
src/lib/features/instance-stats/getEdgeInstances.e2e.test.ts
Normal file
132
src/lib/features/instance-stats/getEdgeInstances.e2e.test.ts
Normal 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,
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -3,9 +3,8 @@ import type { Db } from '../../types/index.js';
|
|||||||
const TABLE = 'edge_node_presence';
|
const TABLE = 'edge_node_presence';
|
||||||
|
|
||||||
export type GetEdgeInstances = () => Promise<{
|
export type GetEdgeInstances = () => Promise<{
|
||||||
last30: number;
|
lastMonth: number;
|
||||||
last60: number;
|
monthBeforeLast: number;
|
||||||
last90: number;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const createGetEdgeInstances =
|
export const createGetEdgeInstances =
|
||||||
@ -15,7 +14,9 @@ export const createGetEdgeInstances =
|
|||||||
.with('buckets', (qb) =>
|
.with('buckets', (qb) =>
|
||||||
qb
|
qb
|
||||||
.from(TABLE)
|
.from(TABLE)
|
||||||
.whereRaw("bucket_ts >= NOW() - INTERVAL '90 days'")
|
.whereRaw(
|
||||||
|
"bucket_ts >= date_trunc('month', NOW()) - INTERVAL '2 months'",
|
||||||
|
)
|
||||||
.groupBy('bucket_ts')
|
.groupBy('bucket_ts')
|
||||||
.select(
|
.select(
|
||||||
db.raw('bucket_ts'),
|
db.raw('bucket_ts'),
|
||||||
@ -24,31 +25,44 @@ export const createGetEdgeInstances =
|
|||||||
)
|
)
|
||||||
.from('buckets')
|
.from('buckets')
|
||||||
.select({
|
.select({
|
||||||
last30: db.raw(
|
lastMonth: db.raw(`
|
||||||
`COALESCE(CEIL(AVG(active_nodes) FILTER (WHERE bucket_ts >= NOW() - INTERVAL '30 days'))::int, 0)`,
|
COALESCE(
|
||||||
),
|
CEIL(
|
||||||
last60: db.raw(
|
AVG(active_nodes)
|
||||||
`COALESCE(CEIL(AVG(active_nodes) FILTER (WHERE bucket_ts >= NOW() - INTERVAL '60 days'))::int, 0)`,
|
FILTER (
|
||||||
),
|
WHERE bucket_ts >= date_trunc('month', NOW()) - INTERVAL '1 month'
|
||||||
last90: db.raw(
|
AND bucket_ts < date_trunc('month', NOW())
|
||||||
`COALESCE(CEIL(AVG(active_nodes) FILTER (WHERE bucket_ts >= NOW() - INTERVAL '90 days'))::int, 0)`,
|
)
|
||||||
),
|
)::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();
|
.first();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
last30: Number(result?.last30 ?? 0),
|
lastMonth: Number(result?.lastMonth ?? 0),
|
||||||
last60: Number(result?.last60 ?? 0),
|
monthBeforeLast: Number(result?.monthBeforeLast ?? 0),
|
||||||
last90: Number(result?.last90 ?? 0),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createFakeGetEdgeInstances =
|
export const createFakeGetEdgeInstances =
|
||||||
(
|
(
|
||||||
edgeInstances: Awaited<ReturnType<GetEdgeInstances>> = {
|
edgeInstances: Awaited<ReturnType<GetEdgeInstances>> = {
|
||||||
last30: 0,
|
lastMonth: 0,
|
||||||
last60: 0,
|
monthBeforeLast: 0,
|
||||||
last90: 0,
|
|
||||||
},
|
},
|
||||||
): GetEdgeInstances =>
|
): GetEdgeInstances =>
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@ -545,9 +545,8 @@ export class InstanceStatsService {
|
|||||||
hostedBy,
|
hostedBy,
|
||||||
releaseTemplates,
|
releaseTemplates,
|
||||||
releasePlans,
|
releasePlans,
|
||||||
edgeInstances30: edgeInstances.last30,
|
edgeInstancesLastMonth: edgeInstances.lastMonth,
|
||||||
edgeInstances60: edgeInstances.last60,
|
edgeInstancesMonthBeforeLast: edgeInstances.monthBeforeLast,
|
||||||
edgeInstances90: edgeInstances.last90,
|
|
||||||
};
|
};
|
||||||
return featureInfo;
|
return featureInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -287,29 +287,22 @@ export const instanceAdminStatsSchema = {
|
|||||||
edgeInstances: {
|
edgeInstances: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description:
|
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: {
|
properties: {
|
||||||
last30: {
|
lastMonth: {
|
||||||
type: 'integer',
|
type: 'integer',
|
||||||
description:
|
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,
|
example: 10,
|
||||||
minimum: 0,
|
minimum: 0,
|
||||||
},
|
},
|
||||||
last60: {
|
monthBeforeLast: {
|
||||||
type: 'integer',
|
type: 'integer',
|
||||||
description:
|
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,
|
example: 12,
|
||||||
minimum: 0,
|
minimum: 0,
|
||||||
},
|
},
|
||||||
last90: {
|
|
||||||
type: 'integer',
|
|
||||||
description:
|
|
||||||
'The billable number of edge instances in the last 90 days',
|
|
||||||
example: 15,
|
|
||||||
minimum: 0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sum: {
|
sum: {
|
||||||
|
|||||||
@ -134,9 +134,8 @@ class InstanceAdminController extends Controller {
|
|||||||
releaseTemplates: 3,
|
releaseTemplates: 3,
|
||||||
releasePlans: 5,
|
releasePlans: 5,
|
||||||
edgeInstances: {
|
edgeInstances: {
|
||||||
last30: 10,
|
lastMonth: 10,
|
||||||
last60: 15,
|
monthBeforeLast: 15,
|
||||||
last90: 20,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,9 +45,8 @@ const fakeTelemetryData = {
|
|||||||
hostedBy: 'self-hosted',
|
hostedBy: 'self-hosted',
|
||||||
releaseTemplates: 2,
|
releaseTemplates: 2,
|
||||||
releasePlans: 4,
|
releasePlans: 4,
|
||||||
edgeInstances30: 0,
|
edgeInstancesLastMonth: 0,
|
||||||
edgeInstances60: 0,
|
edgeInstancesMonthBeforeLast: 0,
|
||||||
edgeInstances90: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
test('yields current versions', async () => {
|
test('yields current versions', async () => {
|
||||||
|
|||||||
@ -54,9 +54,8 @@ export interface IFeatureUsageInfo {
|
|||||||
hostedBy: string;
|
hostedBy: string;
|
||||||
releaseTemplates: number;
|
releaseTemplates: number;
|
||||||
releasePlans: number;
|
releasePlans: number;
|
||||||
edgeInstances30?: number;
|
edgeInstancesLastMonth?: number;
|
||||||
edgeInstances60?: number;
|
edgeInstancesMonthBeforeLast?: number;
|
||||||
edgeInstances90?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class VersionService {
|
export default class VersionService {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user