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
5 changes: 5 additions & 0 deletions .changeset/metric-formula-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperdx/common-utils': minor
---

Render metric formulas (`formulas` on builder chart configs) in the composed multi-series metric query. Letter-ref expressions like `A / (A + B + C) * 100` compile into the final SELECT projection over the pivoted per-series columns, with ratio-consistent missing-data semantics: a missing operand counts as 0 while a zero or missing division denominator yields NULL (a rendered gap). `showOperandSeries: false` emits only the formula column(s). Works for grouped and ungrouped line, table, and number charts, and single-series charts with a formula now route through the composed query path.
5 changes: 5 additions & 0 deletions .changeset/multi-series-convert-to-sql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperdx/common-utils': minor
---

"Convert to SQL" now supports multi-series, ratio, and formula metric charts. The composed UNION ALL + pivot query is emitted as a macro-based raw-SQL template with a `$__sourceTable(<metricType>)` macro per series branch, instead of returning a "cannot be auto-converted" error. Non-time-series metric charts remain unsupported, matching the existing single-series restriction.
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ ORDER BY
$__timeInterval(timestamp) AS \`__hdx_time_bucket\`"
`;

exports[`renderBuilderConfigAsSqlTemplate metric charts (single-series only) generates a macro-based template for a single-series gauge metric line chart 1`] = `
exports[`renderBuilderConfigAsSqlTemplate metric charts generates a macro-based template for a single-series gauge metric line chart 1`] = `
"WITH
Source AS (
SELECT
Expand Down Expand Up @@ -313,7 +313,7 @@ SETTINGS
short_circuit_function_evaluation = 'force_enable'"
`;

exports[`renderBuilderConfigAsSqlTemplate metric charts (single-series only) generates a macro-based template for a single-series histogram metric line chart 1`] = `
exports[`renderBuilderConfigAsSqlTemplate metric charts generates a macro-based template for a single-series histogram metric line chart 1`] = `
"WITH
source AS (
SELECT
Expand Down Expand Up @@ -435,7 +435,7 @@ SETTINGS
short_circuit_function_evaluation = 'force_enable'"
`;

exports[`renderBuilderConfigAsSqlTemplate metric charts (single-series only) generates a macro-based template for a single-series sum metric line chart 1`] = `
exports[`renderBuilderConfigAsSqlTemplate metric charts generates a macro-based template for a single-series sum metric line chart 1`] = `
"WITH
Source AS (
SELECT
Expand Down Expand Up @@ -552,6 +552,307 @@ ORDER BY
$__timeInterval(\`__hdx_time_bucket2\`) AS \`__hdx_time_bucket\`"
`;

exports[`renderBuilderConfigAsSqlTemplate metric charts multi-series and formula metric charts compiles formulas into the composed template projection 1`] = `
"SELECT
anyOrNullIf(\`__hdx_value\`, \`__hdx_series_idx\` = 0) AS "avg(my.metric)",
anyOrNullIf(\`__hdx_value\`, \`__hdx_series_idx\` = 1) AS "avg(my.other.metric)",
(
(
coalesce(
anyOrNullIf(\`__hdx_value\`, \`__hdx_series_idx\` = 0),
0
) / nullif(
(
coalesce(
anyOrNullIf(\`__hdx_value\`, \`__hdx_series_idx\` = 0),
0
) + coalesce(
anyOrNullIf(\`__hdx_value\`, \`__hdx_series_idx\` = 1),
0
)
),
0
)
) * 100
) AS "pct",
* EXCEPT (\`__hdx_value\`, \`__hdx_series_idx\`)
FROM
(
SELECT
* REPLACE(toFloat64(\`__hdx_value\`) AS \`__hdx_value\`),
0 AS \`__hdx_series_idx\`
FROM
(
WITH
Source AS (
SELECT
*,
cityHash64(ScopeAttributes, ResourceAttributes, Attributes) AS AttributesHash
FROM
$__sourceTable(gauge)
WHERE
(
TimeUnix >= $__fromTime_ms
AND TimeUnix <= $__toTime_ms
)
AND ((MetricName = 'my.metric'))
AND $__filters
),
Bucketed AS (
SELECT
$__timeInterval(TimeUnix) AS \`__hdx_time_bucket2\`,
AttributesHash,
last_value(Value) AS LastValue,
any(ScopeAttributes) AS ScopeAttributes,
any(ResourceAttributes) AS ResourceAttributes,
any(Attributes) AS Attributes,
any(ResourceSchemaUrl) AS ResourceSchemaUrl,
any(ScopeName) AS ScopeName,
any(ScopeVersion) AS ScopeVersion,
any(ScopeDroppedAttrCount) AS ScopeDroppedAttrCount,
any(ScopeSchemaUrl) AS ScopeSchemaUrl,
any(ServiceName) AS ServiceName,
any(MetricDescription) AS MetricDescription,
any(MetricUnit) AS MetricUnit,
any(StartTimeUnix) AS StartTimeUnix,
any(Flags) AS Flags
FROM
Source
GROUP BY
AttributesHash,
__hdx_time_bucket2
ORDER BY
AttributesHash,
__hdx_time_bucket2
)
SELECT
avg(toFloat64OrDefault(toString(LastValue))) AS "__hdx_value",
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
FROM
\`Bucketed\`
WHERE
(
__hdx_time_bucket2 >= $__fromTime_ms
AND __hdx_time_bucket2 <= $__toTime_ms
)
GROUP BY
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
ORDER BY
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
)
UNION ALL
SELECT
* REPLACE(toFloat64(\`__hdx_value\`) AS \`__hdx_value\`),
1 AS \`__hdx_series_idx\`
FROM
(
WITH
Source AS (
SELECT
*,
cityHash64(ScopeAttributes, ResourceAttributes, Attributes) AS AttributesHash
FROM
$__sourceTable(gauge)
WHERE
(
TimeUnix >= $__fromTime_ms
AND TimeUnix <= $__toTime_ms
)
AND ((MetricName = 'my.other.metric'))
AND $__filters
),
Bucketed AS (
SELECT
$__timeInterval(TimeUnix) AS \`__hdx_time_bucket2\`,
AttributesHash,
last_value(Value) AS LastValue,
any(ScopeAttributes) AS ScopeAttributes,
any(ResourceAttributes) AS ResourceAttributes,
any(Attributes) AS Attributes,
any(ResourceSchemaUrl) AS ResourceSchemaUrl,
any(ScopeName) AS ScopeName,
any(ScopeVersion) AS ScopeVersion,
any(ScopeDroppedAttrCount) AS ScopeDroppedAttrCount,
any(ScopeSchemaUrl) AS ScopeSchemaUrl,
any(ServiceName) AS ServiceName,
any(MetricDescription) AS MetricDescription,
any(MetricUnit) AS MetricUnit,
any(StartTimeUnix) AS StartTimeUnix,
any(Flags) AS Flags
FROM
Source
GROUP BY
AttributesHash,
__hdx_time_bucket2
ORDER BY
AttributesHash,
__hdx_time_bucket2
)
SELECT
avg(toFloat64OrDefault(toString(LastValue))) AS "__hdx_value",
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
FROM
\`Bucketed\`
WHERE
(
__hdx_time_bucket2 >= $__fromTime_ms
AND __hdx_time_bucket2 <= $__toTime_ms
)
GROUP BY
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
ORDER BY
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
)
)
GROUP BY
ALL
ORDER BY
\`__hdx_time_bucket\`
SETTINGS
short_circuit_function_evaluation = 'force_enable'"
`;

exports[`renderBuilderConfigAsSqlTemplate metric charts multi-series and formula metric charts generates a composed macro-based template for a two-gauge line chart 1`] = `
"SELECT
anyOrNullIf(\`__hdx_value\`, \`__hdx_series_idx\` = 0) AS "avg(my.metric)",
anyOrNullIf(\`__hdx_value\`, \`__hdx_series_idx\` = 1) AS "avg(my.other.metric)",
* EXCEPT (\`__hdx_value\`, \`__hdx_series_idx\`)
FROM
(
SELECT
* REPLACE(toFloat64(\`__hdx_value\`) AS \`__hdx_value\`),
0 AS \`__hdx_series_idx\`
FROM
(
WITH
Source AS (
SELECT
*,
cityHash64(ScopeAttributes, ResourceAttributes, Attributes) AS AttributesHash
FROM
$__sourceTable(gauge)
WHERE
(
TimeUnix >= $__fromTime_ms
AND TimeUnix <= $__toTime_ms
)
AND ((MetricName = 'my.metric'))
AND $__filters
),
Bucketed AS (
SELECT
$__timeInterval(TimeUnix) AS \`__hdx_time_bucket2\`,
AttributesHash,
last_value(Value) AS LastValue,
any(ScopeAttributes) AS ScopeAttributes,
any(ResourceAttributes) AS ResourceAttributes,
any(Attributes) AS Attributes,
any(ResourceSchemaUrl) AS ResourceSchemaUrl,
any(ScopeName) AS ScopeName,
any(ScopeVersion) AS ScopeVersion,
any(ScopeDroppedAttrCount) AS ScopeDroppedAttrCount,
any(ScopeSchemaUrl) AS ScopeSchemaUrl,
any(ServiceName) AS ServiceName,
any(MetricDescription) AS MetricDescription,
any(MetricUnit) AS MetricUnit,
any(StartTimeUnix) AS StartTimeUnix,
any(Flags) AS Flags
FROM
Source
GROUP BY
AttributesHash,
__hdx_time_bucket2
ORDER BY
AttributesHash,
__hdx_time_bucket2
)
SELECT
avg(toFloat64OrDefault(toString(LastValue))) AS "__hdx_value",
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
FROM
\`Bucketed\`
WHERE
(
__hdx_time_bucket2 >= $__fromTime_ms
AND __hdx_time_bucket2 <= $__toTime_ms
)
GROUP BY
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
ORDER BY
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
)
UNION ALL
SELECT
* REPLACE(toFloat64(\`__hdx_value\`) AS \`__hdx_value\`),
1 AS \`__hdx_series_idx\`
FROM
(
WITH
Source AS (
SELECT
*,
cityHash64(ScopeAttributes, ResourceAttributes, Attributes) AS AttributesHash
FROM
$__sourceTable(gauge)
WHERE
(
TimeUnix >= $__fromTime_ms
AND TimeUnix <= $__toTime_ms
)
AND ((MetricName = 'my.other.metric'))
AND $__filters
),
Bucketed AS (
SELECT
$__timeInterval(TimeUnix) AS \`__hdx_time_bucket2\`,
AttributesHash,
last_value(Value) AS LastValue,
any(ScopeAttributes) AS ScopeAttributes,
any(ResourceAttributes) AS ResourceAttributes,
any(Attributes) AS Attributes,
any(ResourceSchemaUrl) AS ResourceSchemaUrl,
any(ScopeName) AS ScopeName,
any(ScopeVersion) AS ScopeVersion,
any(ScopeDroppedAttrCount) AS ScopeDroppedAttrCount,
any(ScopeSchemaUrl) AS ScopeSchemaUrl,
any(ServiceName) AS ServiceName,
any(MetricDescription) AS MetricDescription,
any(MetricUnit) AS MetricUnit,
any(StartTimeUnix) AS StartTimeUnix,
any(Flags) AS Flags
FROM
Source
GROUP BY
AttributesHash,
__hdx_time_bucket2
ORDER BY
AttributesHash,
__hdx_time_bucket2
)
SELECT
avg(toFloat64OrDefault(toString(LastValue))) AS "__hdx_value",
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
FROM
\`Bucketed\`
WHERE
(
__hdx_time_bucket2 >= $__fromTime_ms
AND __hdx_time_bucket2 <= $__toTime_ms
)
GROUP BY
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
ORDER BY
$__timeInterval(__hdx_time_bucket2) AS \`__hdx_time_bucket\`
)
)
GROUP BY
ALL
ORDER BY
\`__hdx_time_bucket\`
SETTINGS
short_circuit_function_evaluation = 'force_enable'"
`;

exports[`renderBuilderConfigAsSqlTemplate series-level WHERE (aggCondition → -If combinators) keeps -If per series but does not push to WHERE when only some series are filtered 1`] = `
"SELECT
countIf((ServiceName ILIKE '%api%')),
Expand Down
Loading
Loading