mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
feat: start exposing environment metrics from feature endpoint (#6986)
We want to start showing same donut that we do show in project page. This is setting it up for UI.
This commit is contained in:
parent
07871e73e5
commit
77d5156eba
@ -373,11 +373,37 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
userId,
|
userId,
|
||||||
}: ILoadFeatureToggleWithEnvsParams): Promise<FeatureToggleWithEnvironment> {
|
}: ILoadFeatureToggleWithEnvsParams): Promise<FeatureToggleWithEnvironment> {
|
||||||
const stopTimer = this.timer('getFeatureAdmin');
|
const stopTimer = this.timer('getFeatureAdmin');
|
||||||
let query = this.db('features_view')
|
const query = this.db.with('metrics', (queryBuilder) => {
|
||||||
|
queryBuilder
|
||||||
|
.sum('yes as yes')
|
||||||
|
.sum('no as no')
|
||||||
|
.select(['client_metrics_env.environment'])
|
||||||
|
.from('client_metrics_env')
|
||||||
|
.where(
|
||||||
|
'client_metrics_env.timestamp',
|
||||||
|
'>=',
|
||||||
|
this.db.raw("NOW() - INTERVAL '1 hour'"),
|
||||||
|
)
|
||||||
|
.andWhere('client_metrics_env.feature_name', featureName)
|
||||||
|
.groupBy(['client_metrics_env.environment']);
|
||||||
|
});
|
||||||
|
|
||||||
|
query
|
||||||
|
.from('features_view')
|
||||||
.where('name', featureName)
|
.where('name', featureName)
|
||||||
.modify(FeatureToggleStore.filterByArchived, archived);
|
.modify(FeatureToggleStore.filterByArchived, archived);
|
||||||
|
|
||||||
let selectColumns = ['features_view.*'] as (string | Raw<any>)[];
|
let selectColumns = ['features_view.*', 'yes', 'no'] as (
|
||||||
|
| string
|
||||||
|
| Raw<any>
|
||||||
|
)[];
|
||||||
|
|
||||||
|
// add metrics
|
||||||
|
query.leftJoin(
|
||||||
|
'metrics',
|
||||||
|
'metrics.environment',
|
||||||
|
'features_view.environment',
|
||||||
|
);
|
||||||
|
|
||||||
query.leftJoin('last_seen_at_metrics', function () {
|
query.leftJoin('last_seen_at_metrics', function () {
|
||||||
this.on(
|
this.on(
|
||||||
@ -390,13 +416,14 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
'features_view.name',
|
'features_view.name',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Override feature view for now
|
// Override feature view for now
|
||||||
selectColumns.push(
|
selectColumns.push(
|
||||||
'last_seen_at_metrics.last_seen_at as env_last_seen_at',
|
'last_seen_at_metrics.last_seen_at as env_last_seen_at',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
query = query.leftJoin(`favorite_features`, function () {
|
query.leftJoin(`favorite_features`, function () {
|
||||||
this.on(
|
this.on(
|
||||||
'favorite_features.feature',
|
'favorite_features.feature',
|
||||||
'features_view.name',
|
'features_view.name',
|
||||||
@ -409,7 +436,6 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = await query.select(selectColumns);
|
const rows = await query.select(selectColumns);
|
||||||
stopTimer();
|
stopTimer();
|
||||||
|
|
||||||
@ -463,6 +489,8 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
acc.variants = Array.from(currentVariants.values());
|
acc.variants = Array.from(currentVariants.values());
|
||||||
|
|
||||||
env.enabled = r.enabled;
|
env.enabled = r.enabled;
|
||||||
|
env.yes = Number(r.yes) || 0;
|
||||||
|
env.no = Number(r.no) || 0;
|
||||||
env.type = r.environment_type;
|
env.type = r.environment_type;
|
||||||
env.sortOrder = r.environment_sort_order;
|
env.sortOrder = r.environment_sort_order;
|
||||||
if (!env.strategies) {
|
if (!env.strategies) {
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
FEATURE_STRATEGY_REMOVE,
|
FEATURE_STRATEGY_REMOVE,
|
||||||
} from '../../../types/events';
|
} from '../../../types/events';
|
||||||
import ApiUser from '../../../types/api-user';
|
import ApiUser from '../../../types/api-user';
|
||||||
import { ApiTokenType } from '../../../types/models/api-token';
|
import { ApiTokenType, type IApiToken } from '../../../types/models/api-token';
|
||||||
import IncompatibleProjectError from '../../../error/incompatible-project-error';
|
import IncompatibleProjectError from '../../../error/incompatible-project-error';
|
||||||
import {
|
import {
|
||||||
type IStrategyConfig,
|
type IStrategyConfig,
|
||||||
@ -36,6 +36,7 @@ import { ForbiddenError } from '../../../error';
|
|||||||
|
|
||||||
let app: IUnleashTest;
|
let app: IUnleashTest;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
|
let defaultToken: IApiToken;
|
||||||
const sortOrderFirst = 0;
|
const sortOrderFirst = 0;
|
||||||
const sortOrderSecond = 10;
|
const sortOrderSecond = 10;
|
||||||
const TESTUSERID = 3333;
|
const TESTUSERID = 3333;
|
||||||
@ -103,6 +104,14 @@ beforeAll(async () => {
|
|||||||
},
|
},
|
||||||
db.rawDatabase,
|
db.rawDatabase,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
defaultToken =
|
||||||
|
await app.services.apiTokenService.createApiTokenWithProjects({
|
||||||
|
type: ApiTokenType.CLIENT,
|
||||||
|
projects: ['default'],
|
||||||
|
environment: 'default',
|
||||||
|
tokenName: 'tester',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@ -3684,3 +3693,43 @@ test('should return correct data structure for /api/admin/features', async () =>
|
|||||||
strategies: [],
|
strategies: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can get evaluation metrics', async () => {
|
||||||
|
await app.createFeature('metric-feature');
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
await app.request
|
||||||
|
.post('/api/client/metrics')
|
||||||
|
.set('Authorization', defaultToken.secret)
|
||||||
|
.send({
|
||||||
|
appName: 'appName',
|
||||||
|
instanceId: 'instanceId',
|
||||||
|
bucket: {
|
||||||
|
start: now,
|
||||||
|
stop: now,
|
||||||
|
toggles: {
|
||||||
|
'metric-feature': {
|
||||||
|
yes: 123,
|
||||||
|
no: 321,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(202);
|
||||||
|
|
||||||
|
await app.services.clientMetricsServiceV2.bulkAdd();
|
||||||
|
|
||||||
|
const { body } = await app.request.get(
|
||||||
|
'/api/admin/projects/default/features/metric-feature',
|
||||||
|
);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
name: 'metric-feature',
|
||||||
|
environments: [
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
yes: 123,
|
||||||
|
no: 321,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -214,6 +214,8 @@ export interface IEnvironmentOverview extends IEnvironmentBase {
|
|||||||
variantCount: number;
|
variantCount: number;
|
||||||
hasStrategies?: boolean;
|
hasStrategies?: boolean;
|
||||||
hasEnabledStrategies?: boolean;
|
hasEnabledStrategies?: boolean;
|
||||||
|
yes?: number;
|
||||||
|
no?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFeatureOverview {
|
export interface IFeatureOverview {
|
||||||
|
Loading…
Reference in New Issue
Block a user