refactor + feat: split name query param + add zod validator at /metrics/chart endpoint (#131)

This commit is contained in:
Warren 2023-11-28 16:56:25 -08:00 committed by GitHub
parent e42b78fe53
commit 6f2c75e362
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 44 deletions

View file

@ -0,0 +1,8 @@
---
'@hyperdx/api': minor
'@hyperdx/app': minor
---
refactor: split metrics chart endpoint `name` query param into `type` and `name`
params (changing an internal API) feat: add validation for metrics chart
endpoint using zod

View file

@ -43,6 +43,12 @@ const tracer = opentelemetry.trace.getTracer(__filename);
export type SortOrder = 'asc' | 'desc' | null;
export enum MetricsDataType {
Gauge = 'Gauge',
Histogram = 'Histogram',
Sum = 'Sum',
}
export enum AggFn {
Avg = 'avg',
AvgRate = 'avg_rate',
@ -695,7 +701,7 @@ export const getMetricsChart = async ({
teamId,
}: {
aggFn: AggFn;
dataType: string;
dataType: MetricsDataType;
endTime: number; // unix in ms,
granularity: Granularity;
groupBy?: string;
@ -727,7 +733,7 @@ export const getMetricsChart = async ({
const isRate = isRateAggFn(aggFn);
if (dataType === 'Gauge' || dataType === 'Sum') {
if (dataType === MetricsDataType.Gauge || dataType === MetricsDataType.Sum) {
selectClause.push(
aggFn === AggFn.Count
? 'COUNT(value) as data'

View file

@ -1,6 +1,7 @@
import express from 'express';
import opentelemetry, { SpanStatusCode } from '@opentelemetry/api';
import { isNumber, parseInt } from 'lodash';
import { validateRequest } from 'zod-express-middleware';
import { z } from 'zod';
import * as clickhouse from '@/clickhouse';
import { isUserAuthenticated } from '@/middleware/auth';
@ -22,46 +23,51 @@ router.get('/tags', isUserAuthenticated, async (req, res, next) => {
}
});
router.post('/chart', isUserAuthenticated, async (req, res, next) => {
try {
const teamId = req.user?.team;
const { aggFn, endTime, granularity, groupBy, name, q, startTime } =
req.body;
router.post(
'/chart',
validateRequest({
body: z.object({
aggFn: z.nativeEnum(clickhouse.AggFn),
endTime: z.number().int().min(0),
granularity: z.nativeEnum(clickhouse.Granularity),
groupBy: z.string().optional(),
name: z.string().min(1),
type: z.nativeEnum(clickhouse.MetricsDataType),
q: z.string(),
startTime: z.number().int().min(0),
}),
}),
isUserAuthenticated,
async (req, res, next) => {
try {
const teamId = req.user?.team;
const { aggFn, endTime, granularity, groupBy, name, q, startTime, type } =
req.body;
if (teamId == null) {
return res.sendStatus(403);
}
const startTimeNum = parseInt(startTime as string);
const endTimeNum = parseInt(endTime as string);
if (!isNumber(startTimeNum) || !isNumber(endTimeNum) || !name) {
return res.sendStatus(400);
}
if (teamId == null) {
return res.sendStatus(403);
}
// FIXME: separate name + dataType
const [metricName, metricDataType] = (name as string).split(' - ');
if (metricName == null || metricDataType == null) {
return res.sendStatus(400);
res.json(
await clickhouse.getMetricsChart({
aggFn,
dataType: type,
endTime,
granularity,
groupBy,
name,
q,
startTime,
teamId: teamId.toString(),
}),
);
} catch (e) {
const span = opentelemetry.trace.getActiveSpan();
span?.recordException(e as Error);
span?.setStatus({ code: SpanStatusCode.ERROR });
next(e);
}
res.json(
await clickhouse.getMetricsChart({
aggFn: aggFn as clickhouse.AggFn,
dataType: metricDataType,
endTime: endTimeNum,
granularity,
groupBy: groupBy as string,
name: metricName,
q: q as string,
startTime: startTimeNum,
teamId: teamId.toString(),
}),
);
} catch (e) {
const span = opentelemetry.trace.getActiveSpan();
span?.recordException(e as Error);
span?.setStatus({ code: SpanStatusCode.ERROR });
next(e);
}
});
},
);
export default router;

View file

@ -109,7 +109,7 @@ const api = {
aggFn: string;
endDate: Date;
granularity: string | undefined;
name: string;
name: string; // WARN: name consists of metric name and type
q: string;
startDate: Date;
groupBy: string;
@ -118,6 +118,10 @@ const api = {
) {
const startTime = startDate.getTime();
const endTime = endDate.getTime();
// FIXME: pass metric name and type separately
const [metricName, metricDataType] = name.split(' - ');
return useQuery<any, Error>({
refetchOnWindowFocus: false,
queryKey: [
@ -137,10 +141,11 @@ const api = {
aggFn,
endTime,
granularity,
name,
groupBy,
name: metricName,
q,
startTime,
groupBy,
type: metricDataType,
},
}).json(),
...options,