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'; | ||||
| 
 | ||||
| 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 => | ||||
|     () => | ||||
|  | ||||
| @ -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; | ||||
|     } | ||||
|  | ||||
| @ -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: { | ||||
|  | ||||
| @ -134,9 +134,8 @@ class InstanceAdminController extends Controller { | ||||
|             releaseTemplates: 3, | ||||
|             releasePlans: 5, | ||||
|             edgeInstances: { | ||||
|                 last30: 10, | ||||
|                 last60: 15, | ||||
|                 last90: 20, | ||||
|                 lastMonth: 10, | ||||
|                 monthBeforeLast: 15, | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| @ -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 () => { | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user