From 0870b75f09d8ba21b6233e24c54d5d3868c6e4a3 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 22 Aug 2024 08:55:38 +0200 Subject: [PATCH 1/8] feat: return apiTokens as an object instead of a map --- src/lib/routes/admin-api/instance-admin.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/lib/routes/admin-api/instance-admin.ts b/src/lib/routes/admin-api/instance-admin.ts index 65e1b457b79c..22cd57a5169c 100644 --- a/src/lib/routes/admin-api/instance-admin.ts +++ b/src/lib/routes/admin-api/instance-admin.ts @@ -15,6 +15,8 @@ import { createCsvResponseSchema, createResponseSchema, } from '../../openapi/util/create-response-schema'; +import type { InstanceAdminStatsSchema } from '../../openapi'; +import { serializeDates } from '../../types'; class InstanceAdminController extends Controller { private instanceStatsService: InstanceStatsService; @@ -129,11 +131,20 @@ class InstanceAdminController extends Controller { } async getStatistics( - req: AuthedRequest, - res: Response, + _: AuthedRequest, + res: Response, ): Promise { const instanceStats = await this.instanceStatsService.getSignedStats(); - res.json(instanceStats); + const apiTokensObj = Object.fromEntries( + instanceStats.apiTokens.entries(), + ); + res.json( + serializeDates({ + ...instanceStats, + + apiTokens: apiTokensObj, + }), + ); } async getStatisticsCSV( From 5b7cd9f5b05ca04b800403e672e971924baf9416 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 22 Aug 2024 08:56:07 +0200 Subject: [PATCH 2/8] Chore: add apiTokens to admin stats schema --- .../spec/instance-admin-stats-schema.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib/openapi/spec/instance-admin-stats-schema.ts b/src/lib/openapi/spec/instance-admin-stats-schema.ts index cdbc1f33cf19..0278324e2da8 100644 --- a/src/lib/openapi/spec/instance-admin-stats-schema.ts +++ b/src/lib/openapi/spec/instance-admin-stats-schema.ts @@ -220,6 +220,30 @@ export const instanceAdminStatsSchema = { example: 0, minimum: 0, }, + apiTokens: { + type: 'object', + description: 'The number of API tokens in Unleash, split by type', + properties: { + admin: { + type: 'number', + description: 'The number of admin tokens.', + minumum: 0, + example: 5, + }, + client: { + type: 'number', + description: 'The number of client tokens.', + minumum: 0, + example: 5, + }, + frontend: { + type: 'number', + description: 'The number of frontend tokens.', + minumum: 0, + example: 5, + }, + }, + }, sum: { type: 'string', description: From 5134d20f9f68029038cde6b3d47e5ebca4f518fc Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 22 Aug 2024 09:02:45 +0200 Subject: [PATCH 3/8] feat: add tests for api token serialization --- src/lib/routes/admin-api/instance-admin.ts | 1 - .../e2e/api/admin/instance-admin.e2e.test.ts | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/lib/routes/admin-api/instance-admin.ts b/src/lib/routes/admin-api/instance-admin.ts index 22cd57a5169c..aad8e4059e9f 100644 --- a/src/lib/routes/admin-api/instance-admin.ts +++ b/src/lib/routes/admin-api/instance-admin.ts @@ -141,7 +141,6 @@ class InstanceAdminController extends Controller { res.json( serializeDates({ ...instanceStats, - apiTokens: apiTokensObj, }), ); diff --git a/src/test/e2e/api/admin/instance-admin.e2e.test.ts b/src/test/e2e/api/admin/instance-admin.e2e.test.ts index ec8249799014..af0226057c6f 100644 --- a/src/test/e2e/api/admin/instance-admin.e2e.test.ts +++ b/src/test/e2e/api/admin/instance-admin.e2e.test.ts @@ -5,6 +5,7 @@ import { } from '../../helpers/test-helper'; import getLogger from '../../../fixtures/no-logger'; import type { IUnleashStores } from '../../../../lib/types'; +import { ApiTokenType } from '../../../../lib/types/models/api-token'; let app: IUnleashTest; let db: ITestDb; @@ -47,6 +48,36 @@ test('should return instance statistics', async () => { }); }); +test('api tokens are serialized correctly', async () => { + await app.services.apiTokenService.createApiTokenWithProjects({ + tokenName: 'admin', + type: ApiTokenType.ADMIN, + environment: '*', + projects: ['*'], + }); + await app.services.apiTokenService.createApiTokenWithProjects({ + tokenName: 'frontend', + type: ApiTokenType.FRONTEND, + environment: 'default', + projects: ['*'], + }); + await app.services.apiTokenService.createApiTokenWithProjects({ + tokenName: 'client', + type: ApiTokenType.CLIENT, + environment: 'default', + projects: ['*'], + }); + + const { body } = await app.request + .get('/api/admin/instance-admin/statistics') + .expect('Content-Type', /json/) + .expect(200); + + expect(body).toMatchObject({ + apiTokens: { client: 1, admin: 1, frontend: 1 }, + }); +}); + test('should return instance statistics with correct number of projects', async () => { await stores.projectStore.create({ id: 'test', From 0b35f94cb9a6d82c7eada791c8b9013a6261253a Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 22 Aug 2024 09:03:00 +0200 Subject: [PATCH 4/8] feat: fix typo --- src/test/e2e/api/admin/instance-admin.e2e.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/e2e/api/admin/instance-admin.e2e.test.ts b/src/test/e2e/api/admin/instance-admin.e2e.test.ts index af0226057c6f..f867dea4f96a 100644 --- a/src/test/e2e/api/admin/instance-admin.e2e.test.ts +++ b/src/test/e2e/api/admin/instance-admin.e2e.test.ts @@ -108,7 +108,7 @@ test('should return signed instance statistics', async () => { }); }); -test('should return instance statistics as CVS', async () => { +test('should return instance statistics as CSV', async () => { await stores.featureToggleStore.create('default', { name: 'TestStats2', createdByUserId: 9999, From b1abbf4e5e54e4008ed51ddad29c27b4c56fa246 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 22 Aug 2024 09:32:58 +0200 Subject: [PATCH 5/8] Fix: api token serialization to CSV --- src/lib/routes/admin-api/instance-admin.ts | 8 +++++++- src/test/e2e/api/admin/instance-admin.e2e.test.ts | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/lib/routes/admin-api/instance-admin.ts b/src/lib/routes/admin-api/instance-admin.ts index aad8e4059e9f..7dd2ff0a7fc5 100644 --- a/src/lib/routes/admin-api/instance-admin.ts +++ b/src/lib/routes/admin-api/instance-admin.ts @@ -156,7 +156,13 @@ class InstanceAdminController extends Controller { }-${Date.now()}.csv`; const json2csvParser = new Parser(); - const csv = json2csvParser.parse(instanceStats); + const apiTokensObj = Object.fromEntries( + instanceStats.apiTokens.entries(), + ); + const csv = json2csvParser.parse({ + ...instanceStats, + apiTokens: apiTokensObj, + }); res.contentType('csv'); res.attachment(fileName); diff --git a/src/test/e2e/api/admin/instance-admin.e2e.test.ts b/src/test/e2e/api/admin/instance-admin.e2e.test.ts index f867dea4f96a..5534b4bd80e5 100644 --- a/src/test/e2e/api/admin/instance-admin.e2e.test.ts +++ b/src/test/e2e/api/admin/instance-admin.e2e.test.ts @@ -76,6 +76,13 @@ test('api tokens are serialized correctly', async () => { expect(body).toMatchObject({ apiTokens: { client: 1, admin: 1, frontend: 1 }, }); + + const { text: csv } = await app.request + .get('/api/admin/instance-admin/statistics/csv') + .expect('Content-Type', /text\/csv/) + .expect(200); + + expect(csv).toMatch(/{""client"":1,""admin"":1,""frontend"":1}/); }); test('should return instance statistics with correct number of projects', async () => { From c1167da09303260691add52bdd57adea7a1c1938 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 22 Aug 2024 10:03:08 +0200 Subject: [PATCH 6/8] Fix: typo --- src/lib/openapi/spec/instance-admin-stats-schema.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/openapi/spec/instance-admin-stats-schema.ts b/src/lib/openapi/spec/instance-admin-stats-schema.ts index 0278324e2da8..0a54a3e7b36d 100644 --- a/src/lib/openapi/spec/instance-admin-stats-schema.ts +++ b/src/lib/openapi/spec/instance-admin-stats-schema.ts @@ -227,19 +227,19 @@ export const instanceAdminStatsSchema = { admin: { type: 'number', description: 'The number of admin tokens.', - minumum: 0, + minimum: 0, example: 5, }, client: { type: 'number', description: 'The number of client tokens.', - minumum: 0, + minimum: 0, example: 5, }, frontend: { type: 'number', description: 'The number of frontend tokens.', - minumum: 0, + minimum: 0, example: 5, }, }, From 8e6c90d85acf6d9db79f1b9baa5c8146928610d7 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 22 Aug 2024 10:07:24 +0200 Subject: [PATCH 7/8] feat: serialize in one place --- src/lib/routes/admin-api/instance-admin.ts | 31 ++++++++++------------ 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/lib/routes/admin-api/instance-admin.ts b/src/lib/routes/admin-api/instance-admin.ts index 7dd2ff0a7fc5..d88f39f0c6c9 100644 --- a/src/lib/routes/admin-api/instance-admin.ts +++ b/src/lib/routes/admin-api/instance-admin.ts @@ -5,7 +5,6 @@ import type { IUnleashServices } from '../../types/services'; import type { IUnleashConfig } from '../../types/option'; import Controller from '../controller'; import { NONE } from '../../types/permissions'; -import type { UiConfigSchema } from '../../openapi/spec/ui-config-schema'; import type { InstanceStatsService, InstanceStatsSigned, @@ -130,20 +129,24 @@ class InstanceAdminController extends Controller { }; } + private serializeStats( + instanceStats: InstanceStatsSigned, + ): InstanceAdminStatsSchema { + const apiTokensObj = Object.fromEntries( + instanceStats.apiTokens.entries(), + ); + return serializeDates({ + ...instanceStats, + apiTokens: apiTokensObj, + }); + } + async getStatistics( _: AuthedRequest, res: Response, ): Promise { const instanceStats = await this.instanceStatsService.getSignedStats(); - const apiTokensObj = Object.fromEntries( - instanceStats.apiTokens.entries(), - ); - res.json( - serializeDates({ - ...instanceStats, - apiTokens: apiTokensObj, - }), - ); + res.json(this.serializeStats(instanceStats)); } async getStatisticsCSV( @@ -156,13 +159,7 @@ class InstanceAdminController extends Controller { }-${Date.now()}.csv`; const json2csvParser = new Parser(); - const apiTokensObj = Object.fromEntries( - instanceStats.apiTokens.entries(), - ); - const csv = json2csvParser.parse({ - ...instanceStats, - apiTokens: apiTokensObj, - }); + const csv = json2csvParser.parse(this.serializeStats(instanceStats)); res.contentType('csv'); res.attachment(fileName); From a2d42ca028035ab22535aa1b1e3b1cf0afab2bc4 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Thu, 22 Aug 2024 10:07:37 +0200 Subject: [PATCH 8/8] feat: fix signature --- src/lib/routes/admin-api/instance-admin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/routes/admin-api/instance-admin.ts b/src/lib/routes/admin-api/instance-admin.ts index d88f39f0c6c9..89d81fabbf8c 100644 --- a/src/lib/routes/admin-api/instance-admin.ts +++ b/src/lib/routes/admin-api/instance-admin.ts @@ -150,8 +150,8 @@ class InstanceAdminController extends Controller { } async getStatisticsCSV( - req: AuthedRequest, - res: Response, + _: AuthedRequest, + res: Response, ): Promise { const instanceStats = await this.instanceStatsService.getSignedStats(); const fileName = `unleash-${