From f7d87a2339ff386cf2d38cf86032980da00d723e Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Mon, 25 Sep 2023 12:07:59 +0300 Subject: [PATCH] feat: add project collaboration mode to prometheus (#4819) --- src/lib/db/project-store.ts | 34 +++++++++++++++++++ .../instance-stats/instance-stats-service.ts | 21 ++++++++---- src/lib/metrics.ts | 7 +++- src/lib/routes/admin-api/instance-admin.ts | 5 ++- src/lib/types/stores/project-store.ts | 19 +++-------- src/test/fixtures/fake-project-store.ts | 5 +++ 6 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/lib/db/project-store.ts b/src/lib/db/project-store.ts index 7689409c5c..d2a88a0cc8 100644 --- a/src/lib/db/project-store.ts +++ b/src/lib/db/project-store.ts @@ -7,6 +7,7 @@ import { IFlagResolver, IProject, IProjectWithCount, + ProjectMode, } from '../types'; import { IProjectHealthUpdate, @@ -49,6 +50,11 @@ export interface IEnvironmentProjectLink { projectId: string; } +export interface ProjectModeCount { + mode: ProjectMode; + count: number; +} + export interface IProjectMembersCount { count: number; project: string; @@ -551,6 +557,34 @@ class ProjectStore implements IProjectStore { .then((res) => Number(res[0].count)); } + async getProjectModeCounts(): Promise { + const result: ProjectModeCount[] = await this.db + .select( + this.db.raw( + `COALESCE(${SETTINGS_TABLE}.project_mode, 'open') as mode`, + ), + ) + .count(`${TABLE}.id as count`) + .from(`${TABLE}`) + .join( + `${SETTINGS_TABLE}`, + `${TABLE}.id`, + `${SETTINGS_TABLE}.project`, + ) + .groupBy( + this.db.raw(`COALESCE(${SETTINGS_TABLE}.project_mode, 'open')`), + ); + return result.map(this.mapProjectModeCount); + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + mapProjectModeCount(row): ProjectModeCount { + return { + mode: row.mode, + count: Number(row.count), + }; + } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types mapLinkRow(row): IEnvironmentProjectLink { return { diff --git a/src/lib/features/instance-stats/instance-stats-service.ts b/src/lib/features/instance-stats/instance-stats-service.ts index 901eb1604b..9cce8af42c 100644 --- a/src/lib/features/instance-stats/instance-stats-service.ts +++ b/src/lib/features/instance-stats/instance-stats-service.ts @@ -20,6 +20,7 @@ import { ISettingStore } from '../../types/stores/settings-store'; import { FEATURES_EXPORTED, FEATURES_IMPORTED } from '../../types'; import { CUSTOM_ROOT_ROLE_TYPE } from '../../util'; import { type GetActiveUsers } from './getActiveUsers'; +import { ProjectModeCount } from '../../db/project-store'; export type TimeRange = 'allTime' | '30d' | '7d'; @@ -30,7 +31,7 @@ export interface InstanceStats { versionEnterprise?: string; users: number; featureToggles: number; - projects: number; + projects: ProjectModeCount[]; contextFields: number; roles: number; customRootRoles: number; @@ -47,9 +48,10 @@ export interface InstanceStats { activeUsers: Awaited>; } -export interface InstanceStatsSigned extends InstanceStats { +export type InstanceStatsSigned = Omit & { + projects: number; sum: string; -} +}; export class InstanceStatsService { private logger: Logger; @@ -152,6 +154,10 @@ export class InstanceStatsService { } } + getProjectModeCount(): Promise { + return this.projectStore.getProjectModeCounts(); + } + getToggleCount(): Promise { return this.featureToggleStore.count({ archived: false, @@ -201,7 +207,7 @@ export class InstanceStatsService { this.getToggleCount(), this.userStore.count(), this.getActiveUsers(), - this.projectStore.count(), + this.getProjectModeCount(), this.contextFieldStore.count(), this.groupStore.count(), this.roleStore.count(), @@ -275,10 +281,13 @@ export class InstanceStatsService { async getSignedStats(): Promise { const instanceStats = await this.getStats(); + const totalProjects = instanceStats.projects + .map((p) => p.count) + .reduce((a, b) => a + b, 0); const sum = sha256( - `${instanceStats.instanceId}${instanceStats.users}${instanceStats.featureToggles}${instanceStats.projects}${instanceStats.roles}${instanceStats.groups}${instanceStats.environments}${instanceStats.segments}`, + `${instanceStats.instanceId}${instanceStats.users}${instanceStats.featureToggles}${totalProjects}${instanceStats.roles}${instanceStats.groups}${instanceStats.environments}${instanceStats.segments}`, ); - return { ...instanceStats, sum }; + return { ...instanceStats, sum, projects: totalProjects }; } } diff --git a/src/lib/metrics.ts b/src/lib/metrics.ts index 18c8824ffd..fbdc89937a 100644 --- a/src/lib/metrics.ts +++ b/src/lib/metrics.ts @@ -105,6 +105,7 @@ export default class MetricsMonitor { const projectsTotal = new client.Gauge({ name: 'projects_total', help: 'Number of projects', + labelNames: ['mode'], }); const environmentsTotal = new client.Gauge({ name: 'environments_total', @@ -193,7 +194,11 @@ export default class MetricsMonitor { usersActive90days.set(stats.activeUsers.last90); projectsTotal.reset(); - projectsTotal.set(stats.projects); + stats.projects.forEach((projectStat) => { + projectsTotal + .labels({ mode: projectStat.mode }) + .set(projectStat.count); + }); environmentsTotal.reset(); environmentsTotal.set(stats.environments); diff --git a/src/lib/routes/admin-api/instance-admin.ts b/src/lib/routes/admin-api/instance-admin.ts index 02610a3e69..9b8a343e75 100644 --- a/src/lib/routes/admin-api/instance-admin.ts +++ b/src/lib/routes/admin-api/instance-admin.ts @@ -7,7 +7,6 @@ import Controller from '../controller'; import { NONE } from '../../types/permissions'; import { UiConfigSchema } from '../../openapi/spec/ui-config-schema'; import { - InstanceStats, InstanceStatsService, InstanceStatsSigned, } from '../../features/instance-stats/instance-stats-service'; @@ -97,7 +96,7 @@ class InstanceAdminController extends Controller { featureToggles: 29, groups: 3, instanceId: 'ed3861ae-78f9-4e8c-8e57-b57efc15f82b', - projects: 1, + projects: 4, roles: 5, customRootRoles: 2, customRootRolesInUse: 1, @@ -119,7 +118,7 @@ class InstanceAdminController extends Controller { async getStatistics( req: AuthedRequest, - res: Response, + res: Response, ): Promise { const instanceStats = await this.instanceStatsService.getSignedStats(); res.json(instanceStats); diff --git a/src/lib/types/stores/project-store.ts b/src/lib/types/stores/project-store.ts index dd565298a9..da6e8cfeb1 100644 --- a/src/lib/types/stores/project-store.ts +++ b/src/lib/types/stores/project-store.ts @@ -1,6 +1,7 @@ import { IEnvironmentProjectLink, IProjectMembersCount, + ProjectModeCount, } from '../../db/project-store'; import { IEnvironment, @@ -32,21 +33,6 @@ export interface IProjectSettings { featureNamingDescription?: string; } -export interface IProjectSettingsRow { - project_mode: ProjectMode; - default_stickiness: string; -} - -export interface IProjectEnvironmenDefaultStrategyRow { - environment: string; - default_strategy: any; -} - -export interface IProjectArchived { - id: string; - archived: boolean; -} - export interface IProjectHealthUpdate { id: string; health: number; @@ -115,6 +101,7 @@ export interface IProjectStore extends Store { projectId: string, environment: string, ): Promise; + updateDefaultStrategy( projectId: string, environment: string, @@ -122,4 +109,6 @@ export interface IProjectStore extends Store { ): Promise; isFeatureLimitReached(id: string): Promise; + + getProjectModeCounts(): Promise; } diff --git a/src/test/fixtures/fake-project-store.ts b/src/test/fixtures/fake-project-store.ts index 0645cda6a9..28eae24098 100644 --- a/src/test/fixtures/fake-project-store.ts +++ b/src/test/fixtures/fake-project-store.ts @@ -9,6 +9,7 @@ import NotFoundError from '../../lib/error/notfound-error'; import { IEnvironmentProjectLink, IProjectMembersCount, + ProjectModeCount, } from 'lib/db/project-store'; import { CreateFeatureStrategySchema } from '../../lib/openapi'; @@ -185,4 +186,8 @@ export default class FakeProjectStore implements IProjectStore { isFeatureLimitReached(id: string): Promise { return Promise.resolve(false); } + + getProjectModeCounts(): Promise { + return Promise.resolve([]); + } }