diff --git a/src/lib/db/project-store.ts b/src/lib/db/project-store.ts index 7689409c5c..c2f45f21b7 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,28 @@ class ProjectStore implements IProjectStore { .then((res) => Number(res[0].count)); } + async getProjectModeCounts(): Promise { + const result: ProjectModeCount[] = await this.db + .select(`${SETTINGS_TABLE}.project_mode as mode`) + .count(`${TABLE}.id as count`) + .from(`${TABLE}`) + .join( + `${SETTINGS_TABLE}`, + `${TABLE}.id`, + `${SETTINGS_TABLE}.project`, + ) + .groupBy(`${SETTINGS_TABLE}.project_mode`); + return result.map(this.mapProjectModeCount); + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + mapProjectModeCount(row): ProjectModeCount { + return { + mode: row.mode, + count: parseInt(row.count, 10), + }; + } + // 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..26631e84ba 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; @@ -152,6 +153,10 @@ export class InstanceStatsService { } } + getProjectModeCount(): Promise { + return this.projectStore.getProjectModeCounts(); + } + getToggleCount(): Promise { return this.featureToggleStore.count({ archived: false, @@ -201,7 +206,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(), 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..030cc848f1 100644 --- a/src/lib/routes/admin-api/instance-admin.ts +++ b/src/lib/routes/admin-api/instance-admin.ts @@ -97,7 +97,11 @@ class InstanceAdminController extends Controller { featureToggles: 29, groups: 3, instanceId: 'ed3861ae-78f9-4e8c-8e57-b57efc15f82b', - projects: 1, + projects: [ + { mode: 'open', count: 5 }, + { mode: 'protected', count: 2 }, + { mode: 'private', count: 1 }, + ], roles: 5, customRootRoles: 2, customRootRolesInUse: 1, 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([]); + } }