1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

fix: serialize API token data correctly in instance stats (#7953)

Turns out we've been trying to return API token data in instance stats
for a while, but that the serialization has failed. Serializing a JS map
just yields an empty object.

This PR fixes that serialization and also adds API tokens to the
instance stats schema (it wasn't before, but we did return it). Adding
it to the schema is also part of making resource usage visible as part
of the soft limits project.
This commit is contained in:
Thomas Heartman 2024-08-22 10:29:05 +02:00 committed by GitHub
parent 341703978a
commit e5cca661d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 83 additions and 8 deletions

View File

@ -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.',
minimum: 0,
example: 5,
},
client: {
type: 'number',
description: 'The number of client tokens.',
minimum: 0,
example: 5,
},
frontend: {
type: 'number',
description: 'The number of frontend tokens.',
minimum: 0,
example: 5,
},
},
},
sum: {
type: 'string',
description:

View File

@ -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,
@ -15,6 +14,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;
@ -128,17 +129,29 @@ class InstanceAdminController extends Controller {
};
}
private serializeStats(
instanceStats: InstanceStatsSigned,
): InstanceAdminStatsSchema {
const apiTokensObj = Object.fromEntries(
instanceStats.apiTokens.entries(),
);
return serializeDates({
...instanceStats,
apiTokens: apiTokensObj,
});
}
async getStatistics(
req: AuthedRequest,
res: Response<InstanceStatsSigned>,
_: AuthedRequest,
res: Response<InstanceAdminStatsSchema>,
): Promise<void> {
const instanceStats = await this.instanceStatsService.getSignedStats();
res.json(instanceStats);
res.json(this.serializeStats(instanceStats));
}
async getStatisticsCSV(
req: AuthedRequest,
res: Response<UiConfigSchema>,
_: AuthedRequest,
res: Response<InstanceAdminStatsSchema>,
): Promise<void> {
const instanceStats = await this.instanceStatsService.getSignedStats();
const fileName = `unleash-${
@ -146,7 +159,7 @@ class InstanceAdminController extends Controller {
}-${Date.now()}.csv`;
const json2csvParser = new Parser();
const csv = json2csvParser.parse(instanceStats);
const csv = json2csvParser.parse(this.serializeStats(instanceStats));
res.contentType('csv');
res.attachment(fileName);

View File

@ -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,43 @@ 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 },
});
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 () => {
await stores.projectStore.create({
id: 'test',
@ -77,7 +115,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,