mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
feat: validate impact metrics (#10181)
This commit is contained in:
parent
40840c98cf
commit
632f3a04cb
@ -6,7 +6,7 @@ import type {
|
|||||||
IClientMetricsEnv,
|
IClientMetricsEnv,
|
||||||
IClientMetricsStoreV2,
|
IClientMetricsStoreV2,
|
||||||
} from './client-metrics-store-v2-type.js';
|
} from './client-metrics-store-v2-type.js';
|
||||||
import { clientMetricsSchema } from '../shared/schema.js';
|
import { clientMetricsSchema, impactMetricsSchema } from '../shared/schema.js';
|
||||||
import { compareAsc, secondsToMilliseconds } from 'date-fns';
|
import { compareAsc, secondsToMilliseconds } from 'date-fns';
|
||||||
import {
|
import {
|
||||||
CLIENT_METRICS,
|
CLIENT_METRICS,
|
||||||
@ -30,6 +30,11 @@ import {
|
|||||||
MAX_UNKNOWN_FLAGS,
|
MAX_UNKNOWN_FLAGS,
|
||||||
type UnknownFlagsService,
|
type UnknownFlagsService,
|
||||||
} from '../unknown-flags/unknown-flags-service.js';
|
} from '../unknown-flags/unknown-flags-service.js';
|
||||||
|
import {
|
||||||
|
type Metric,
|
||||||
|
MetricsTranslator,
|
||||||
|
} from '../impact/metrics-translator.js';
|
||||||
|
import { impactRegister } from '../impact/impact-register.js';
|
||||||
|
|
||||||
export default class ClientMetricsServiceV2 {
|
export default class ClientMetricsServiceV2 {
|
||||||
private config: IUnleashConfig;
|
private config: IUnleashConfig;
|
||||||
@ -46,6 +51,8 @@ export default class ClientMetricsServiceV2 {
|
|||||||
|
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
|
private impactMetricsTranslator: MetricsTranslator;
|
||||||
|
|
||||||
private cachedFeatureNames: () => Promise<string[]>;
|
private cachedFeatureNames: () => Promise<string[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -69,6 +76,7 @@ export default class ClientMetricsServiceV2 {
|
|||||||
maxAge: secondsToMilliseconds(10),
|
maxAge: secondsToMilliseconds(10),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
this.impactMetricsTranslator = new MetricsTranslator(impactRegister);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearMetrics(hoursAgo: number) {
|
async clearMetrics(hoursAgo: number) {
|
||||||
@ -187,6 +195,11 @@ export default class ClientMetricsServiceV2 {
|
|||||||
this.lastSeenService.updateLastSeen(metrics);
|
this.lastSeenService.updateLastSeen(metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async registerImpactMetrics(impactMetrics: Metric[]) {
|
||||||
|
const value = await impactMetricsSchema.validateAsync(impactMetrics);
|
||||||
|
this.impactMetricsTranslator.translateMetrics(value);
|
||||||
|
}
|
||||||
|
|
||||||
async registerClientMetrics(
|
async registerClientMetrics(
|
||||||
data: ClientMetricsSchema,
|
data: ClientMetricsSchema,
|
||||||
clientIp: string,
|
clientIp: string,
|
||||||
|
@ -11,7 +11,7 @@ import type { Metric } from './metrics-translator.js';
|
|||||||
let app: IUnleashTest;
|
let app: IUnleashTest;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
|
|
||||||
const sendImpactMetrics = async (impactMetrics: Metric[]) =>
|
const sendImpactMetrics = async (impactMetrics: Metric[], status = 202) =>
|
||||||
app.request
|
app.request
|
||||||
.post('/api/client/metrics')
|
.post('/api/client/metrics')
|
||||||
.send({
|
.send({
|
||||||
@ -24,7 +24,7 @@ const sendImpactMetrics = async (impactMetrics: Metric[]) =>
|
|||||||
},
|
},
|
||||||
impactMetrics,
|
impactMetrics,
|
||||||
})
|
})
|
||||||
.expect(202);
|
.expect(status);
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('impact_metrics', getLogger);
|
db = await dbInit('impact_metrics', getLogger);
|
||||||
@ -71,6 +71,25 @@ test('should store impact metrics in memory and be able to retrieve them', async
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
await sendImpactMetrics([]);
|
||||||
|
// missing help
|
||||||
|
await sendImpactMetrics(
|
||||||
|
[
|
||||||
|
// @ts-expect-error
|
||||||
|
{
|
||||||
|
name: 'labeled_counter',
|
||||||
|
type: 'counter',
|
||||||
|
samples: [
|
||||||
|
{
|
||||||
|
labels: { foo: 'bar' },
|
||||||
|
value: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
|
||||||
const response = await app.request
|
const response = await app.request
|
||||||
.get('/internal-backstage/impact/metrics')
|
.get('/internal-backstage/impact/metrics')
|
||||||
.expect('Content-Type', /text/)
|
.expect('Content-Type', /text/)
|
||||||
|
@ -58,7 +58,6 @@ export default class ClientMetricsController extends Controller {
|
|||||||
| 'customMetricsService'
|
| 'customMetricsService'
|
||||||
>,
|
>,
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
metricsTranslator: MetricsTranslator,
|
|
||||||
) {
|
) {
|
||||||
super(config);
|
super(config);
|
||||||
const { getLogger } = config;
|
const { getLogger } = config;
|
||||||
@ -69,7 +68,6 @@ export default class ClientMetricsController extends Controller {
|
|||||||
this.metricsV2 = clientMetricsServiceV2;
|
this.metricsV2 = clientMetricsServiceV2;
|
||||||
this.customMetricsService = customMetricsService;
|
this.customMetricsService = customMetricsService;
|
||||||
this.flagResolver = config.flagResolver;
|
this.flagResolver = config.flagResolver;
|
||||||
this.metricsTranslator = metricsTranslator;
|
|
||||||
|
|
||||||
this.route({
|
this.route({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -171,7 +169,7 @@ export default class ClientMetricsController extends Controller {
|
|||||||
this.flagResolver.isEnabled('impactMetrics') &&
|
this.flagResolver.isEnabled('impactMetrics') &&
|
||||||
impactMetrics
|
impactMetrics
|
||||||
) {
|
) {
|
||||||
this.metricsTranslator.translateMetrics(impactMetrics);
|
await this.metricsV2.registerImpactMetrics(impactMetrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.getHeaderNames().forEach((header) =>
|
res.getHeaderNames().forEach((header) =>
|
||||||
|
@ -85,6 +85,35 @@ export const customMetricsSchema = joi
|
|||||||
metrics: joi.array().items(customMetricSchema).required(),
|
metrics: joi.array().items(customMetricSchema).required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const metricSampleSchema = joi
|
||||||
|
.object()
|
||||||
|
.options({ stripUnknown: true })
|
||||||
|
.keys({
|
||||||
|
value: joi.number().required(),
|
||||||
|
labels: joi
|
||||||
|
.object()
|
||||||
|
.pattern(
|
||||||
|
joi.string(),
|
||||||
|
joi.alternatives().try(joi.string(), joi.number()),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const impactMetricSchema = joi
|
||||||
|
.object()
|
||||||
|
.options({ stripUnknown: true })
|
||||||
|
.keys({
|
||||||
|
name: joi.string().required(),
|
||||||
|
help: joi.string().required(),
|
||||||
|
type: joi.string().required(),
|
||||||
|
samples: joi.array().items(metricSampleSchema).required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const impactMetricsSchema = joi
|
||||||
|
.array()
|
||||||
|
.items(impactMetricSchema)
|
||||||
|
.empty();
|
||||||
|
|
||||||
export const batchMetricsSchema = joi
|
export const batchMetricsSchema = joi
|
||||||
.object()
|
.object()
|
||||||
.options({ stripUnknown: true })
|
.options({ stripUnknown: true })
|
||||||
|
@ -4,20 +4,13 @@ import MetricsController from '../../features/metrics/instance/metrics.js';
|
|||||||
import RegisterController from '../../features/metrics/instance/register.js';
|
import RegisterController from '../../features/metrics/instance/register.js';
|
||||||
import type { IUnleashConfig } from '../../types/index.js';
|
import type { IUnleashConfig } from '../../types/index.js';
|
||||||
import type { IUnleashServices } from '../../services/index.js';
|
import type { IUnleashServices } from '../../services/index.js';
|
||||||
import { impactRegister } from '../../features/metrics/impact/impact-register.js';
|
|
||||||
import { MetricsTranslator } from '../../features/metrics/impact/metrics-translator.js';
|
|
||||||
|
|
||||||
export default class ClientApi extends Controller {
|
export default class ClientApi extends Controller {
|
||||||
constructor(config: IUnleashConfig, services: IUnleashServices) {
|
constructor(config: IUnleashConfig, services: IUnleashServices) {
|
||||||
super(config);
|
super(config);
|
||||||
|
|
||||||
const metricsTranslator = new MetricsTranslator(impactRegister);
|
|
||||||
|
|
||||||
this.use('/features', new FeatureController(services, config).router);
|
this.use('/features', new FeatureController(services, config).router);
|
||||||
this.use(
|
this.use('/metrics', new MetricsController(services, config).router);
|
||||||
'/metrics',
|
|
||||||
new MetricsController(services, config, metricsTranslator).router,
|
|
||||||
);
|
|
||||||
this.use('/register', new RegisterController(services, config).router);
|
this.use('/register', new RegisterController(services, config).router);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user