mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: add remaining resource usage to instance stats (#7958)
Updates the instance stats endpoint with - maxEnvironmentStrategies - maxConstraints - maxConstraintValues It adds the following rows to the front end table: - segments (already in the payload, just not used for the table before) - API tokens (separate rows for type, + one for total) (also existed before, but wasn't listed) - Highest number of strategies used for a single flag in a single environment - Highest number of constraints used on a single strategy - Highest number of values used for a single constraint ![image](https://github.com/user-attachments/assets/57798f8e-c466-4590-820b-15afd3729243)
This commit is contained in:
parent
cb8d689bd8
commit
b0541a0af2
@ -8,13 +8,13 @@ import {
|
||||
TableRow,
|
||||
} from '@mui/material';
|
||||
import { Box } from '@mui/system';
|
||||
import type { VFC } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useInstanceStats } from 'hooks/api/getters/useInstanceStats/useInstanceStats';
|
||||
import { formatApiPath } from '../../../../utils/formatPath';
|
||||
import { PageContent } from '../../../common/PageContent/PageContent';
|
||||
import { PageHeader } from '../../../common/PageHeader/PageHeader';
|
||||
|
||||
export const InstanceStats: VFC = () => {
|
||||
export const InstanceStats: FC = () => {
|
||||
const { stats } = useInstanceStats();
|
||||
|
||||
let versionTitle: string;
|
||||
@ -28,6 +28,11 @@ export const InstanceStats: VFC = () => {
|
||||
version = stats?.versionOSS;
|
||||
}
|
||||
|
||||
const apiTokensTotal = Object.values(stats?.apiTokens ?? {}).reduce(
|
||||
(acc, val) => acc + val,
|
||||
0,
|
||||
);
|
||||
|
||||
const rows = [
|
||||
{ title: 'Instance Id', value: stats?.instanceId, offset: false },
|
||||
{ title: versionTitle, value: version },
|
||||
@ -41,6 +46,23 @@ export const InstanceStats: VFC = () => {
|
||||
{ title: 'Strategies', value: stats?.strategies },
|
||||
{ title: 'Feature exports', value: stats?.featureExports },
|
||||
{ title: 'Feature imports', value: stats?.featureImports },
|
||||
{ title: 'Admin API tokens', value: stats?.apiTokens?.admin },
|
||||
{ title: 'Client API tokens', value: stats?.apiTokens?.client },
|
||||
{ title: 'Frontend API tokens', value: stats?.apiTokens?.frontend },
|
||||
{ title: 'API tokens total', value: apiTokensTotal },
|
||||
{ title: 'Segments', value: stats?.segments },
|
||||
{
|
||||
title: 'Highest number of strategies used for a single flag in a single environment',
|
||||
value: stats?.maxEnvironmentStrategies,
|
||||
},
|
||||
{
|
||||
title: 'Highest number of constraints used on a single strategy',
|
||||
value: stats?.maxConstraints,
|
||||
},
|
||||
{
|
||||
title: 'Highest number of values used for a single constraint',
|
||||
value: stats?.maxConstraintValues,
|
||||
},
|
||||
];
|
||||
|
||||
if (stats?.versionEnterprise) {
|
||||
|
@ -92,4 +92,20 @@ export interface InstanceAdminStatsSchema {
|
||||
versionEnterprise?: string;
|
||||
/** The version of Unleash OSS that is bundled in this instance */
|
||||
versionOSS?: string;
|
||||
|
||||
/** A breakdown of API tokens that exist in this instance */
|
||||
apiTokens: {
|
||||
client: number;
|
||||
admin: number;
|
||||
frontend: number;
|
||||
};
|
||||
|
||||
// The highest number of strategies used on a single feature flag in a single environment.
|
||||
maxEnvironmentStrategies: number;
|
||||
|
||||
// The highest number of constraints used on a single strategy.
|
||||
maxConstraints: number;
|
||||
|
||||
// The highest number of constraint values used on a single constraint.
|
||||
maxConstraintValues: number;
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ import FakeSettingStore from '../../../test/fixtures/fake-setting-store';
|
||||
import FakeSegmentStore from '../../../test/fixtures/fake-segment-store';
|
||||
import FakeStrategiesStore from '../../../test/fixtures/fake-strategies-store';
|
||||
import FakeFeatureStrategiesStore from '../feature-toggle/fakes/fake-feature-strategies-store';
|
||||
import { FeatureStrategiesReadModel } from '../feature-toggle/feature-strategies-read-model';
|
||||
import { FakeFeatureStrategiesReadModel } from '../feature-toggle/fake-feature-strategies-read-model';
|
||||
|
||||
export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
|
||||
const { eventBus, getLogger, flagResolver } = config;
|
||||
@ -89,6 +91,8 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
|
||||
getLogger,
|
||||
flagResolver,
|
||||
);
|
||||
|
||||
const featureStrategiesReadModel = new FeatureStrategiesReadModel(db);
|
||||
const instanceStatsServiceStores = {
|
||||
featureToggleStore,
|
||||
userStore,
|
||||
@ -104,6 +108,7 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
|
||||
eventStore,
|
||||
apiTokenStore,
|
||||
clientMetricsStoreV2,
|
||||
featureStrategiesReadModel,
|
||||
};
|
||||
const featureStrategiesStore = new FeatureStrategyStore(
|
||||
db,
|
||||
@ -151,6 +156,7 @@ export const createFakeInstanceStatsService = (config: IUnleashConfig) => {
|
||||
const eventStore = new FakeEventStore();
|
||||
const apiTokenStore = new FakeApiTokenStore();
|
||||
const clientMetricsStoreV2 = new FakeClientMetricsStoreV2();
|
||||
const featureStrategiesReadModel = new FakeFeatureStrategiesReadModel();
|
||||
|
||||
const instanceStatsServiceStores = {
|
||||
featureToggleStore,
|
||||
@ -167,6 +173,7 @@ export const createFakeInstanceStatsService = (config: IUnleashConfig) => {
|
||||
eventStore,
|
||||
apiTokenStore,
|
||||
clientMetricsStoreV2,
|
||||
featureStrategiesReadModel,
|
||||
};
|
||||
const featureStrategiesStore = new FakeFeatureStrategiesStore();
|
||||
const versionServiceStores = {
|
||||
|
@ -5,6 +5,7 @@ import type {
|
||||
IClientInstanceStore,
|
||||
IClientMetricsStoreV2,
|
||||
IEventStore,
|
||||
IFeatureStrategiesReadModel,
|
||||
IUnleashStores,
|
||||
} from '../../types/stores';
|
||||
import type { IContextFieldStore } from '../../types/stores/context-field-store';
|
||||
@ -61,6 +62,9 @@ export interface InstanceStats {
|
||||
enabledCount: number;
|
||||
variantCount: number;
|
||||
};
|
||||
maxEnvironmentStrategies: number;
|
||||
maxConstraints: number;
|
||||
maxConstraintValues: number;
|
||||
}
|
||||
|
||||
export type InstanceStatsSigned = Omit<InstanceStats, 'projects'> & {
|
||||
@ -109,6 +113,8 @@ export class InstanceStatsService {
|
||||
|
||||
private getProductionChanges: GetProductionChanges;
|
||||
|
||||
private featureStrategiesReadModel: IFeatureStrategiesReadModel;
|
||||
|
||||
constructor(
|
||||
{
|
||||
featureToggleStore,
|
||||
@ -125,6 +131,7 @@ export class InstanceStatsService {
|
||||
eventStore,
|
||||
apiTokenStore,
|
||||
clientMetricsStoreV2,
|
||||
featureStrategiesReadModel,
|
||||
}: Pick<
|
||||
IUnleashStores,
|
||||
| 'featureToggleStore'
|
||||
@ -141,6 +148,7 @@ export class InstanceStatsService {
|
||||
| 'eventStore'
|
||||
| 'apiTokenStore'
|
||||
| 'clientMetricsStoreV2'
|
||||
| 'featureStrategiesReadModel'
|
||||
>,
|
||||
{
|
||||
getLogger,
|
||||
@ -169,6 +177,7 @@ export class InstanceStatsService {
|
||||
this.apiTokenStore = apiTokenStore;
|
||||
this.clientMetricsStore = clientMetricsStoreV2;
|
||||
this.flagResolver = flagResolver;
|
||||
this.featureStrategiesReadModel = featureStrategiesReadModel;
|
||||
}
|
||||
|
||||
async refreshAppCountSnapshot(): Promise<
|
||||
@ -250,6 +259,9 @@ export class InstanceStatsService {
|
||||
featureImports,
|
||||
productionChanges,
|
||||
previousDayMetricsBucketsCount,
|
||||
maxEnvironmentStrategies,
|
||||
maxConstraintValues,
|
||||
maxConstraints,
|
||||
] = await Promise.all([
|
||||
this.getToggleCount(),
|
||||
this.getArchivedToggleCount(),
|
||||
@ -277,6 +289,9 @@ export class InstanceStatsService {
|
||||
}),
|
||||
this.getProductionChanges(),
|
||||
this.clientMetricsStore.countPreviousDayHourlyMetricsBuckets(),
|
||||
this.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies(),
|
||||
this.featureStrategiesReadModel.getMaxConstraintValues(),
|
||||
this.featureStrategiesReadModel.getMaxConstraintsPerStrategy(),
|
||||
]);
|
||||
|
||||
return {
|
||||
@ -309,6 +324,9 @@ export class InstanceStatsService {
|
||||
featureImports,
|
||||
productionChanges,
|
||||
previousDayMetricsBucketsCount,
|
||||
maxEnvironmentStrategies: maxEnvironmentStrategies?.count ?? 0,
|
||||
maxConstraintValues: maxConstraintValues?.count ?? 0,
|
||||
maxConstraints: maxConstraints?.count ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ export const instanceAdminStatsSchema = {
|
||||
example: '5.1.7',
|
||||
},
|
||||
users: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of users this instance has',
|
||||
example: 8,
|
||||
minimum: 0,
|
||||
@ -45,14 +45,14 @@ export const instanceAdminStatsSchema = {
|
||||
'The number client metrics buckets records recorded in the previous day. # features * # apps * # envs * # hours with metrics',
|
||||
properties: {
|
||||
enabledCount: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of enabled/disabled metrics buckets recorded in the previous day',
|
||||
example: 10,
|
||||
minimum: 0,
|
||||
},
|
||||
variantCount: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of variant metrics buckets recorded in the previous day',
|
||||
example: 10,
|
||||
@ -66,28 +66,28 @@ export const instanceAdminStatsSchema = {
|
||||
'The number of active users in the last 7, 30 and 90 days',
|
||||
properties: {
|
||||
last7: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of active users in the last 7 days',
|
||||
example: 5,
|
||||
minimum: 0,
|
||||
},
|
||||
last30: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of active users in the last 30 days',
|
||||
example: 10,
|
||||
minimum: 0,
|
||||
},
|
||||
last60: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of active users in the last 60 days',
|
||||
example: 12,
|
||||
minimum: 0,
|
||||
},
|
||||
last90: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of active users in the last 90 days',
|
||||
example: 15,
|
||||
@ -101,21 +101,21 @@ export const instanceAdminStatsSchema = {
|
||||
'The number of changes to the production environment in the last 30, 60 and 90 days',
|
||||
properties: {
|
||||
last30: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of changes in production in the last 30 days',
|
||||
example: 10,
|
||||
minimum: 0,
|
||||
},
|
||||
last60: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of changes in production in the last 60 days',
|
||||
example: 12,
|
||||
minimum: 0,
|
||||
},
|
||||
last90: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of changes in production in the last 90 days',
|
||||
example: 15,
|
||||
@ -124,50 +124,50 @@ export const instanceAdminStatsSchema = {
|
||||
},
|
||||
},
|
||||
featureToggles: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of feature-toggles this instance has',
|
||||
example: 47,
|
||||
minimum: 0,
|
||||
},
|
||||
projects: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of projects defined in this instance.',
|
||||
example: 3,
|
||||
minimum: 0,
|
||||
},
|
||||
contextFields: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of context fields defined in this instance.',
|
||||
example: 7,
|
||||
minimum: 0,
|
||||
},
|
||||
roles: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of roles defined in this instance',
|
||||
example: 5,
|
||||
minimum: 0,
|
||||
},
|
||||
groups: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of groups defined in this instance',
|
||||
example: 12,
|
||||
minimum: 0,
|
||||
},
|
||||
environments: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of environments defined in this instance',
|
||||
example: 3,
|
||||
minimum: 0,
|
||||
},
|
||||
segments: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of segments defined in this instance',
|
||||
example: 19,
|
||||
minimum: 0,
|
||||
},
|
||||
strategies: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of strategies defined in this instance',
|
||||
example: 8,
|
||||
minimum: 0,
|
||||
@ -200,7 +200,7 @@ export const instanceAdminStatsSchema = {
|
||||
example: '30d',
|
||||
},
|
||||
count: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description:
|
||||
'The number of client applications that have been observed in this period',
|
||||
example: 1,
|
||||
@ -209,13 +209,13 @@ export const instanceAdminStatsSchema = {
|
||||
},
|
||||
},
|
||||
featureExports: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of export operations on this instance',
|
||||
example: 0,
|
||||
minimum: 0,
|
||||
},
|
||||
featureImports: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of import operations on this instance',
|
||||
example: 0,
|
||||
minimum: 0,
|
||||
@ -225,25 +225,46 @@ export const instanceAdminStatsSchema = {
|
||||
description: 'The number of API tokens in Unleash, split by type',
|
||||
properties: {
|
||||
admin: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of admin tokens.',
|
||||
minimum: 0,
|
||||
example: 5,
|
||||
},
|
||||
client: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of client tokens.',
|
||||
minimum: 0,
|
||||
example: 5,
|
||||
},
|
||||
frontend: {
|
||||
type: 'number',
|
||||
type: 'integer',
|
||||
description: 'The number of frontend tokens.',
|
||||
minimum: 0,
|
||||
example: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
maxEnvironmentStrategies: {
|
||||
type: 'integer',
|
||||
minimum: 0,
|
||||
example: 3,
|
||||
description:
|
||||
'The highest number of strategies used on a single feature flag in a single environment.',
|
||||
},
|
||||
maxConstraints: {
|
||||
type: 'integer',
|
||||
minimum: 0,
|
||||
example: 4,
|
||||
description:
|
||||
'The highest number of constraints used on a single strategy.',
|
||||
},
|
||||
maxConstraintValues: {
|
||||
type: 'integer',
|
||||
minimum: 0,
|
||||
example: 17,
|
||||
description:
|
||||
'The highest number of constraint values used on a single constraint.',
|
||||
},
|
||||
sum: {
|
||||
type: 'string',
|
||||
description:
|
||||
|
@ -126,6 +126,9 @@ class InstanceAdminController extends Controller {
|
||||
variantCount: 100,
|
||||
enabledCount: 200,
|
||||
},
|
||||
maxEnvironmentStrategies: 20,
|
||||
maxConstraints: 17,
|
||||
maxConstraintValues: 123,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -133,3 +133,16 @@ test('should return instance statistics as CSV', async () => {
|
||||
expect(res.text).toMatch(/featureToggles/);
|
||||
expect(res.text).toMatch(/"sum"/);
|
||||
});
|
||||
|
||||
test('contains new max* properties', async () => {
|
||||
const { body } = await app.request
|
||||
.get('/api/admin/instance-admin/statistics')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
|
||||
expect(body).toMatchObject({
|
||||
maxEnvironmentStrategies: 0,
|
||||
maxConstraints: 0,
|
||||
maxConstraintValues: 0,
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user