mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
refactor + feat: split name query param + add zod validator at /metrics/chart endpoint (#131)
This commit is contained in:
parent
e42b78fe53
commit
6f2c75e362
4 changed files with 69 additions and 44 deletions
8
.changeset/itchy-pandas-live.md
Normal file
8
.changeset/itchy-pandas-live.md
Normal 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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue