1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-27 11:02:16 +01:00
unleash.unleash/src/lib/routes/admin-api/instance-admin.ts
Thomas Heartman e5cca661d9
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.
2024-08-22 10:29:05 +02:00

171 lines
5.8 KiB
TypeScript

import { Parser } from 'json2csv';
import type { Response } from 'express';
import type { AuthedRequest } from '../../types/core';
import type { IUnleashServices } from '../../types/services';
import type { IUnleashConfig } from '../../types/option';
import Controller from '../controller';
import { NONE } from '../../types/permissions';
import type {
InstanceStatsService,
InstanceStatsSigned,
} from '../../features/instance-stats/instance-stats-service';
import type { OpenApiService } from '../../services/openapi-service';
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;
private openApiService: OpenApiService;
private jsonCsvParser: Parser;
constructor(
config: IUnleashConfig,
{
instanceStatsService,
openApiService,
}: Pick<IUnleashServices, 'instanceStatsService' | 'openApiService'>,
) {
super(config);
this.jsonCsvParser = new Parser();
this.openApiService = openApiService;
this.instanceStatsService = instanceStatsService;
this.route({
method: 'get',
path: '/statistics/csv',
handler: this.getStatisticsCSV,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['Instance Admin'],
summary: 'Instance usage statistics',
description:
'Provides statistics about various features of Unleash to allow for reporting of usage for self-hosted customers. The response contains data such as the number of users, groups, features, strategies, versions, etc.',
operationId: 'getInstanceAdminStatsCsv',
responses: {
200: createCsvResponseSchema(
'instanceAdminStatsSchemaCsv',
this.jsonCsvParser.parse(
this.instanceStatsExample(),
),
),
},
}),
],
});
this.route({
method: 'get',
path: '/statistics',
handler: this.getStatistics,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['Instance Admin'],
operationId: 'getInstanceAdminStats',
summary: 'Instance usage statistics',
description:
'Provides statistics about various features of Unleash to allow for reporting of usage for self-hosted customers. The response contains data such as the number of users, groups, features, strategies, versions, etc.',
responses: {
200: createResponseSchema('instanceAdminStatsSchema'),
},
deprecated: true,
}),
],
});
}
instanceStatsExample(): InstanceStatsSigned {
return {
OIDCenabled: true,
SAMLenabled: false,
clientApps: [
{ range: 'allTime', count: 15 },
{ range: '30d', count: 9 },
{ range: '7d', count: 5 },
],
contextFields: 6,
environments: 2,
featureExports: 0,
featureImports: 0,
featureToggles: 29,
archivedFeatureToggles: 10,
groups: 3,
instanceId: 'ed3861ae-78f9-4e8c-8e57-b57efc15f82b',
projects: 4,
roles: 5,
customRootRoles: 2,
customRootRolesInUse: 1,
segments: 2,
strategies: 8,
sum: 'some-sha256-hash',
timestamp: new Date(2023, 6, 12, 10, 0, 0, 0),
users: 10,
serviceAccounts: 2,
apiTokens: new Map([]),
versionEnterprise: '5.1.7',
versionOSS: '5.1.7',
activeUsers: {
last90: 15,
last60: 12,
last30: 10,
last7: 5,
},
productionChanges: {
last30: 100,
last60: 200,
last90: 200,
},
previousDayMetricsBucketsCount: {
variantCount: 100,
enabledCount: 200,
},
};
}
private serializeStats(
instanceStats: InstanceStatsSigned,
): InstanceAdminStatsSchema {
const apiTokensObj = Object.fromEntries(
instanceStats.apiTokens.entries(),
);
return serializeDates({
...instanceStats,
apiTokens: apiTokensObj,
});
}
async getStatistics(
_: AuthedRequest,
res: Response<InstanceAdminStatsSchema>,
): Promise<void> {
const instanceStats = await this.instanceStatsService.getSignedStats();
res.json(this.serializeStats(instanceStats));
}
async getStatisticsCSV(
_: AuthedRequest,
res: Response<InstanceAdminStatsSchema>,
): Promise<void> {
const instanceStats = await this.instanceStatsService.getSignedStats();
const fileName = `unleash-${
instanceStats.instanceId
}-${Date.now()}.csv`;
const json2csvParser = new Parser();
const csv = json2csvParser.parse(this.serializeStats(instanceStats));
res.contentType('csv');
res.attachment(fileName);
res.send(csv);
}
}
export default InstanceAdminController;