Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/plenty-pumas-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@hyperdx/common-utils": patch
"@hyperdx/api": patch
"@hyperdx/app": patch
---

feat: queryChartConfig method + events chart ratio
15 changes: 6 additions & 9 deletions packages/api/src/tasks/checkAlerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ export const processAlert = async (now: Date, alert: EnhancedAlert) => {
select: firstTile.config.select,
timestampValueExpression: source.timestampValueExpression,
where: firstTile.config.where,
seriesReturnType: firstTile.config.seriesReturnType,
};
}
}
Expand Down Expand Up @@ -821,14 +822,10 @@ export const processAlert = async (now: Date, alert: EnhancedAlert) => {
password: connection.password,
});
const metadata = getMetadata(clickhouseClient);
const query = await renderChartConfig(chartConfig, metadata);
const checksData = await clickhouseClient
.query<'JSON'>({
query: query.sql,
query_params: query.params,
format: 'JSON',
})
.then(res => res.json<Record<string, string>>());
const checksData = await clickhouseClient.queryChartConfig({
config: chartConfig,
metadata,
});

logger.info({
message: `Received alert metric [${alert.source} source]`,
Expand All @@ -846,7 +843,7 @@ export const processAlert = async (now: Date, alert: EnhancedAlert) => {
state: alertState,
}).save();

if (checksData?.rows && checksData?.rows > 0) {
if (checksData?.data && checksData?.data.length > 0) {
// attach JS type
const meta =
checksData.meta?.map(m => ({
Expand Down
135 changes: 0 additions & 135 deletions packages/app/src/hooks/__tests__/useChartConfig.test.tsx

This file was deleted.

190 changes: 7 additions & 183 deletions packages/app/src/hooks/useChartConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,107 +19,6 @@ import { IS_MTVIEWS_ENABLED } from '@/config';
import { buildMTViewSelectQuery } from '@/hdxMTViews';
import { getMetadata } from '@/metadata';

export const isMetric = (config: ChartConfigWithOptDateRange) =>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move all these to common-utils

config.metricTables != null;

// TODO: apply this to all chart configs
export const setChartSelectsAlias = (config: ChartConfigWithOptDateRange) => {
if (Array.isArray(config.select) && isMetric(config)) {
return {
...config,
select: config.select.map(s => ({
...s,
alias: `${s.aggFn}(${s.metricName})`,
})),
};
}
return config;
};

export const splitChartConfigs = (config: ChartConfigWithOptDateRange) => {
// only split metric queries for now
if (isMetric(config) && Array.isArray(config.select)) {
const _configs = [];
// split the query into multiple queries
for (const select of config.select) {
_configs.push({
...config,
select: [select],
});
}
return _configs;
}
return [config];
};

const castToNumber = (value: string | number) => {
if (typeof value === 'string') {
if (value.trim() === '') {
return NaN;
}
return Number(value);
}
return value;
};

export const computeRatio = (
numeratorInput: string | number,
denominatorInput: string | number,
) => {
const numerator = castToNumber(numeratorInput);
const denominator = castToNumber(denominatorInput);

if (isNaN(numerator) || isNaN(denominator) || denominator === 0) {
return NaN;
}

return numerator / denominator;
};

export const computeResultSetRatio = (resultSet: ResponseJSON<any>) => {
const _meta = resultSet.meta;
const _data = resultSet.data;
const timestampColumn = inferTimestampColumn(_meta ?? []);
const _restColumns = _meta?.filter(m => m.name !== timestampColumn?.name);
const firstColumn = _restColumns?.[0];
const secondColumn = _restColumns?.[1];
if (!firstColumn || !secondColumn) {
throw new Error(
`Unable to compute ratio - meta information: ${JSON.stringify(_meta)}.`,
);
}
const ratioColumnName = `${firstColumn.name}/${secondColumn.name}`;
const result = {
...resultSet,
data: _data.map(row => ({
[ratioColumnName]: computeRatio(
row[firstColumn.name],
row[secondColumn.name],
),
...(timestampColumn
? {
[timestampColumn.name]: row[timestampColumn.name],
}
: {}),
})),
meta: [
{
name: ratioColumnName,
type: 'Float64',
},
...(timestampColumn
? [
{
name: timestampColumn.name,
type: timestampColumn.type,
},
]
: []),
],
};
return result;
};

interface AdditionalUseQueriedChartConfigOptions {
onError?: (error: Error | ClickHouseQueryError) => void;
}
Expand All @@ -134,8 +33,6 @@ export function useQueriedChartConfig(
const query = useQuery<ResponseJSON<any>, ClickHouseQueryError | Error>({
queryKey: [config],
queryFn: async ({ signal }) => {
config = setChartSelectsAlias(config);

let query = null;
if (IS_MTVIEWS_ENABLED) {
const { dataTableDDL, mtViewDDL, renderMTViewConfig } =
Expand All @@ -148,86 +45,13 @@ export function useQueriedChartConfig(
query = await renderMTViewConfig();
}

// TODO: move multi-series logics to common-utils so alerting can use it
const queries: ChSql[] = await Promise.all(
splitChartConfigs(config).map(c => renderChartConfig(c, getMetadata())),
);

const isTimeSeries = config.displayType === 'line';

const resultSets = await Promise.all(
queries.map(async query => {
const resp = await clickhouseClient.query<'JSON'>({
query: query.sql,
query_params: query.params,
format: 'JSON',
abort_signal: signal,
connectionId: config.connection,
});
return resp.json<any>();
}),
);

if (resultSets.length === 1) {
const isRatio =
config.seriesReturnType === 'ratio' &&
resultSets[0].meta?.length === 3;
return isRatio ? computeResultSetRatio(resultSets[0]) : resultSets[0];
}
// metrics -> join resultSets
else if (resultSets.length > 1) {
const metaSet = new Map<string, { name: string; type: string }>();
const tsBucketMap = new Map<string, Record<string, string | number>>();
for (const resultSet of resultSets) {
// set up the meta data
if (Array.isArray(resultSet.meta)) {
for (const meta of resultSet.meta) {
const key = meta.name;
if (!metaSet.has(key)) {
metaSet.set(key, meta);
}
}
}

const timestampColumn = inferTimestampColumn(resultSet.meta ?? []);
const numericColumn = inferNumericColumn(resultSet.meta ?? []);
const numericColumnName = numericColumn?.[0]?.name;
for (const row of resultSet.data) {
const _rowWithoutValue = numericColumnName
? Object.fromEntries(
Object.entries(row).filter(
([key]) => key !== numericColumnName,
),
)
: { ...row };
const ts =
timestampColumn != null
? row[timestampColumn.name]
: isTimeSeries
? objectHash(_rowWithoutValue)
: '__FIXED_TIMESTAMP__';
if (tsBucketMap.has(ts)) {
const existingRow = tsBucketMap.get(ts);
tsBucketMap.set(ts, {
...existingRow,
...row,
});
} else {
tsBucketMap.set(ts, row);
}
}
}

const isRatio =
config.seriesReturnType === 'ratio' && resultSets.length === 2;

const _resultSet = {
meta: Array.from(metaSet.values()),
data: Array.from(tsBucketMap.values()),
};
return isRatio ? computeResultSetRatio(_resultSet) : _resultSet;
}
throw new Error('No result sets');
return clickhouseClient.queryChartConfig({
config,
metadata: getMetadata(),
opts: {
abort_signal: signal,
},
});
},
retry: 1,
refetchOnWindowFocus: false,
Expand Down
Loading