mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: Max strategies metrics (#7392)
This commit is contained in:
parent
8e64303ef6
commit
0c79b36b74
@ -46,6 +46,7 @@ import { SegmentReadModel } from '../features/segment/segment-read-model';
|
|||||||
import { ProjectOwnersReadModel } from '../features/project/project-owners-read-model';
|
import { ProjectOwnersReadModel } from '../features/project/project-owners-read-model';
|
||||||
import { FeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store';
|
import { FeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store';
|
||||||
import { ProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model';
|
import { ProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model';
|
||||||
|
import { FeatureStrategiesReadModel } from '../features/feature-toggle/feature-strategies-read-model';
|
||||||
|
|
||||||
export const createStores = (
|
export const createStores = (
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
@ -159,6 +160,7 @@ export const createStores = (
|
|||||||
projectOwnersReadModel: new ProjectOwnersReadModel(db),
|
projectOwnersReadModel: new ProjectOwnersReadModel(db),
|
||||||
projectFlagCreatorsReadModel: new ProjectFlagCreatorsReadModel(db),
|
projectFlagCreatorsReadModel: new ProjectFlagCreatorsReadModel(db),
|
||||||
featureLifecycleStore: new FeatureLifecycleStore(db),
|
featureLifecycleStore: new FeatureLifecycleStore(db),
|
||||||
|
featureStrategiesReadModel: new FeatureStrategiesReadModel(db),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import type { IFeatureStrategiesReadModel } from './types/feature-strategies-read-model-type';
|
||||||
|
|
||||||
|
export class FakeFeatureStrategiesReadModel
|
||||||
|
implements IFeatureStrategiesReadModel
|
||||||
|
{
|
||||||
|
async getMaxFeatureEnvironmentStrategies(): Promise<{
|
||||||
|
feature: string;
|
||||||
|
environment: string;
|
||||||
|
count: number;
|
||||||
|
} | null> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMaxFeatureStrategies(): Promise<{
|
||||||
|
feature: string;
|
||||||
|
count: number;
|
||||||
|
} | null> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
import type { Db } from '../../db/db';
|
||||||
|
import type { IFeatureStrategiesReadModel } from './types/feature-strategies-read-model-type';
|
||||||
|
|
||||||
|
export class FeatureStrategiesReadModel implements IFeatureStrategiesReadModel {
|
||||||
|
private db: Db;
|
||||||
|
|
||||||
|
constructor(db: Db) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMaxFeatureEnvironmentStrategies(): Promise<{
|
||||||
|
feature: string;
|
||||||
|
environment: string;
|
||||||
|
count: number;
|
||||||
|
} | null> {
|
||||||
|
const rows = await this.db('feature_strategies')
|
||||||
|
.select('feature_name', 'environment')
|
||||||
|
.count('id as strategy_count')
|
||||||
|
.groupBy('feature_name', 'environment')
|
||||||
|
.orderBy('strategy_count', 'desc')
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return rows.length > 0
|
||||||
|
? {
|
||||||
|
feature: String(rows[0].feature_name),
|
||||||
|
environment: String(rows[0].environment),
|
||||||
|
count: Number(rows[0].strategy_count),
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMaxFeatureStrategies(): Promise<{
|
||||||
|
feature: string;
|
||||||
|
count: number;
|
||||||
|
} | null> {
|
||||||
|
const rows = await this.db('feature_strategies')
|
||||||
|
.select('feature_name')
|
||||||
|
.count('id as strategy_count')
|
||||||
|
.groupBy('feature_name')
|
||||||
|
.orderBy('strategy_count', 'desc')
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return rows.length > 0
|
||||||
|
? {
|
||||||
|
feature: String(rows[0].feature_name),
|
||||||
|
count: Number(rows[0].strategy_count),
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
@ -4,13 +4,18 @@ import dbInit, {
|
|||||||
type ITestDb,
|
type ITestDb,
|
||||||
} from '../../../../test/e2e/helpers/database-init';
|
} from '../../../../test/e2e/helpers/database-init';
|
||||||
import getLogger from '../../../../test/fixtures/no-logger';
|
import getLogger from '../../../../test/fixtures/no-logger';
|
||||||
import type { IProjectStore, IUnleashStores } from '../../../types';
|
import type {
|
||||||
|
IFeatureStrategiesReadModel,
|
||||||
|
IProjectStore,
|
||||||
|
IUnleashStores,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
let stores: IUnleashStores;
|
let stores: IUnleashStores;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
let featureStrategiesStore: IFeatureStrategiesStore;
|
let featureStrategiesStore: IFeatureStrategiesStore;
|
||||||
let featureToggleStore: IFeatureToggleStore;
|
let featureToggleStore: IFeatureToggleStore;
|
||||||
let projectStore: IProjectStore;
|
let projectStore: IProjectStore;
|
||||||
|
let featureStrategiesReadModel: IFeatureStrategiesReadModel;
|
||||||
|
|
||||||
const featureName = 'test-strategies-move-project';
|
const featureName = 'test-strategies-move-project';
|
||||||
|
|
||||||
@ -20,12 +25,17 @@ beforeAll(async () => {
|
|||||||
featureStrategiesStore = stores.featureStrategiesStore;
|
featureStrategiesStore = stores.featureStrategiesStore;
|
||||||
featureToggleStore = stores.featureToggleStore;
|
featureToggleStore = stores.featureToggleStore;
|
||||||
projectStore = stores.projectStore;
|
projectStore = stores.projectStore;
|
||||||
|
featureStrategiesReadModel = stores.featureStrategiesReadModel;
|
||||||
await featureToggleStore.create('default', {
|
await featureToggleStore.create('default', {
|
||||||
name: featureName,
|
name: featureName,
|
||||||
createdByUserId: 9999,
|
createdByUserId: 9999,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await featureStrategiesStore.deleteAll();
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
});
|
});
|
||||||
@ -236,4 +246,47 @@ describe('strategy parameters default to sane defaults', () => {
|
|||||||
});
|
});
|
||||||
expect(strategy.parameters.stickiness).toBe(defaultStickiness);
|
expect(strategy.parameters.stickiness).toBe(defaultStickiness);
|
||||||
});
|
});
|
||||||
|
test('Read feature with max number of strategies', async () => {
|
||||||
|
const toggle = await featureToggleStore.create('default', {
|
||||||
|
name: 'featureA',
|
||||||
|
createdByUserId: 9999,
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxStrategiesBefore =
|
||||||
|
await featureStrategiesReadModel.getMaxFeatureStrategies();
|
||||||
|
const maxEnvStrategiesBefore =
|
||||||
|
await featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies();
|
||||||
|
expect(maxStrategiesBefore).toBe(null);
|
||||||
|
expect(maxEnvStrategiesBefore).toBe(null);
|
||||||
|
|
||||||
|
await featureStrategiesStore.createStrategyFeatureEnv({
|
||||||
|
strategyName: 'gradualRollout',
|
||||||
|
projectId: 'default',
|
||||||
|
environment: 'default',
|
||||||
|
featureName: toggle.name,
|
||||||
|
constraints: [],
|
||||||
|
sortOrder: 0,
|
||||||
|
parameters: {},
|
||||||
|
});
|
||||||
|
await featureStrategiesStore.createStrategyFeatureEnv({
|
||||||
|
strategyName: 'gradualRollout',
|
||||||
|
projectId: 'default',
|
||||||
|
environment: 'default',
|
||||||
|
featureName: toggle.name,
|
||||||
|
constraints: [],
|
||||||
|
sortOrder: 0,
|
||||||
|
parameters: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxStrategies =
|
||||||
|
await featureStrategiesReadModel.getMaxFeatureStrategies();
|
||||||
|
const maxEnvStrategies =
|
||||||
|
await featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies();
|
||||||
|
expect(maxStrategies).toEqual({ feature: 'featureA', count: 2 });
|
||||||
|
expect(maxEnvStrategies).toEqual({
|
||||||
|
feature: 'featureA',
|
||||||
|
environment: 'default',
|
||||||
|
count: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
export interface IFeatureStrategiesReadModel {
|
||||||
|
getMaxFeatureEnvironmentStrategies(): Promise<{
|
||||||
|
feature: string;
|
||||||
|
environment: string;
|
||||||
|
count: number;
|
||||||
|
} | null>;
|
||||||
|
getMaxFeatureStrategies(): Promise<{
|
||||||
|
feature: string;
|
||||||
|
count: number;
|
||||||
|
} | null>;
|
||||||
|
}
|
@ -109,6 +109,16 @@ export default class MetricsMonitor {
|
|||||||
help: 'Number of feature flags',
|
help: 'Number of feature flags',
|
||||||
labelNames: ['version'],
|
labelNames: ['version'],
|
||||||
});
|
});
|
||||||
|
const maxFeatureEnvironmentStrategies = createGauge({
|
||||||
|
name: 'max_feature_environment_strategies',
|
||||||
|
help: 'Maximum number of environment strategies in one feature',
|
||||||
|
labelNames: ['feature', 'environment'],
|
||||||
|
});
|
||||||
|
const maxFeatureStrategies = createGauge({
|
||||||
|
name: 'max_feature_strategies',
|
||||||
|
help: 'Maximum number of strategies in one feature',
|
||||||
|
labelNames: ['feature'],
|
||||||
|
});
|
||||||
|
|
||||||
const featureTogglesArchivedTotal = createGauge({
|
const featureTogglesArchivedTotal = createGauge({
|
||||||
name: 'feature_toggles_archived_total',
|
name: 'feature_toggles_archived_total',
|
||||||
@ -274,6 +284,11 @@ export default class MetricsMonitor {
|
|||||||
async function collectStaticCounters() {
|
async function collectStaticCounters() {
|
||||||
try {
|
try {
|
||||||
const stats = await instanceStatsService.getStats();
|
const stats = await instanceStatsService.getStats();
|
||||||
|
const [maxStrategies, maxEnvironmentStrategies] =
|
||||||
|
await Promise.all([
|
||||||
|
stores.featureStrategiesReadModel.getMaxFeatureStrategies(),
|
||||||
|
stores.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies(),
|
||||||
|
]);
|
||||||
|
|
||||||
featureFlagsTotal.reset();
|
featureFlagsTotal.reset();
|
||||||
featureFlagsTotal.labels({ version }).set(stats.featureToggles);
|
featureFlagsTotal.labels({ version }).set(stats.featureToggles);
|
||||||
@ -302,6 +317,22 @@ export default class MetricsMonitor {
|
|||||||
apiTokens.labels({ type }).set(value);
|
apiTokens.labels({ type }).set(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (maxEnvironmentStrategies) {
|
||||||
|
maxFeatureEnvironmentStrategies.reset();
|
||||||
|
maxFeatureEnvironmentStrategies
|
||||||
|
.labels({
|
||||||
|
environment: maxEnvironmentStrategies.environment,
|
||||||
|
feature: maxEnvironmentStrategies.feature,
|
||||||
|
})
|
||||||
|
.set(maxEnvironmentStrategies.count);
|
||||||
|
}
|
||||||
|
if (maxStrategies) {
|
||||||
|
maxFeatureStrategies.reset();
|
||||||
|
maxFeatureStrategies
|
||||||
|
.labels({ feature: maxStrategies.feature })
|
||||||
|
.set(maxStrategies.count);
|
||||||
|
}
|
||||||
|
|
||||||
enabledMetricsBucketsPreviousDay.reset();
|
enabledMetricsBucketsPreviousDay.reset();
|
||||||
enabledMetricsBucketsPreviousDay.set(
|
enabledMetricsBucketsPreviousDay.set(
|
||||||
stats.previousDayMetricsBucketsCount.enabledCount,
|
stats.previousDayMetricsBucketsCount.enabledCount,
|
||||||
@ -426,6 +457,7 @@ export default class MetricsMonitor {
|
|||||||
);
|
);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
await schedulerService.schedule(
|
await schedulerService.schedule(
|
||||||
collectStaticCounters.bind(this),
|
collectStaticCounters.bind(this),
|
||||||
hoursToMilliseconds(2),
|
hoursToMilliseconds(2),
|
||||||
|
@ -43,6 +43,7 @@ import { ISegmentReadModel } from '../features/segment/segment-read-model-type';
|
|||||||
import { IProjectOwnersReadModel } from '../features/project/project-owners-read-model.type';
|
import { IProjectOwnersReadModel } from '../features/project/project-owners-read-model.type';
|
||||||
import { IFeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store-type';
|
import { IFeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store-type';
|
||||||
import { IProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model.type';
|
import { IProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model.type';
|
||||||
|
import { IFeatureStrategiesReadModel } from '../features/feature-toggle/types/feature-strategies-read-model-type';
|
||||||
|
|
||||||
export interface IUnleashStores {
|
export interface IUnleashStores {
|
||||||
accessStore: IAccessStore;
|
accessStore: IAccessStore;
|
||||||
@ -90,6 +91,7 @@ export interface IUnleashStores {
|
|||||||
projectOwnersReadModel: IProjectOwnersReadModel;
|
projectOwnersReadModel: IProjectOwnersReadModel;
|
||||||
projectFlagCreatorsReadModel: IProjectFlagCreatorsReadModel;
|
projectFlagCreatorsReadModel: IProjectFlagCreatorsReadModel;
|
||||||
featureLifecycleStore: IFeatureLifecycleStore;
|
featureLifecycleStore: IFeatureLifecycleStore;
|
||||||
|
featureStrategiesReadModel: IFeatureStrategiesReadModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -136,4 +138,5 @@ export {
|
|||||||
IProjectOwnersReadModel,
|
IProjectOwnersReadModel,
|
||||||
IFeatureLifecycleStore,
|
IFeatureLifecycleStore,
|
||||||
IProjectFlagCreatorsReadModel,
|
IProjectFlagCreatorsReadModel,
|
||||||
|
IFeatureStrategiesReadModel,
|
||||||
};
|
};
|
||||||
|
2
src/test/fixtures/store.ts
vendored
2
src/test/fixtures/store.ts
vendored
@ -46,6 +46,7 @@ import { FakeSegmentReadModel } from '../../lib/features/segment/fake-segment-re
|
|||||||
import { FakeProjectOwnersReadModel } from '../../lib/features/project/fake-project-owners-read-model';
|
import { FakeProjectOwnersReadModel } from '../../lib/features/project/fake-project-owners-read-model';
|
||||||
import { FakeFeatureLifecycleStore } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-store';
|
import { FakeFeatureLifecycleStore } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-store';
|
||||||
import { FakeProjectFlagCreatorsReadModel } from '../../lib/features/project/fake-project-flag-creators-read-model';
|
import { FakeProjectFlagCreatorsReadModel } from '../../lib/features/project/fake-project-flag-creators-read-model';
|
||||||
|
import { FakeFeatureStrategiesReadModel } from '../../lib/features/feature-toggle/fake-feature-strategies-read-model';
|
||||||
|
|
||||||
const db = {
|
const db = {
|
||||||
select: () => ({
|
select: () => ({
|
||||||
@ -101,6 +102,7 @@ const createStores: () => IUnleashStores = () => {
|
|||||||
projectOwnersReadModel: new FakeProjectOwnersReadModel(),
|
projectOwnersReadModel: new FakeProjectOwnersReadModel(),
|
||||||
projectFlagCreatorsReadModel: new FakeProjectFlagCreatorsReadModel(),
|
projectFlagCreatorsReadModel: new FakeProjectFlagCreatorsReadModel(),
|
||||||
featureLifecycleStore: new FakeFeatureLifecycleStore(),
|
featureLifecycleStore: new FakeFeatureLifecycleStore(),
|
||||||
|
featureStrategiesReadModel: new FakeFeatureStrategiesReadModel(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user