1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

feat: add prom metric for total custom root roles (#4435)

https://linear.app/unleash/issue/2-1293/label-our-metrics-about-roles-to-include-also-if-the-role-is-a-root

Adds a Prometheus metric for total custom root roles. Also adds it to
the instance telemetry collection.

Q: Should we use a `labeledRoles` kind of metric instead, similar to
what we're doing for `clientApps` and their ranges?
This commit is contained in:
Nuno Góis 2023-08-07 14:59:29 +01:00 committed by GitHub
parent 18b5161ade
commit 555b27a653
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 44 additions and 5 deletions

View File

@ -42,6 +42,8 @@ const featureCollectionDetails = {
'Context Fields': 'The number of custom context fields in use',
Groups: 'The number of groups present in your instance',
Roles: 'The number of custom roles defined in your instance',
'Custom Root Roles':
'The number of custom root roles defined in your instance',
Environments: 'The number of environments in your instance',
Segments: 'The number of segments defined in your instance',
Strategies: 'The number of strategies defined in your instance',

View File

@ -9,6 +9,8 @@ import {
} from 'lib/types/stores/role-store';
import { IRole, IUserRole } from 'lib/types/stores/access-store';
import { Db } from './db';
import { PROJECT_ROLE_TYPES, ROOT_ROLE_TYPES } from '../util';
import { RoleSchema } from 'lib/openapi';
const T = {
ROLE_USER: 'role_user',
@ -54,6 +56,14 @@ export default class RoleStore implements IRoleStore {
.then((res) => Number(res[0].count));
}
async filteredCount(filter: Partial<RoleSchema>): Promise<number> {
return this.db
.from(T.ROLES)
.count('*')
.where(filter)
.then((res) => Number(res[0].count));
}
async create(role: ICustomRoleInsert): Promise<ICustomRole> {
const row = await this.db(T.ROLES)
.insert({
@ -144,8 +154,7 @@ export default class RoleStore implements IRoleStore {
return this.db
.select(['id', 'name', 'type', 'description'])
.from<IRole>(T.ROLES)
.where('type', 'custom')
.orWhere('type', 'project');
.whereIn('type', PROJECT_ROLE_TYPES);
}
async getRolesForProject(projectId: string): Promise<IRole[]> {
@ -160,7 +169,7 @@ export default class RoleStore implements IRoleStore {
return this.db
.select(['id', 'name', 'type', 'description'])
.from<IRole>(T.ROLES)
.whereIn('type', ['root', 'root-custom']);
.whereIn('type', ROOT_ROLE_TYPES);
}
async removeRolesForProject(projectId: string): Promise<void> {
@ -177,7 +186,7 @@ export default class RoleStore implements IRoleStore {
.distinctOn('user_id')
.from(`${T.ROLES} AS r`)
.leftJoin(`${T.ROLE_USER} AS ru`, 'r.id', 'ru.role_id')
.whereIn('r.type', ['root', 'root-custom']);
.whereIn('r.type', ROOT_ROLE_TYPES);
return rows.map((row) => ({
roleId: Number(row.id),

View File

@ -122,7 +122,7 @@ test('should collect metrics for feature toggle size', async () => {
expect(metrics).toMatch(/feature_toggles_total\{version="(.*)"\} 0/);
});
test('should collect metrics for feature toggle size', async () => {
test('should collect metrics for total client apps', async () => {
await new Promise((done) => {
setTimeout(done, 10);
});

View File

@ -104,6 +104,11 @@ export default class MetricsMonitor {
help: 'Number of roles',
});
const customRootRolesTotal = new client.Gauge({
name: 'custom_root_roles_total',
help: 'Number of custom root roles',
});
const segmentsTotal = new client.Gauge({
name: 'segments_total',
help: 'Number of segments',
@ -169,6 +174,9 @@ export default class MetricsMonitor {
rolesTotal.reset();
rolesTotal.set(stats.roles);
customRootRolesTotal.reset();
customRootRolesTotal.set(stats.customRootRoles);
segmentsTotal.reset();
segmentsTotal.set(stats.segments);

View File

@ -99,6 +99,7 @@ class InstanceAdminController extends Controller {
instanceId: 'ed3861ae-78f9-4e8c-8e57-b57efc15f82b',
projects: 1,
roles: 5,
customRootRoles: 1,
segments: 2,
strategies: 8,
sum: 'some-sha256-hash',

View File

@ -18,6 +18,7 @@ import { IRoleStore } from '../types/stores/role-store';
import VersionService from './version-service';
import { ISettingStore } from '../types/stores/settings-store';
import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../types';
import { CUSTOM_ROOT_ROLE_TYPE } from '../util';
export type TimeRange = 'allTime' | '30d' | '7d';
@ -31,6 +32,7 @@ export interface InstanceStats {
projects: number;
contextFields: number;
roles: number;
customRootRoles: number;
featureExports: number;
featureImports: number;
groups: number;
@ -177,6 +179,7 @@ export class InstanceStatsService {
contextFields,
groups,
roles,
customRootRoles,
environments,
segments,
strategies,
@ -192,6 +195,7 @@ export class InstanceStatsService {
this.contextFieldStore.count(),
this.groupStore.count(),
this.roleStore.count(),
this.roleStore.filteredCount({ type: CUSTOM_ROOT_ROLE_TYPE }),
this.environmentStore.count(),
this.segmentStore.count(),
this.strategyStore.count(),
@ -212,6 +216,7 @@ export class InstanceStatsService {
projects,
contextFields,
roles,
customRootRoles,
groups,
environments,
segments,

View File

@ -19,6 +19,7 @@ import { ISettingStore } from '../types/stores/settings-store';
import { hoursToMilliseconds } from 'date-fns';
import { IStrategyStore } from 'lib/types';
import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../types';
import { CUSTOM_ROOT_ROLE_TYPE } from '../util';
export interface IVersionInfo {
oss: string;
@ -46,6 +47,7 @@ export interface IFeatureUsageInfo {
projects: number;
contextFields: number;
roles: number;
customRootRoles: number;
featureExports: number;
featureImports: number;
groups: number;
@ -226,6 +228,7 @@ export default class VersionService {
contextFields,
groups,
roles,
customRootRoles,
environments,
segments,
strategies,
@ -242,6 +245,9 @@ export default class VersionService {
this.contextFieldStore.count(),
this.groupStore.count(),
this.roleStore.count(),
this.roleStore.filteredCount({
type: CUSTOM_ROOT_ROLE_TYPE,
}),
this.environmentStore.count(),
this.segmentStore.count(),
this.strategyStore.count(),
@ -262,6 +268,7 @@ export default class VersionService {
contextFields,
groups,
roles,
customRootRoles,
environments,
segments,
strategies,

View File

@ -1,3 +1,4 @@
import { RoleSchema } from 'lib/openapi';
import { ICustomRole } from '../model';
import { IRole, IUserRole } from './access-store';
import { Store } from './store';
@ -29,4 +30,5 @@ export interface IRoleStore extends Store<ICustomRole, number> {
getRootRoleForAllUsers(): Promise<IUserRole[]>;
nameInUse(name: string, existingId?: number): Promise<boolean>;
count(): Promise<number>;
filteredCount(filter: Partial<RoleSchema>): Promise<number>;
}

View File

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { RoleSchema } from 'lib/openapi';
import { ICustomRole } from 'lib/types/model';
import { IRole, IUserRole } from 'lib/types/stores/access-store';
import {
@ -12,6 +13,10 @@ export default class FakeRoleStore implements IRoleStore {
return Promise.resolve(0);
}
filteredCount(search: Partial<RoleSchema>): Promise<number> {
return Promise.resolve(0);
}
roles: ICustomRole[] = [];
getGroupRolesForProject(projectId: string): Promise<IRole[]> {