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:
parent
18b5161ade
commit
555b27a653
@ -42,6 +42,8 @@ const featureCollectionDetails = {
|
|||||||
'Context Fields': 'The number of custom context fields in use',
|
'Context Fields': 'The number of custom context fields in use',
|
||||||
Groups: 'The number of groups present in your instance',
|
Groups: 'The number of groups present in your instance',
|
||||||
Roles: 'The number of custom roles defined 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',
|
Environments: 'The number of environments in your instance',
|
||||||
Segments: 'The number of segments defined in your instance',
|
Segments: 'The number of segments defined in your instance',
|
||||||
Strategies: 'The number of strategies defined in your instance',
|
Strategies: 'The number of strategies defined in your instance',
|
||||||
|
@ -9,6 +9,8 @@ import {
|
|||||||
} from 'lib/types/stores/role-store';
|
} from 'lib/types/stores/role-store';
|
||||||
import { IRole, IUserRole } from 'lib/types/stores/access-store';
|
import { IRole, IUserRole } from 'lib/types/stores/access-store';
|
||||||
import { Db } from './db';
|
import { Db } from './db';
|
||||||
|
import { PROJECT_ROLE_TYPES, ROOT_ROLE_TYPES } from '../util';
|
||||||
|
import { RoleSchema } from 'lib/openapi';
|
||||||
|
|
||||||
const T = {
|
const T = {
|
||||||
ROLE_USER: 'role_user',
|
ROLE_USER: 'role_user',
|
||||||
@ -54,6 +56,14 @@ export default class RoleStore implements IRoleStore {
|
|||||||
.then((res) => Number(res[0].count));
|
.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> {
|
async create(role: ICustomRoleInsert): Promise<ICustomRole> {
|
||||||
const row = await this.db(T.ROLES)
|
const row = await this.db(T.ROLES)
|
||||||
.insert({
|
.insert({
|
||||||
@ -144,8 +154,7 @@ export default class RoleStore implements IRoleStore {
|
|||||||
return this.db
|
return this.db
|
||||||
.select(['id', 'name', 'type', 'description'])
|
.select(['id', 'name', 'type', 'description'])
|
||||||
.from<IRole>(T.ROLES)
|
.from<IRole>(T.ROLES)
|
||||||
.where('type', 'custom')
|
.whereIn('type', PROJECT_ROLE_TYPES);
|
||||||
.orWhere('type', 'project');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRolesForProject(projectId: string): Promise<IRole[]> {
|
async getRolesForProject(projectId: string): Promise<IRole[]> {
|
||||||
@ -160,7 +169,7 @@ export default class RoleStore implements IRoleStore {
|
|||||||
return this.db
|
return this.db
|
||||||
.select(['id', 'name', 'type', 'description'])
|
.select(['id', 'name', 'type', 'description'])
|
||||||
.from<IRole>(T.ROLES)
|
.from<IRole>(T.ROLES)
|
||||||
.whereIn('type', ['root', 'root-custom']);
|
.whereIn('type', ROOT_ROLE_TYPES);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeRolesForProject(projectId: string): Promise<void> {
|
async removeRolesForProject(projectId: string): Promise<void> {
|
||||||
@ -177,7 +186,7 @@ export default class RoleStore implements IRoleStore {
|
|||||||
.distinctOn('user_id')
|
.distinctOn('user_id')
|
||||||
.from(`${T.ROLES} AS r`)
|
.from(`${T.ROLES} AS r`)
|
||||||
.leftJoin(`${T.ROLE_USER} AS ru`, 'r.id', 'ru.role_id')
|
.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) => ({
|
return rows.map((row) => ({
|
||||||
roleId: Number(row.id),
|
roleId: Number(row.id),
|
||||||
|
@ -122,7 +122,7 @@ test('should collect metrics for feature toggle size', async () => {
|
|||||||
expect(metrics).toMatch(/feature_toggles_total\{version="(.*)"\} 0/);
|
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) => {
|
await new Promise((done) => {
|
||||||
setTimeout(done, 10);
|
setTimeout(done, 10);
|
||||||
});
|
});
|
||||||
|
@ -104,6 +104,11 @@ export default class MetricsMonitor {
|
|||||||
help: 'Number of roles',
|
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({
|
const segmentsTotal = new client.Gauge({
|
||||||
name: 'segments_total',
|
name: 'segments_total',
|
||||||
help: 'Number of segments',
|
help: 'Number of segments',
|
||||||
@ -169,6 +174,9 @@ export default class MetricsMonitor {
|
|||||||
rolesTotal.reset();
|
rolesTotal.reset();
|
||||||
rolesTotal.set(stats.roles);
|
rolesTotal.set(stats.roles);
|
||||||
|
|
||||||
|
customRootRolesTotal.reset();
|
||||||
|
customRootRolesTotal.set(stats.customRootRoles);
|
||||||
|
|
||||||
segmentsTotal.reset();
|
segmentsTotal.reset();
|
||||||
segmentsTotal.set(stats.segments);
|
segmentsTotal.set(stats.segments);
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@ class InstanceAdminController extends Controller {
|
|||||||
instanceId: 'ed3861ae-78f9-4e8c-8e57-b57efc15f82b',
|
instanceId: 'ed3861ae-78f9-4e8c-8e57-b57efc15f82b',
|
||||||
projects: 1,
|
projects: 1,
|
||||||
roles: 5,
|
roles: 5,
|
||||||
|
customRootRoles: 1,
|
||||||
segments: 2,
|
segments: 2,
|
||||||
strategies: 8,
|
strategies: 8,
|
||||||
sum: 'some-sha256-hash',
|
sum: 'some-sha256-hash',
|
||||||
|
@ -18,6 +18,7 @@ import { IRoleStore } from '../types/stores/role-store';
|
|||||||
import VersionService from './version-service';
|
import VersionService from './version-service';
|
||||||
import { ISettingStore } from '../types/stores/settings-store';
|
import { ISettingStore } from '../types/stores/settings-store';
|
||||||
import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../types';
|
import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../types';
|
||||||
|
import { CUSTOM_ROOT_ROLE_TYPE } from '../util';
|
||||||
|
|
||||||
export type TimeRange = 'allTime' | '30d' | '7d';
|
export type TimeRange = 'allTime' | '30d' | '7d';
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ export interface InstanceStats {
|
|||||||
projects: number;
|
projects: number;
|
||||||
contextFields: number;
|
contextFields: number;
|
||||||
roles: number;
|
roles: number;
|
||||||
|
customRootRoles: number;
|
||||||
featureExports: number;
|
featureExports: number;
|
||||||
featureImports: number;
|
featureImports: number;
|
||||||
groups: number;
|
groups: number;
|
||||||
@ -177,6 +179,7 @@ export class InstanceStatsService {
|
|||||||
contextFields,
|
contextFields,
|
||||||
groups,
|
groups,
|
||||||
roles,
|
roles,
|
||||||
|
customRootRoles,
|
||||||
environments,
|
environments,
|
||||||
segments,
|
segments,
|
||||||
strategies,
|
strategies,
|
||||||
@ -192,6 +195,7 @@ export class InstanceStatsService {
|
|||||||
this.contextFieldStore.count(),
|
this.contextFieldStore.count(),
|
||||||
this.groupStore.count(),
|
this.groupStore.count(),
|
||||||
this.roleStore.count(),
|
this.roleStore.count(),
|
||||||
|
this.roleStore.filteredCount({ type: CUSTOM_ROOT_ROLE_TYPE }),
|
||||||
this.environmentStore.count(),
|
this.environmentStore.count(),
|
||||||
this.segmentStore.count(),
|
this.segmentStore.count(),
|
||||||
this.strategyStore.count(),
|
this.strategyStore.count(),
|
||||||
@ -212,6 +216,7 @@ export class InstanceStatsService {
|
|||||||
projects,
|
projects,
|
||||||
contextFields,
|
contextFields,
|
||||||
roles,
|
roles,
|
||||||
|
customRootRoles,
|
||||||
groups,
|
groups,
|
||||||
environments,
|
environments,
|
||||||
segments,
|
segments,
|
||||||
|
@ -19,6 +19,7 @@ import { ISettingStore } from '../types/stores/settings-store';
|
|||||||
import { hoursToMilliseconds } from 'date-fns';
|
import { hoursToMilliseconds } from 'date-fns';
|
||||||
import { IStrategyStore } from 'lib/types';
|
import { IStrategyStore } from 'lib/types';
|
||||||
import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../types';
|
import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../types';
|
||||||
|
import { CUSTOM_ROOT_ROLE_TYPE } from '../util';
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
oss: string;
|
oss: string;
|
||||||
@ -46,6 +47,7 @@ export interface IFeatureUsageInfo {
|
|||||||
projects: number;
|
projects: number;
|
||||||
contextFields: number;
|
contextFields: number;
|
||||||
roles: number;
|
roles: number;
|
||||||
|
customRootRoles: number;
|
||||||
featureExports: number;
|
featureExports: number;
|
||||||
featureImports: number;
|
featureImports: number;
|
||||||
groups: number;
|
groups: number;
|
||||||
@ -226,6 +228,7 @@ export default class VersionService {
|
|||||||
contextFields,
|
contextFields,
|
||||||
groups,
|
groups,
|
||||||
roles,
|
roles,
|
||||||
|
customRootRoles,
|
||||||
environments,
|
environments,
|
||||||
segments,
|
segments,
|
||||||
strategies,
|
strategies,
|
||||||
@ -242,6 +245,9 @@ export default class VersionService {
|
|||||||
this.contextFieldStore.count(),
|
this.contextFieldStore.count(),
|
||||||
this.groupStore.count(),
|
this.groupStore.count(),
|
||||||
this.roleStore.count(),
|
this.roleStore.count(),
|
||||||
|
this.roleStore.filteredCount({
|
||||||
|
type: CUSTOM_ROOT_ROLE_TYPE,
|
||||||
|
}),
|
||||||
this.environmentStore.count(),
|
this.environmentStore.count(),
|
||||||
this.segmentStore.count(),
|
this.segmentStore.count(),
|
||||||
this.strategyStore.count(),
|
this.strategyStore.count(),
|
||||||
@ -262,6 +268,7 @@ export default class VersionService {
|
|||||||
contextFields,
|
contextFields,
|
||||||
groups,
|
groups,
|
||||||
roles,
|
roles,
|
||||||
|
customRootRoles,
|
||||||
environments,
|
environments,
|
||||||
segments,
|
segments,
|
||||||
strategies,
|
strategies,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { RoleSchema } from 'lib/openapi';
|
||||||
import { ICustomRole } from '../model';
|
import { ICustomRole } from '../model';
|
||||||
import { IRole, IUserRole } from './access-store';
|
import { IRole, IUserRole } from './access-store';
|
||||||
import { Store } from './store';
|
import { Store } from './store';
|
||||||
@ -29,4 +30,5 @@ export interface IRoleStore extends Store<ICustomRole, number> {
|
|||||||
getRootRoleForAllUsers(): Promise<IUserRole[]>;
|
getRootRoleForAllUsers(): Promise<IUserRole[]>;
|
||||||
nameInUse(name: string, existingId?: number): Promise<boolean>;
|
nameInUse(name: string, existingId?: number): Promise<boolean>;
|
||||||
count(): Promise<number>;
|
count(): Promise<number>;
|
||||||
|
filteredCount(filter: Partial<RoleSchema>): Promise<number>;
|
||||||
}
|
}
|
||||||
|
5
src/test/fixtures/fake-role-store.ts
vendored
5
src/test/fixtures/fake-role-store.ts
vendored
@ -1,4 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import { RoleSchema } from 'lib/openapi';
|
||||||
import { ICustomRole } from 'lib/types/model';
|
import { ICustomRole } from 'lib/types/model';
|
||||||
import { IRole, IUserRole } from 'lib/types/stores/access-store';
|
import { IRole, IUserRole } from 'lib/types/stores/access-store';
|
||||||
import {
|
import {
|
||||||
@ -12,6 +13,10 @@ export default class FakeRoleStore implements IRoleStore {
|
|||||||
return Promise.resolve(0);
|
return Promise.resolve(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filteredCount(search: Partial<RoleSchema>): Promise<number> {
|
||||||
|
return Promise.resolve(0);
|
||||||
|
}
|
||||||
|
|
||||||
roles: ICustomRole[] = [];
|
roles: ICustomRole[] = [];
|
||||||
|
|
||||||
getGroupRolesForProject(projectId: string): Promise<IRole[]> {
|
getGroupRolesForProject(projectId: string): Promise<IRole[]> {
|
||||||
|
Loading…
Reference in New Issue
Block a user