From 685da2137cd0e0a1b4bc6b073e0dc3c3bb0d4f3b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 17:50:40 +0000 Subject: [PATCH 1/2] chore(release): bump HyperDX app/package versions --- .changeset/alert-sample-rows-per-window.md | 7 - .changeset/alert-tags-suggestions.md | 5 - .changeset/backfill-alert-name-tags.md | 5 - .changeset/chilled-pillows-joke.md | 5 - .../disabled-sources-no-load-queries.md | 5 - .changeset/flat-foxes-report.md | 5 - .changeset/great-hoops-shave.md | 41 ---- .changeset/heavy-toys-cheat.md | 7 - .changeset/iac-export-tile-alerts.md | 18 -- .changeset/incidentio-webhook-body.md | 9 - .changeset/inline-alert-ui.md | 5 - .changeset/lazy-pugs-smoke.md | 15 -- .changeset/metric-select-value-expression.md | 5 - .../multiline-search-bar-always-visible.md | 13 -- .changeset/notification-render-breakdown.md | 9 - .changeset/olive-cameras-repeat.md | 15 -- .changeset/onboarding-checklist-tasks.md | 7 - .../onboarding-probe-escape-source-names.md | 5 - .changeset/orange-pens-accept.md | 7 - .changeset/paginate-alerts-list.md | 6 - .changeset/perfect-jars-tan.md | 5 - .changeset/promql-chart-editor-refactor.md | 6 - .changeset/promql-via-clickhouse-http-api.md | 7 - .changeset/quiet-pugs-clap.md | 11 - .changeset/quote-row-where-identifiers.md | 6 - .changeset/required-dashboard-filters.md | 7 - .changeset/search-row-multi-select.md | 5 - .../service-map-absolute-error-thresholds.md | 18 -- .changeset/silent-pandas-search.md | 6 - .changeset/spanmetrics-connector.md | 13 -- .../stable-metadata-materialized-views-id.md | 5 - .changeset/stream-metric-names-from-index.md | 14 -- .changeset/swift-pans-deny.md | 7 - .changeset/tile-editor-dashboard-filters.md | 6 - .changeset/token-encryption-service.md | 5 - ...webhook-template-range-and-inline-query.md | 29 --- .../whats-new-sparkle-release-version.md | 5 - .env | 4 +- packages/api/CHANGELOG.md | 89 ++++++++ packages/api/package.json | 4 +- packages/app/CHANGELOG.md | 195 ++++++++++++++++++ packages/app/package.json | 4 +- packages/cli/package.json | 2 +- packages/common-utils/CHANGELOG.md | 81 ++++++++ packages/common-utils/package.json | 2 +- packages/otel-collector/CHANGELOG.md | 15 ++ packages/otel-collector/package.json | 2 +- yarn.lock | 8 +- 48 files changed, 393 insertions(+), 362 deletions(-) delete mode 100644 .changeset/alert-sample-rows-per-window.md delete mode 100644 .changeset/alert-tags-suggestions.md delete mode 100644 .changeset/backfill-alert-name-tags.md delete mode 100644 .changeset/chilled-pillows-joke.md delete mode 100644 .changeset/disabled-sources-no-load-queries.md delete mode 100644 .changeset/flat-foxes-report.md delete mode 100644 .changeset/great-hoops-shave.md delete mode 100644 .changeset/heavy-toys-cheat.md delete mode 100644 .changeset/iac-export-tile-alerts.md delete mode 100644 .changeset/incidentio-webhook-body.md delete mode 100644 .changeset/inline-alert-ui.md delete mode 100644 .changeset/lazy-pugs-smoke.md delete mode 100644 .changeset/metric-select-value-expression.md delete mode 100644 .changeset/multiline-search-bar-always-visible.md delete mode 100644 .changeset/notification-render-breakdown.md delete mode 100644 .changeset/olive-cameras-repeat.md delete mode 100644 .changeset/onboarding-checklist-tasks.md delete mode 100644 .changeset/onboarding-probe-escape-source-names.md delete mode 100644 .changeset/orange-pens-accept.md delete mode 100644 .changeset/paginate-alerts-list.md delete mode 100644 .changeset/perfect-jars-tan.md delete mode 100644 .changeset/promql-chart-editor-refactor.md delete mode 100644 .changeset/promql-via-clickhouse-http-api.md delete mode 100644 .changeset/quiet-pugs-clap.md delete mode 100644 .changeset/quote-row-where-identifiers.md delete mode 100644 .changeset/required-dashboard-filters.md delete mode 100644 .changeset/search-row-multi-select.md delete mode 100644 .changeset/service-map-absolute-error-thresholds.md delete mode 100644 .changeset/silent-pandas-search.md delete mode 100644 .changeset/spanmetrics-connector.md delete mode 100644 .changeset/stable-metadata-materialized-views-id.md delete mode 100644 .changeset/stream-metric-names-from-index.md delete mode 100644 .changeset/swift-pans-deny.md delete mode 100644 .changeset/tile-editor-dashboard-filters.md delete mode 100644 .changeset/token-encryption-service.md delete mode 100644 .changeset/webhook-template-range-and-inline-query.md delete mode 100644 .changeset/whats-new-sparkle-release-version.md diff --git a/.changeset/alert-sample-rows-per-window.md b/.changeset/alert-sample-rows-per-window.md deleted file mode 100644 index 7e4df2e283..0000000000 --- a/.changeset/alert-sample-rows-per-window.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperdx/api': patch ---- - -fix: fetch a grouped alert's example log lines once per window - -A saved-search alert puts a handful of example log lines into the notification it sends, and fetching them takes a second query. That query was being made while building each message, so an alert grouped by service asked ClickHouse for the same lines once per breaching service — ten services meant ten identical queries over the same data, because the query only filters by the saved search and the time window, never by the group. It now runs once and every notification for that window shares the answer. An alert catching up on skipped ticks still fetches lines for each window it backfills, since those genuinely differ. Ungrouped alerts are unaffected; they only ever asked once. diff --git a/.changeset/alert-tags-suggestions.md b/.changeset/alert-tags-suggestions.md deleted file mode 100644 index ed04a68204..0000000000 --- a/.changeset/alert-tags-suggestions.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperdx/api": patch ---- - -feat: Include alert tags in the tags API response diff --git a/.changeset/backfill-alert-name-tags.md b/.changeset/backfill-alert-name-tags.md deleted file mode 100644 index 6f0a755b48..0000000000 --- a/.changeset/backfill-alert-name-tags.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperdx/api': minor ---- - -Backfill alert `displayName` and `tags` from the referenced saved search or dashboard on API startup. Alerts that already have a display name or tags are skipped. diff --git a/.changeset/chilled-pillows-joke.md b/.changeset/chilled-pillows-joke.md deleted file mode 100644 index 81037d9c60..0000000000 --- a/.changeset/chilled-pillows-joke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperdx/app": patch ---- - -Fix default time range resolution for long-lived sessions diff --git a/.changeset/disabled-sources-no-load-queries.md b/.changeset/disabled-sources-no-load-queries.md deleted file mode 100644 index c8f62b3a19..0000000000 --- a/.changeset/disabled-sources-no-load-queries.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperdx/app": patch ---- - -fix: Don't run ClickHouse queries for disabled sources on load. Disabled sources are now excluded from the metadata/field autocomplete and dashboard filter-value lookups that fire on page load, so loading a page no longer issues source-settings queries (e.g. `SELECT name, value FROM system.settings`) for sources that are turned off. diff --git a/.changeset/flat-foxes-report.md b/.changeset/flat-foxes-report.md deleted file mode 100644 index 3679889de7..0000000000 --- a/.changeset/flat-foxes-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperdx/otel-collector": minor ---- - -feat: add text indexes for seeding the trace schema diff --git a/.changeset/great-hoops-shave.md b/.changeset/great-hoops-shave.md deleted file mode 100644 index 52b23549a4..0000000000 --- a/.changeset/great-hoops-shave.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -'@hyperdx/app': patch ---- - -fix: use mapContains for LLM dashboard attribute-presence filters - -The LLM dashboard tested attribute presence with `SpanAttributes['key'] != ''`, -which no skip index can serve — the trace schema's `mapKeys(SpanAttributes)` -index only answers `mapContains`, and `!= ''` normalizes to `notEmpty()`. Every -tile therefore scanned all granules. Map subscripts are also subcolumn -references, so on ClickHouse 26.3+ each one adds a per-part size lookup during -PREWHERE planning. - -These filters now lead with `mapContains`, which the index serves and which -costs no per-part lookups. On a staging trace table the LLM span predicate went -from a 36s planning stall to 7ms, and a two-key filter dropped from 1,306 -granules to 3. - -Gates that pair with a value expression the dashboard groups by keep their -non-empty check, so an attribute set to `''` still cannot appear as a blank row. -There the value term defines the result and the presence term is pruning only, -so it is wrapped in `indexHint` — it reaches skip-index analysis without being -re-evaluated per surviving row. The value term costs no extra per-part lookups, -since it reads the same keys the group-by already reads. Gates that land in a -select-list aggregate are left unhinted, since skip-index analysis does not -reach the select list; the tool-call gate is used in both positions and so is -exposed in both forms. - -The one behavior change is LLM span detection, which is now presence-based: a -span carrying `gen_ai.system` at all is treated as an LLM span whatever the -value. Nothing groups by that predicate. - -One caveat for tables with materialized columns: a `SpanAttributes['key']` -subscript gets rewritten onto a materialized column when an operator has created -one, and `mapContains` is not matched by that rewrite. Such tables were never -affected by the planning cost either, since a rewritten subscript is no longer a -subcolumn reference — so this trades that rewrite for skip-index pruning, which -is the better deal only where those columns do not exist. - -JSON attribute columns are unchanged — their paths are real subcolumns, there is -no key index to prune with, and a presence term would only duplicate reads. diff --git a/.changeset/heavy-toys-cheat.md b/.changeset/heavy-toys-cheat.md deleted file mode 100644 index b8ad01ed35..0000000000 --- a/.changeset/heavy-toys-cheat.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@hyperdx/common-utils": patch -"@hyperdx/api": patch -"@hyperdx/app": patch ---- - -feat: Paginate the alerts page diff --git a/.changeset/iac-export-tile-alerts.md b/.changeset/iac-export-tile-alerts.md deleted file mode 100644 index e00d37607e..0000000000 --- a/.changeset/iac-export-tile-alerts.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@hyperdx/common-utils': minor -'@hyperdx/api': minor -'@hyperdx/app': minor ---- - -Offer dashboard tile alerts for Terraform import. `clickhouse_clickstack_alert` -gained `source = "tile"` with `dashboard_id`/`tile_id` in provider 3.28.0, so -the bulk export and the per-alert menu now include tile alerts instead of -skipping every alert that is not a saved-search one. A file carrying a tile -alert asks for `>= 3.28.0` and explains the hand edit its generated config -needs; an export without one still installs on 3.25.x. A tile alert is withheld -when its tile has a blank or duplicated name — the provider's `tile_ids` map is -keyed by tile name and omits those, so the alert could only be pinned to a -literal id the next dashboard apply can re-mint — or when its dashboard is -provisioned, since ProvisionDashboardsTask rewrites those tiles wholesale. Both -decisions are made server-side, on the import manifest and on the alerts -listing, because neither response carries a dashboard's sibling tile names. diff --git a/.changeset/incidentio-webhook-body.md b/.changeset/incidentio-webhook-body.md deleted file mode 100644 index e6942148f5..0000000000 --- a/.changeset/incidentio-webhook-body.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@hyperdx/api': patch -'@hyperdx/app': patch -'@hyperdx/common-utils': patch ---- - -fix: give incident.io webhooks a body incident.io accepts - -An incident.io webhook saved without a body was sent the generic `{"text": ...}` payload, which has neither of the two fields incident.io requires, so every delivery was rejected and no alert was ever raised. It now gets an incident.io payload carrying a deduplication key that is stable across a firing and its resolve, so incident.io closes the alert it opened, plus the alert id, status, condition and evaluation window in `metadata` for routing. The webhook body editor and its list of template variables are also available when incident.io is the selected service, not only for Generic, so the payload can be tailored to an alert source's configured fields. diff --git a/.changeset/inline-alert-ui.md b/.changeset/inline-alert-ui.md deleted file mode 100644 index eebb1cdcbb..0000000000 --- a/.changeset/inline-alert-ui.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperdx/app': minor ---- - -Create and edit alerts from the chart explorer, without a saved search or dashboard tile. Build a chart on `/chart` (logs, traces, or metrics — builder or raw SQL), add an alert, name it, and create it; the alert persists its own chart config. On the alerts page these alerts show their name with a chart icon and link back to the explorer seeded with their query, and the alert detail page renders that query and edits both the alert's fields and the chart behind it in the full chart editor. diff --git a/.changeset/lazy-pugs-smoke.md b/.changeset/lazy-pugs-smoke.md deleted file mode 100644 index 0fd3d30296..0000000000 --- a/.changeset/lazy-pugs-smoke.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@hyperdx/common-utils': patch -'@hyperdx/app': patch ---- - -fix: disable per-part subcolumn size calculation on ClickHouse 26.3+ - -ClickHouse 26.3 turned on -`allow_calculating_subcolumns_sizes_for_merge_tree_reading` by default, which -makes PREWHERE planning fetch per-part sizes for every map key a query -references. On SharedMergeTree that is one S3 GET per (key × active part), it -runs before any row is read, and `max_execution_time` does not interrupt it. -Queries referencing many attribute keys — the LLM dashboard reads ~64 — could -spend minutes in planning. Queries now send the setting as `0` when the server -supports it. diff --git a/.changeset/metric-select-value-expression.md b/.changeset/metric-select-value-expression.md deleted file mode 100644 index cf8a36acd7..0000000000 --- a/.changeset/metric-select-value-expression.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperdx/api': patch ---- - -Accept metric selects without a value expression on the external dashboards API. A tile that aggregates a metric names its value with `metricName` and has no expression to give, so `/api/v2/dashboards/validate` was rejecting dashboards the editor itself writes, and Terraform could not import them. diff --git a/.changeset/multiline-search-bar-always-visible.md b/.changeset/multiline-search-bar-always-visible.md deleted file mode 100644 index 7eaa144c79..0000000000 --- a/.changeset/multiline-search-bar-always-visible.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@hyperdx/app': patch ---- - -fix: keep a multi-line query visible when the field is not focused - -Multi-line SQL fields collapsed to a single line whenever they lost focus, so everything past the first line was hidden until you clicked back into them. Focusing one expanded it into a floating overlay that covered the content underneath — and in a container sized to its content, that overlay left the layout flow and shrank the field to a sliver one character wide. - -Any field that allows multiple lines now simply sizes to its content, focused or not, growing the layout rather than floating over it, up to 150px before it scrolls. That covers the search WHERE, SELECT and ORDER BY, the chart editor's SQL fields, and PromQL. Lucene search fields similarly grow up to four lines. Fields with `allowMultiline={false}` remain single-line in both SQL and Lucene. The focus overlay is gone rather than made optional, so there is no longer a separate expand-on-focus state to reason about. - -The language switch beside the search bar stretches to match, with no divider between it and the input. A Lucene bar at the default size also no longer overhangs that switch. The Lucene input reserved a 38px row while drawing a 36px box inside it, so the switch — sized to the 36px the SQL editor uses — stood 2px proud of the input. Both languages now take their height from one shared table, so the seam is flush and the bar does not change height when you switch. Only the default size was affected; the compact bar was already consistent at 30px. - -Focus recolors the whole control, including the language switch, without overriding an error or warning border. diff --git a/.changeset/notification-render-breakdown.md b/.changeset/notification-render-breakdown.md deleted file mode 100644 index 684a963921..0000000000 --- a/.changeset/notification-render-breakdown.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@hyperdx/app': patch -'@hyperdx/api': patch -'@hyperdx/common-utils': patch ---- - -fix: show only the delivery time in an alert's notification duration - -The notification duration on an alert's evaluation list was timing everything an alert does once it decides to fire: building the message title and links, querying the log lines that go in the body, rendering the template, and then delivering it. That made the column read in seconds while the webhook underneath it answered in milliseconds — the column and its own per-target breakdown disagreed, and the figure was dominated by work that has nothing to do with how fast the notification target responded. It now times the delivery alone. Evaluations already recorded keep their old figure and will read high. diff --git a/.changeset/olive-cameras-repeat.md b/.changeset/olive-cameras-repeat.md deleted file mode 100644 index 6d8385f665..0000000000 --- a/.changeset/olive-cameras-repeat.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@hyperdx/app': minor ---- - -feat: filter the LLM dashboard by end user - -Adds a user filter alongside the existing session filter. It lists the distinct -users seen on LLM spans in the searched range and scopes every tab to the one -selected, including the Errors tab's correlated log events. - -Users are resolved with the same cross-dialect expression the "Top Users" chart -groups by (`user.email`, `enduser.id`, `user.id`, -`ai.telemetry.metadata.userId`), so a value picked from the dropdown always -matches the rows that produced it. The selection lives in the URL, so a filtered -view can be shared. diff --git a/.changeset/onboarding-checklist-tasks.md b/.changeset/onboarding-checklist-tasks.md deleted file mode 100644 index 77dbfb4a90..0000000000 --- a/.changeset/onboarding-checklist-tasks.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperdx/common-utils': minor -'@hyperdx/api': minor -'@hyperdx/app': minor ---- - -Add a context-aware getting-started checklist to the sidebar for recently-created teams. After the setup steps (connect ClickHouse, add data) complete, a second phase tracks product-usage milestones persisted per user on `user.onboardingData`: exploring data, building a dashboard, setting up an alert, and using the MCP server. Completion is recorded server-side so it counts from the UI, the external REST API v2, or an MCP tool; the card can be dismissed and reappears if a new task is added to the registry. diff --git a/.changeset/onboarding-probe-escape-source-names.md b/.changeset/onboarding-probe-escape-source-names.md deleted file mode 100644 index 214d65428f..0000000000 --- a/.changeset/onboarding-probe-escape-source-names.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperdx/app': patch ---- - -Escape source table/database names in the onboarding checklist's has-data probe to prevent SQL injection via a maliciously named data source. diff --git a/.changeset/orange-pens-accept.md b/.changeset/orange-pens-accept.md deleted file mode 100644 index e533390e3e..0000000000 --- a/.changeset/orange-pens-accept.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@hyperdx/common-utils": patch -"@hyperdx/api": patch -"@hyperdx/app": patch ---- - -feat: relative date ranges for dashboards can now be saved diff --git a/.changeset/paginate-alerts-list.md b/.changeset/paginate-alerts-list.md deleted file mode 100644 index b837f08194..0000000000 --- a/.changeset/paginate-alerts-list.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperdx/api': patch -'@hyperdx/common-utils': patch ---- - -feat: page and filter GET /alerts server-side \ No newline at end of file diff --git a/.changeset/perfect-jars-tan.md b/.changeset/perfect-jars-tan.md deleted file mode 100644 index cdba172af5..0000000000 --- a/.changeset/perfect-jars-tan.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperdx/api": patch ---- - -refactor(api): backport alertConfigHasGroupBy helper diff --git a/.changeset/promql-chart-editor-refactor.md b/.changeset/promql-chart-editor-refactor.md deleted file mode 100644 index ce897b816b..0000000000 --- a/.changeset/promql-chart-editor-refactor.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperdx/common-utils': patch -'@hyperdx/app': patch ---- - -refactor: Extract PromQL functions, PromqlExpressionEditor, and ChartSeriesControls diff --git a/.changeset/promql-via-clickhouse-http-api.md b/.changeset/promql-via-clickhouse-http-api.md deleted file mode 100644 index a4995bc598..0000000000 --- a/.changeset/promql-via-clickhouse-http-api.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperdx/api': minor ---- - -feat: run PromQL against ClickHouse through its Prometheus HTTP API - -ClickHouse-backed `/query` and `/query_range` now proxy to the connection's `/prometheus/api/v1/*` endpoint (the `prometheus_api_v1` HTTP handler, ClickHouse 26.8+) instead of calling the `prometheusQuery`/`prometheusQueryRange` table functions. The `database` and `table` query parameters are forwarded so one handler serves any TimeSeries table. ClickHouse holds the Prometheus API forward-compatible while the TimeSeries engine is in preview; the table functions and inner-table schema are not. Bundled ClickHouse images move to 26.8. diff --git a/.changeset/quiet-pugs-clap.md b/.changeset/quiet-pugs-clap.md deleted file mode 100644 index 733a6f7d9b..0000000000 --- a/.changeset/quiet-pugs-clap.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@hyperdx/app': patch ---- - -fix: keep the LLM dashboard scope filters clearable when their options fail to -load - -The session and user dropdowns were disabled whenever their distinct-value query -was loading or had failed. With a filter applied that left the user looking at a -scope they could see but could not remove — permanently, if the query kept -failing. They now stay interactive whenever a value is applied. diff --git a/.changeset/quote-row-where-identifiers.md b/.changeset/quote-row-where-identifiers.md deleted file mode 100644 index 0922f1882a..0000000000 --- a/.changeset/quote-row-where-identifiers.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@hyperdx/app": patch -"@hyperdx/common-utils": patch ---- - -fix: Quote column identifiers when opening row details diff --git a/.changeset/required-dashboard-filters.md b/.changeset/required-dashboard-filters.md deleted file mode 100644 index d32723a452..0000000000 --- a/.changeset/required-dashboard-filters.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@hyperdx/common-utils': patch -'@hyperdx/api': patch -'@hyperdx/app': patch ---- - -feat: Allow configuring dashboard filters as required diff --git a/.changeset/search-row-multi-select.md b/.changeset/search-row-multi-select.md deleted file mode 100644 index 79b83475ca..0000000000 --- a/.changeset/search-row-multi-select.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperdx/app': minor ---- - -feat: Select rows in the search results table to copy or download them diff --git a/.changeset/service-map-absolute-error-thresholds.md b/.changeset/service-map-absolute-error-thresholds.md deleted file mode 100644 index 7774c52724..0000000000 --- a/.changeset/service-map-absolute-error-thresholds.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@hyperdx/app': patch ---- - -Color service map nodes by absolute error-rate thresholds instead of scaling -against the worst service on the graph. A service with no errors now renders -neutral grey rather than a pale red, and the remaining nodes fall into three -fixed buckets (under 1%, 1-5%, and 5% or above). A service the map has no error -data for — one that only calls others, with no incoming requests in the window — -renders hollow rather than filled, so "nothing measured" no longer looks like -"nothing wrong". - -Previously every node was a shade of red whose intensity was normalized against -the graph-wide maximum, so a map whose worst service sat at 0.3% errors painted -it the same deep red as one at 60%, and a healthy service was indistinguishable -from one with a trace of errors. The legend for error rate now shows the four -discrete steps, marks the 1% and 5% boundaries, and adds a key for the hollow -state. Latency and throughput coloring is unchanged. diff --git a/.changeset/silent-pandas-search.md b/.changeset/silent-pandas-search.md deleted file mode 100644 index 9d9ef3e761..0000000000 --- a/.changeset/silent-pandas-search.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperdx/api': patch ---- - -Apply saved-search pinned filters to alert notification sample queries so sample -log lines come from the same row set the alert counted. diff --git a/.changeset/spanmetrics-connector.md b/.changeset/spanmetrics-connector.md deleted file mode 100644 index 0565f27569..0000000000 --- a/.changeset/spanmetrics-connector.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@hyperdx/otel-collector": minor ---- - -feat(otel-collector): compile in spanmetricsconnector - -Available for a user's own pipeline config (e.g. via -`CUSTOM_OTELCOL_CONFIG_FILE`) to compute call-count and duration -metrics from spans - most useful alongside the existing -`datadogreceiver` support for ingesting Datadog Agent traces, where -there was previously no way to derive RED metrics from that trace data -once ingested. Purely additive: being compiled in changes no default -pipeline or behavior on its own. diff --git a/.changeset/stable-metadata-materialized-views-id.md b/.changeset/stable-metadata-materialized-views-id.md deleted file mode 100644 index e4cd967a83..0000000000 --- a/.changeset/stable-metadata-materialized-views-id.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperdx/api": patch ---- - -fix: Keep `/api/sources` responses stable for sources whose stored `metadataMaterializedViews` has no nested `_id` diff --git a/.changeset/stream-metric-names-from-index.md b/.changeset/stream-metric-names-from-index.md deleted file mode 100644 index a45f05a1cd..0000000000 --- a/.changeset/stream-metric-names-from-index.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@hyperdx/common-utils': minor -'@hyperdx/app': minor ---- - -Fill the metric name select from the table's primary index, so it populates almost immediately instead of waiting on an aggregation over the data. On a source reporting ~4,900 gauge metrics the first options appear in ~30ms rather than ~770ms, and they stream in progressively rather than arriving all at once. A small spinner replaces the dropdown chevron while more are still on the way. - -The picker now has two modes. **Browsing** streams `MetricName` out of the sparse primary index via the `mergeTreeIndex` table function — one row per granule mark instead of a full column scan. Because the index only records the value at each granule boundary, that list is a subset, weighted towards metrics that actually carry data (index-visible metrics have a median ~32k datapoints against ~14 for the rest). **Typing** switches to the exhaustive, relevance-ranked `GROUP BY` search, so any metric the index omitted is still reachable by name. The placeholder reads "Search metrics..." to invite that. - -Two details that matter in use: while the first search for a pattern is in flight the browse list is held and filtered client-side, so the options never blank out mid-keystroke; and the dropdown's render cap is raised to 500 to match the server-side page size, so a search that is not reported as truncated is fully renderable. - -Browsing falls back to the exhaustive listing when the index cannot be read at all — a server older than 24.2, a Distributed or non-MergeTree metric table, or a schema whose primary key omits `MetricName` — so no deployment loses the picker. - -`Metadata` gains `streamDistinctIndexValues`, an async generator generic over table and column, so any primary-key column (`ServiceName`, for instance) can be listed the same way. `streamToAsyncIterator` moves from `packages/app`'s session code into `common-utils` beside the ClickHouse client, and a new `useStreamingQuery` hook accumulates an async iterable into a React Query cache entry, publishing partial results on a throttle. diff --git a/.changeset/swift-pans-deny.md b/.changeset/swift-pans-deny.md deleted file mode 100644 index 00c29b0088..0000000000 --- a/.changeset/swift-pans-deny.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@hyperdx/common-utils": patch -"@hyperdx/api": patch -"@hyperdx/app": patch ---- - -feat: Scope tags endpoint by resource type diff --git a/.changeset/tile-editor-dashboard-filters.md b/.changeset/tile-editor-dashboard-filters.md deleted file mode 100644 index eef4144cb9..0000000000 --- a/.changeset/tile-editor-dashboard-filters.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hyperdx/common-utils': patch -'@hyperdx/app': patch ---- - -feat: Optionally apply the dashboard's filter selections to the tile editor preview diff --git a/.changeset/token-encryption-service.md b/.changeset/token-encryption-service.md deleted file mode 100644 index 97bc6c7bd3..0000000000 --- a/.changeset/token-encryption-service.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperdx/api": patch ---- - -feat: add a pluggable token encryption service for stored third-party tokens. Set `TOKEN_ENCRYPTION_KEY` to a 32-byte key (base64 or hex) to encrypt them with AES-256-GCM; without it they are stored unencrypted. diff --git a/.changeset/webhook-template-range-and-inline-query.md b/.changeset/webhook-template-range-and-inline-query.md deleted file mode 100644 index 4de5524d77..0000000000 --- a/.changeset/webhook-template-range-and-inline-query.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'@hyperdx/api': minor -'@hyperdx/app': minor -'@hyperdx/common-utils': minor ---- - -Report the whole alert condition in the `{{sourceQuery}}` webhook template -variable. It read only a chart's top-level `where`, so an alert defined by a -per-series `aggCondition` — a common shape — still rendered empty. The variable -now reports every part of the condition the alert query actually applies: a -chart's `where` plus the `aggCondition` of the series the alert reads, and a -saved search's `where` plus its pinned filters. A chart's pinned filters are -deliberately excluded, since a tile or inline alert does not apply them. The -value is truncated at 2000 characters. - -Editing an alert off a `between` or `outside` comparator now clears the stored -`thresholdMax` instead of leaving the old bound on the document, where it was -also served by the alerts APIs and would advertise a range that no longer -fires. Webhook templates already guarded against this on read. - -The webhook form's variable list and the API's fallback body template both -derive from one list in common-utils, which `buildWebhookTemplateVariables` is -typed against, so a variable cannot be added without appearing in both places. -The "Send test" payload carries a sample value for every variable, so a body -template can be checked before an alert fires. - -The documented guard for an optional number is now -`{{#unless (eq thresholdMax undefined)}}` rather than `{{#if thresholdMax}}`, -which treats a legitimate bound of `0` as absent. diff --git a/.changeset/whats-new-sparkle-release-version.md b/.changeset/whats-new-sparkle-release-version.md deleted file mode 100644 index 234d5d29c0..0000000000 --- a/.changeset/whats-new-sparkle-release-version.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hyperdx/app': patch ---- - -Stop the Help menu sparkling on every deploy. The "you haven't read the latest release notes" indicator compared the browser's last acknowledgement against `NEXT_PUBLIC_APP_VERSION`, which any deployment that stamps a build id into it (a git short SHA, a CI build number) changes on every deploy — so the nudge fired for every user every time whether a new release had been published or not. It now keys on the newest release version in the changelog, inlined at build time, and nudges only when that release is strictly newer than the one the browser has acknowledged, so a rollback no longer re-nudges everyone either. diff --git a/.env b/.env index f2857785e0..477e902bfe 100644 --- a/.env +++ b/.env @@ -8,8 +8,8 @@ NEXT_ALL_IN_ONE_IMAGE_NAME_DOCKERHUB=clickhouse/clickstack-all-in-one ALL_IN_ONE_IMAGE_NAME_DOCKERHUB=hyperdx/hyperdx-all-in-one NEXT_OTEL_COLLECTOR_IMAGE_NAME_DOCKERHUB=clickhouse/clickstack-otel-collector OTEL_COLLECTOR_IMAGE_NAME_DOCKERHUB=hyperdx/hyperdx-otel-collector -CODE_VERSION=2.38.0 -IMAGE_VERSION_SUB_TAG=.38.0 +CODE_VERSION=2.39.0 +IMAGE_VERSION_SUB_TAG=.39.0 IMAGE_VERSION=2 IMAGE_NIGHTLY_TAG=2-nightly IMAGE_LATEST_TAG=latest diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index dbfad2091e..64f7cabe3e 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,94 @@ # @hyperdx/api +## 2.39.0 + +### Minor Changes + +- ff1e77ce: Backfill alert `displayName` and `tags` from the referenced saved search or dashboard on API startup. Alerts that already have a display name or tags are skipped. +- e31e5d8d: Offer dashboard tile alerts for Terraform import. `clickhouse_clickstack_alert` + gained `source = "tile"` with `dashboard_id`/`tile_id` in provider 3.28.0, so + the bulk export and the per-alert menu now include tile alerts instead of + skipping every alert that is not a saved-search one. A file carrying a tile + alert asks for `>= 3.28.0` and explains the hand edit its generated config + needs; an export without one still installs on 3.25.x. A tile alert is withheld + when its tile has a blank or duplicated name — the provider's `tile_ids` map is + keyed by tile name and omits those, so the alert could only be pinned to a + literal id the next dashboard apply can re-mint — or when its dashboard is + provisioned, since ProvisionDashboardsTask rewrites those tiles wholesale. Both + decisions are made server-side, on the import manifest and on the alerts + listing, because neither response carries a dashboard's sibling tile names. +- f007c37f: Add a context-aware getting-started checklist to the sidebar for recently-created teams. After the setup steps (connect ClickHouse, add data) complete, a second phase tracks product-usage milestones persisted per user on `user.onboardingData`: exploring data, building a dashboard, setting up an alert, and using the MCP server. Completion is recorded server-side so it counts from the UI, the external REST API v2, or an MCP tool; the card can be dismissed and reappears if a new task is added to the registry. +- b2174306: feat: run PromQL against ClickHouse through its Prometheus HTTP API + + ClickHouse-backed `/query` and `/query_range` now proxy to the connection's `/prometheus/api/v1/*` endpoint (the `prometheus_api_v1` HTTP handler, ClickHouse 26.8+) instead of calling the `prometheusQuery`/`prometheusQueryRange` table functions. The `database` and `table` query parameters are forwarded so one handler serves any TimeSeries table. ClickHouse holds the Prometheus API forward-compatible while the TimeSeries engine is in preview; the table functions and inner-table schema are not. Bundled ClickHouse images move to 26.8. + +- 972634d2: Report the whole alert condition in the `{{sourceQuery}}` webhook template + variable. It read only a chart's top-level `where`, so an alert defined by a + per-series `aggCondition` — a common shape — still rendered empty. The variable + now reports every part of the condition the alert query actually applies: a + chart's `where` plus the `aggCondition` of the series the alert reads, and a + saved search's `where` plus its pinned filters. A chart's pinned filters are + deliberately excluded, since a tile or inline alert does not apply them. The + value is truncated at 2000 characters. + + Editing an alert off a `between` or `outside` comparator now clears the stored + `thresholdMax` instead of leaving the old bound on the document, where it was + also served by the alerts APIs and would advertise a range that no longer + fires. Webhook templates already guarded against this on read. + + The webhook form's variable list and the API's fallback body template both + derive from one list in common-utils, which `buildWebhookTemplateVariables` is + typed against, so a variable cannot be added without appearing in both places. + The "Send test" payload carries a sample value for every variable, so a body + template can be checked before an alert fires. + + The documented guard for an optional number is now + `{{#unless (eq thresholdMax undefined)}}` rather than `{{#if thresholdMax}}`, + which treats a legitimate bound of `0` as absent. + +### Patch Changes + +- 4d18cb09: fix: fetch a grouped alert's example log lines once per window + + A saved-search alert puts a handful of example log lines into the notification it sends, and fetching them takes a second query. That query was being made while building each message, so an alert grouped by service asked ClickHouse for the same lines once per breaching service — ten services meant ten identical queries over the same data, because the query only filters by the saved search and the time window, never by the group. It now runs once and every notification for that window shares the answer. An alert catching up on skipped ticks still fetches lines for each window it backfills, since those genuinely differ. Ungrouped alerts are unaffected; they only ever asked once. + +- c8cc8e5e: feat: Include alert tags in the tags API response +- 482d2cb0: feat: Paginate the alerts page +- 5311d63c: fix: give incident.io webhooks a body incident.io accepts + + An incident.io webhook saved without a body was sent the generic `{"text": ...}` payload, which has neither of the two fields incident.io requires, so every delivery was rejected and no alert was ever raised. It now gets an incident.io payload carrying a deduplication key that is stable across a firing and its resolve, so incident.io closes the alert it opened, plus the alert id, status, condition and evaluation window in `metadata` for routing. The webhook body editor and its list of template variables are also available when incident.io is the selected service, not only for Generic, so the payload can be tailored to an alert source's configured fields. + +- 6b391715: Accept metric selects without a value expression on the external dashboards API. A tile that aggregates a metric names its value with `metricName` and has no expression to give, so `/api/v2/dashboards/validate` was rejecting dashboards the editor itself writes, and Terraform could not import them. +- 84c67f4b: fix: show only the delivery time in an alert's notification duration + + The notification duration on an alert's evaluation list was timing everything an alert does once it decides to fire: building the message title and links, querying the log lines that go in the body, rendering the template, and then delivering it. That made the column read in seconds while the webhook underneath it answered in milliseconds — the column and its own per-target breakdown disagreed, and the figure was dominated by work that has nothing to do with how fast the notification target responded. It now times the delivery alone. Evaluations already recorded keep their old figure and will read high. + +- cfacdbe5: feat: relative date ranges for dashboards can now be saved +- 806d242e: feat: page and filter GET /alerts server-side +- b19fa12a: refactor(api): backport alertConfigHasGroupBy helper +- 78a33ba4: feat: Allow configuring dashboard filters as required +- edb693a6: Apply saved-search pinned filters to alert notification sample queries so sample + log lines come from the same row set the alert counted. +- 0a371980: fix: Keep `/api/sources` responses stable for sources whose stored `metadataMaterializedViews` has no nested `_id` +- bef61fbc: feat: Scope tags endpoint by resource type +- 6c85ca02: feat: add a pluggable token encryption service for stored third-party tokens. Set `TOKEN_ENCRYPTION_KEY` to a 32-byte key (base64 or hex) to encrypt them with AES-256-GCM; without it they are stored unencrypted. +- Updated dependencies [482d2cb0] +- Updated dependencies [e31e5d8d] +- Updated dependencies [5311d63c] +- Updated dependencies [96ac6b1b] +- Updated dependencies [84c67f4b] +- Updated dependencies [f007c37f] +- Updated dependencies [cfacdbe5] +- Updated dependencies [806d242e] +- Updated dependencies [f7ae72c2] +- Updated dependencies [b1e48b99] +- Updated dependencies [78a33ba4] +- Updated dependencies [3876d6b9] +- Updated dependencies [bef61fbc] +- Updated dependencies [b4840573] +- Updated dependencies [972634d2] + - @hyperdx/common-utils@0.29.0 + ## 2.38.0 ### Minor Changes diff --git a/packages/api/package.json b/packages/api/package.json index 29bbc4f608..01f21d442d 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@hyperdx/api", - "version": "2.38.0", + "version": "2.39.0", "license": "MIT", "private": true, "engines": { @@ -21,7 +21,7 @@ "@braintree/sanitize-url": "^7.1.1", "@clickhouse/client-common": "1.23.0-head.fae5998.1", "@esm2cjs/p-queue": "^7.3.0", - "@hyperdx/common-utils": "^0.28.1", + "@hyperdx/common-utils": "^0.29.0", "@hyperdx/node-opentelemetry": "^0.9.0", "@hyperdx/passport-local-mongoose": "^9.0.1", "@modelcontextprotocol/sdk": "^1.27.1", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 19853f8613..8b56a2647e 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,200 @@ # @hyperdx/app +## 2.39.0 + +### Minor Changes + +- e31e5d8d: Offer dashboard tile alerts for Terraform import. `clickhouse_clickstack_alert` + gained `source = "tile"` with `dashboard_id`/`tile_id` in provider 3.28.0, so + the bulk export and the per-alert menu now include tile alerts instead of + skipping every alert that is not a saved-search one. A file carrying a tile + alert asks for `>= 3.28.0` and explains the hand edit its generated config + needs; an export without one still installs on 3.25.x. A tile alert is withheld + when its tile has a blank or duplicated name — the provider's `tile_ids` map is + keyed by tile name and omits those, so the alert could only be pinned to a + literal id the next dashboard apply can re-mint — or when its dashboard is + provisioned, since ProvisionDashboardsTask rewrites those tiles wholesale. Both + decisions are made server-side, on the import manifest and on the alerts + listing, because neither response carries a dashboard's sibling tile names. +- 41eee7d3: Create and edit alerts from the chart explorer, without a saved search or dashboard tile. Build a chart on `/chart` (logs, traces, or metrics — builder or raw SQL), add an alert, name it, and create it; the alert persists its own chart config. On the alerts page these alerts show their name with a chart icon and link back to the explorer seeded with their query, and the alert detail page renders that query and edits both the alert's fields and the chart behind it in the full chart editor. +- 34d829c7: feat: filter the LLM dashboard by end user + + Adds a user filter alongside the existing session filter. It lists the distinct + users seen on LLM spans in the searched range and scopes every tab to the one + selected, including the Errors tab's correlated log events. + + Users are resolved with the same cross-dialect expression the "Top Users" chart + groups by (`user.email`, `enduser.id`, `user.id`, + `ai.telemetry.metadata.userId`), so a value picked from the dropdown always + matches the rows that produced it. The selection lives in the URL, so a filtered + view can be shared. + +- f007c37f: Add a context-aware getting-started checklist to the sidebar for recently-created teams. After the setup steps (connect ClickHouse, add data) complete, a second phase tracks product-usage milestones persisted per user on `user.onboardingData`: exploring data, building a dashboard, setting up an alert, and using the MCP server. Completion is recorded server-side so it counts from the UI, the external REST API v2, or an MCP tool; the card can be dismissed and reappears if a new task is added to the registry. +- 264f2164: feat: Select rows in the search results table to copy or download them +- 3876d6b9: Fill the metric name select from the table's primary index, so it populates almost immediately instead of waiting on an aggregation over the data. On a source reporting ~4,900 gauge metrics the first options appear in ~30ms rather than ~770ms, and they stream in progressively rather than arriving all at once. A small spinner replaces the dropdown chevron while more are still on the way. + + The picker now has two modes. **Browsing** streams `MetricName` out of the sparse primary index via the `mergeTreeIndex` table function — one row per granule mark instead of a full column scan. Because the index only records the value at each granule boundary, that list is a subset, weighted towards metrics that actually carry data (index-visible metrics have a median ~32k datapoints against ~14 for the rest). **Typing** switches to the exhaustive, relevance-ranked `GROUP BY` search, so any metric the index omitted is still reachable by name. The placeholder reads "Search metrics..." to invite that. + + Two details that matter in use: while the first search for a pattern is in flight the browse list is held and filtered client-side, so the options never blank out mid-keystroke; and the dropdown's render cap is raised to 500 to match the server-side page size, so a search that is not reported as truncated is fully renderable. + + Browsing falls back to the exhaustive listing when the index cannot be read at all — a server older than 24.2, a Distributed or non-MergeTree metric table, or a schema whose primary key omits `MetricName` — so no deployment loses the picker. + + `Metadata` gains `streamDistinctIndexValues`, an async generator generic over table and column, so any primary-key column (`ServiceName`, for instance) can be listed the same way. `streamToAsyncIterator` moves from `packages/app`'s session code into `common-utils` beside the ClickHouse client, and a new `useStreamingQuery` hook accumulates an async iterable into a React Query cache entry, publishing partial results on a throttle. + +- 972634d2: Report the whole alert condition in the `{{sourceQuery}}` webhook template + variable. It read only a chart's top-level `where`, so an alert defined by a + per-series `aggCondition` — a common shape — still rendered empty. The variable + now reports every part of the condition the alert query actually applies: a + chart's `where` plus the `aggCondition` of the series the alert reads, and a + saved search's `where` plus its pinned filters. A chart's pinned filters are + deliberately excluded, since a tile or inline alert does not apply them. The + value is truncated at 2000 characters. + + Editing an alert off a `between` or `outside` comparator now clears the stored + `thresholdMax` instead of leaving the old bound on the document, where it was + also served by the alerts APIs and would advertise a range that no longer + fires. Webhook templates already guarded against this on read. + + The webhook form's variable list and the API's fallback body template both + derive from one list in common-utils, which `buildWebhookTemplateVariables` is + typed against, so a variable cannot be added without appearing in both places. + The "Send test" payload carries a sample value for every variable, so a body + template can be checked before an alert fires. + + The documented guard for an optional number is now + `{{#unless (eq thresholdMax undefined)}}` rather than `{{#if thresholdMax}}`, + which treats a legitimate bound of `0` as absent. + +### Patch Changes + +- ab15643f: Fix default time range resolution for long-lived sessions +- 71d792a6: fix: Don't run ClickHouse queries for disabled sources on load. Disabled sources are now excluded from the metadata/field autocomplete and dashboard filter-value lookups that fire on page load, so loading a page no longer issues source-settings queries (e.g. `SELECT name, value FROM system.settings`) for sources that are turned off. +- 34d829c7: fix: use mapContains for LLM dashboard attribute-presence filters + + The LLM dashboard tested attribute presence with `SpanAttributes['key'] != ''`, + which no skip index can serve — the trace schema's `mapKeys(SpanAttributes)` + index only answers `mapContains`, and `!= ''` normalizes to `notEmpty()`. Every + tile therefore scanned all granules. Map subscripts are also subcolumn + references, so on ClickHouse 26.3+ each one adds a per-part size lookup during + PREWHERE planning. + + These filters now lead with `mapContains`, which the index serves and which + costs no per-part lookups. On a staging trace table the LLM span predicate went + from a 36s planning stall to 7ms, and a two-key filter dropped from 1,306 + granules to 3. + + Gates that pair with a value expression the dashboard groups by keep their + non-empty check, so an attribute set to `''` still cannot appear as a blank row. + There the value term defines the result and the presence term is pruning only, + so it is wrapped in `indexHint` — it reaches skip-index analysis without being + re-evaluated per surviving row. The value term costs no extra per-part lookups, + since it reads the same keys the group-by already reads. Gates that land in a + select-list aggregate are left unhinted, since skip-index analysis does not + reach the select list; the tool-call gate is used in both positions and so is + exposed in both forms. + + The one behavior change is LLM span detection, which is now presence-based: a + span carrying `gen_ai.system` at all is treated as an LLM span whatever the + value. Nothing groups by that predicate. + + One caveat for tables with materialized columns: a `SpanAttributes['key']` + subscript gets rewritten onto a materialized column when an operator has created + one, and `mapContains` is not matched by that rewrite. Such tables were never + affected by the planning cost either, since a rewritten subscript is no longer a + subcolumn reference — so this trades that rewrite for skip-index pruning, which + is the better deal only where those columns do not exist. + + JSON attribute columns are unchanged — their paths are real subcolumns, there is + no key index to prune with, and a presence term would only duplicate reads. + +- 482d2cb0: feat: Paginate the alerts page +- 5311d63c: fix: give incident.io webhooks a body incident.io accepts + + An incident.io webhook saved without a body was sent the generic `{"text": ...}` payload, which has neither of the two fields incident.io requires, so every delivery was rejected and no alert was ever raised. It now gets an incident.io payload carrying a deduplication key that is stable across a firing and its resolve, so incident.io closes the alert it opened, plus the alert id, status, condition and evaluation window in `metadata` for routing. The webhook body editor and its list of template variables are also available when incident.io is the selected service, not only for Generic, so the payload can be tailored to an alert source's configured fields. + +- 96ac6b1b: fix: disable per-part subcolumn size calculation on ClickHouse 26.3+ + + ClickHouse 26.3 turned on + `allow_calculating_subcolumns_sizes_for_merge_tree_reading` by default, which + makes PREWHERE planning fetch per-part sizes for every map key a query + references. On SharedMergeTree that is one S3 GET per (key × active part), it + runs before any row is read, and `max_execution_time` does not interrupt it. + Queries referencing many attribute keys — the LLM dashboard reads ~64 — could + spend minutes in planning. Queries now send the setting as `0` when the server + supports it. + +- 4db73970: fix: keep a multi-line query visible when the field is not focused + + Multi-line SQL fields collapsed to a single line whenever they lost focus, so everything past the first line was hidden until you clicked back into them. Focusing one expanded it into a floating overlay that covered the content underneath — and in a container sized to its content, that overlay left the layout flow and shrank the field to a sliver one character wide. + + Any field that allows multiple lines now simply sizes to its content, focused or not, growing the layout rather than floating over it, up to 150px before it scrolls. That covers the search WHERE, SELECT and ORDER BY, the chart editor's SQL fields, and PromQL. Lucene search fields similarly grow up to four lines. Fields with `allowMultiline={false}` remain single-line in both SQL and Lucene. The focus overlay is gone rather than made optional, so there is no longer a separate expand-on-focus state to reason about. + + The language switch beside the search bar stretches to match, with no divider between it and the input. A Lucene bar at the default size also no longer overhangs that switch. The Lucene input reserved a 38px row while drawing a 36px box inside it, so the switch — sized to the 36px the SQL editor uses — stood 2px proud of the input. Both languages now take their height from one shared table, so the seam is flush and the bar does not change height when you switch. Only the default size was affected; the compact bar was already consistent at 30px. + + Focus recolors the whole control, including the language switch, without overriding an error or warning border. + +- 84c67f4b: fix: show only the delivery time in an alert's notification duration + + The notification duration on an alert's evaluation list was timing everything an alert does once it decides to fire: building the message title and links, querying the log lines that go in the body, rendering the template, and then delivering it. That made the column read in seconds while the webhook underneath it answered in milliseconds — the column and its own per-target breakdown disagreed, and the figure was dominated by work that has nothing to do with how fast the notification target responded. It now times the delivery alone. Evaluations already recorded keep their old figure and will read high. + +- c98be91f: Escape source table/database names in the onboarding checklist's has-data probe to prevent SQL injection via a maliciously named data source. +- cfacdbe5: feat: relative date ranges for dashboards can now be saved +- f7ae72c2: refactor: Extract PromQL functions, PromqlExpressionEditor, and ChartSeriesControls +- fda038d6: fix: keep the LLM dashboard scope filters clearable when their options fail to + load + + The session and user dropdowns were disabled whenever their distinct-value query + was loading or had failed. With a filter applied that left the user looking at a + scope they could see but could not remove — permanently, if the query kept + failing. They now stay interactive whenever a value is applied. + +- b1e48b99: fix: Quote column identifiers when opening row details +- 78a33ba4: feat: Allow configuring dashboard filters as required +- db708a82: Color service map nodes by absolute error-rate thresholds instead of scaling + against the worst service on the graph. A service with no errors now renders + neutral grey rather than a pale red, and the remaining nodes fall into three + fixed buckets (under 1%, 1-5%, and 5% or above). A service the map has no error + data for — one that only calls others, with no incoming requests in the window — + renders hollow rather than filled, so "nothing measured" no longer looks like + "nothing wrong". + + Previously every node was a shade of red whose intensity was normalized against + the graph-wide maximum, so a map whose worst service sat at 0.3% errors painted + it the same deep red as one at 60%, and a healthy service was indistinguishable + from one with a trace of errors. The legend for error rate now shows the four + discrete steps, marks the 1% and 5% boundaries, and adds a key for the hollow + state. Latency and throughput coloring is unchanged. + +- bef61fbc: feat: Scope tags endpoint by resource type +- b4840573: feat: Optionally apply the dashboard's filter selections to the tile editor preview +- 25695c1a: Stop the Help menu sparkling on every deploy. The "you haven't read the latest release notes" indicator compared the browser's last acknowledgement against `NEXT_PUBLIC_APP_VERSION`, which any deployment that stamps a build id into it (a git short SHA, a CI build number) changes on every deploy — so the nudge fired for every user every time whether a new release had been published or not. It now keys on the newest release version in the changelog, inlined at build time, and nudges only when that release is strictly newer than the one the browser has acknowledged, so a rollback no longer re-nudges everyone either. +- Updated dependencies [4d18cb09] +- Updated dependencies [c8cc8e5e] +- Updated dependencies [ff1e77ce] +- Updated dependencies [482d2cb0] +- Updated dependencies [e31e5d8d] +- Updated dependencies [5311d63c] +- Updated dependencies [96ac6b1b] +- Updated dependencies [6b391715] +- Updated dependencies [84c67f4b] +- Updated dependencies [f007c37f] +- Updated dependencies [cfacdbe5] +- Updated dependencies [806d242e] +- Updated dependencies [b19fa12a] +- Updated dependencies [f7ae72c2] +- Updated dependencies [b2174306] +- Updated dependencies [b1e48b99] +- Updated dependencies [78a33ba4] +- Updated dependencies [edb693a6] +- Updated dependencies [0a371980] +- Updated dependencies [3876d6b9] +- Updated dependencies [bef61fbc] +- Updated dependencies [b4840573] +- Updated dependencies [6c85ca02] +- Updated dependencies [972634d2] + - @hyperdx/api@2.39.0 + - @hyperdx/common-utils@0.29.0 + ## 2.38.0 ### Minor Changes diff --git a/packages/app/package.json b/packages/app/package.json index 2d119d716a..f5eeccb102 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@hyperdx/app", - "version": "2.38.0", + "version": "2.39.0", "private": true, "license": "MIT", "engines": { @@ -38,7 +38,7 @@ "@hookform/resolvers": "^3.9.0", "@hyperdx/api": "workspace:*", "@hyperdx/browser": "^0.22.1", - "@hyperdx/common-utils": "^0.28.1", + "@hyperdx/common-utils": "^0.29.0", "@hyperdx/node-opentelemetry": "^0.9.0", "@mantine/core": "^9.0.0", "@mantine/dates": "^9.0.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index 41d1706c02..6170b3e198 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@clickhouse/client": "1.23.0-head.fae5998.1", - "@hyperdx/common-utils": "^0.28.1", + "@hyperdx/common-utils": "^0.29.0", "@jest/globals": "^30.2.0", "@types/crypto-js": "^4.2.2", "@types/jest": "^29.5.14", diff --git a/packages/common-utils/CHANGELOG.md b/packages/common-utils/CHANGELOG.md index 3c516d8fab..f595ae0fbc 100644 --- a/packages/common-utils/CHANGELOG.md +++ b/packages/common-utils/CHANGELOG.md @@ -1,5 +1,86 @@ # @hyperdx/common-utils +## 0.29.0 + +### Minor Changes + +- e31e5d8d: Offer dashboard tile alerts for Terraform import. `clickhouse_clickstack_alert` + gained `source = "tile"` with `dashboard_id`/`tile_id` in provider 3.28.0, so + the bulk export and the per-alert menu now include tile alerts instead of + skipping every alert that is not a saved-search one. A file carrying a tile + alert asks for `>= 3.28.0` and explains the hand edit its generated config + needs; an export without one still installs on 3.25.x. A tile alert is withheld + when its tile has a blank or duplicated name — the provider's `tile_ids` map is + keyed by tile name and omits those, so the alert could only be pinned to a + literal id the next dashboard apply can re-mint — or when its dashboard is + provisioned, since ProvisionDashboardsTask rewrites those tiles wholesale. Both + decisions are made server-side, on the import manifest and on the alerts + listing, because neither response carries a dashboard's sibling tile names. +- f007c37f: Add a context-aware getting-started checklist to the sidebar for recently-created teams. After the setup steps (connect ClickHouse, add data) complete, a second phase tracks product-usage milestones persisted per user on `user.onboardingData`: exploring data, building a dashboard, setting up an alert, and using the MCP server. Completion is recorded server-side so it counts from the UI, the external REST API v2, or an MCP tool; the card can be dismissed and reappears if a new task is added to the registry. +- 3876d6b9: Fill the metric name select from the table's primary index, so it populates almost immediately instead of waiting on an aggregation over the data. On a source reporting ~4,900 gauge metrics the first options appear in ~30ms rather than ~770ms, and they stream in progressively rather than arriving all at once. A small spinner replaces the dropdown chevron while more are still on the way. + + The picker now has two modes. **Browsing** streams `MetricName` out of the sparse primary index via the `mergeTreeIndex` table function — one row per granule mark instead of a full column scan. Because the index only records the value at each granule boundary, that list is a subset, weighted towards metrics that actually carry data (index-visible metrics have a median ~32k datapoints against ~14 for the rest). **Typing** switches to the exhaustive, relevance-ranked `GROUP BY` search, so any metric the index omitted is still reachable by name. The placeholder reads "Search metrics..." to invite that. + + Two details that matter in use: while the first search for a pattern is in flight the browse list is held and filtered client-side, so the options never blank out mid-keystroke; and the dropdown's render cap is raised to 500 to match the server-side page size, so a search that is not reported as truncated is fully renderable. + + Browsing falls back to the exhaustive listing when the index cannot be read at all — a server older than 24.2, a Distributed or non-MergeTree metric table, or a schema whose primary key omits `MetricName` — so no deployment loses the picker. + + `Metadata` gains `streamDistinctIndexValues`, an async generator generic over table and column, so any primary-key column (`ServiceName`, for instance) can be listed the same way. `streamToAsyncIterator` moves from `packages/app`'s session code into `common-utils` beside the ClickHouse client, and a new `useStreamingQuery` hook accumulates an async iterable into a React Query cache entry, publishing partial results on a throttle. + +- 972634d2: Report the whole alert condition in the `{{sourceQuery}}` webhook template + variable. It read only a chart's top-level `where`, so an alert defined by a + per-series `aggCondition` — a common shape — still rendered empty. The variable + now reports every part of the condition the alert query actually applies: a + chart's `where` plus the `aggCondition` of the series the alert reads, and a + saved search's `where` plus its pinned filters. A chart's pinned filters are + deliberately excluded, since a tile or inline alert does not apply them. The + value is truncated at 2000 characters. + + Editing an alert off a `between` or `outside` comparator now clears the stored + `thresholdMax` instead of leaving the old bound on the document, where it was + also served by the alerts APIs and would advertise a range that no longer + fires. Webhook templates already guarded against this on read. + + The webhook form's variable list and the API's fallback body template both + derive from one list in common-utils, which `buildWebhookTemplateVariables` is + typed against, so a variable cannot be added without appearing in both places. + The "Send test" payload carries a sample value for every variable, so a body + template can be checked before an alert fires. + + The documented guard for an optional number is now + `{{#unless (eq thresholdMax undefined)}}` rather than `{{#if thresholdMax}}`, + which treats a legitimate bound of `0` as absent. + +### Patch Changes + +- 482d2cb0: feat: Paginate the alerts page +- 5311d63c: fix: give incident.io webhooks a body incident.io accepts + + An incident.io webhook saved without a body was sent the generic `{"text": ...}` payload, which has neither of the two fields incident.io requires, so every delivery was rejected and no alert was ever raised. It now gets an incident.io payload carrying a deduplication key that is stable across a firing and its resolve, so incident.io closes the alert it opened, plus the alert id, status, condition and evaluation window in `metadata` for routing. The webhook body editor and its list of template variables are also available when incident.io is the selected service, not only for Generic, so the payload can be tailored to an alert source's configured fields. + +- 96ac6b1b: fix: disable per-part subcolumn size calculation on ClickHouse 26.3+ + + ClickHouse 26.3 turned on + `allow_calculating_subcolumns_sizes_for_merge_tree_reading` by default, which + makes PREWHERE planning fetch per-part sizes for every map key a query + references. On SharedMergeTree that is one S3 GET per (key × active part), it + runs before any row is read, and `max_execution_time` does not interrupt it. + Queries referencing many attribute keys — the LLM dashboard reads ~64 — could + spend minutes in planning. Queries now send the setting as `0` when the server + supports it. + +- 84c67f4b: fix: show only the delivery time in an alert's notification duration + + The notification duration on an alert's evaluation list was timing everything an alert does once it decides to fire: building the message title and links, querying the log lines that go in the body, rendering the template, and then delivering it. That made the column read in seconds while the webhook underneath it answered in milliseconds — the column and its own per-target breakdown disagreed, and the figure was dominated by work that has nothing to do with how fast the notification target responded. It now times the delivery alone. Evaluations already recorded keep their old figure and will read high. + +- cfacdbe5: feat: relative date ranges for dashboards can now be saved +- 806d242e: feat: page and filter GET /alerts server-side +- f7ae72c2: refactor: Extract PromQL functions, PromqlExpressionEditor, and ChartSeriesControls +- b1e48b99: fix: Quote column identifiers when opening row details +- 78a33ba4: feat: Allow configuring dashboard filters as required +- bef61fbc: feat: Scope tags endpoint by resource type +- b4840573: feat: Optionally apply the dashboard's filter selections to the tile editor preview + ## 0.28.1 ### Patch Changes diff --git a/packages/common-utils/package.json b/packages/common-utils/package.json index 86ee7fc6a6..6ba9affaa3 100644 --- a/packages/common-utils/package.json +++ b/packages/common-utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperdx/common-utils", "description": "Common utilities for HyperDX application", - "version": "0.28.1", + "version": "0.29.0", "license": "MIT", "private": true, "files": [ diff --git a/packages/otel-collector/CHANGELOG.md b/packages/otel-collector/CHANGELOG.md index cda065776c..54857ffea8 100644 --- a/packages/otel-collector/CHANGELOG.md +++ b/packages/otel-collector/CHANGELOG.md @@ -1,5 +1,20 @@ # @hyperdx/otel-collector +## 2.39.0 + +### Minor Changes + +- 7651bc34: feat: add text indexes for seeding the trace schema +- fc4d5878: feat(otel-collector): compile in spanmetricsconnector + + Available for a user's own pipeline config (e.g. via + `CUSTOM_OTELCOL_CONFIG_FILE`) to compute call-count and duration + metrics from spans - most useful alongside the existing + `datadogreceiver` support for ingesting Datadog Agent traces, where + there was previously no way to derive RED metrics from that trace data + once ingested. Purely additive: being compiled in changes no default + pipeline or behavior on its own. + ## 2.38.0 ### Patch Changes diff --git a/packages/otel-collector/package.json b/packages/otel-collector/package.json index 6b702248a4..5b95c644ce 100644 --- a/packages/otel-collector/package.json +++ b/packages/otel-collector/package.json @@ -1,7 +1,7 @@ { "name": "@hyperdx/otel-collector", "description": "HyperDX OpenTelemetry Collector configuration and Docker image", - "version": "2.38.0", + "version": "2.39.0", "license": "MIT", "private": true } diff --git a/yarn.lock b/yarn.lock index 9946b4048c..1725e301e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4625,7 +4625,7 @@ __metadata: "@braintree/sanitize-url": "npm:^7.1.1" "@clickhouse/client-common": "npm:1.23.0-head.fae5998.1" "@esm2cjs/p-queue": "npm:^7.3.0" - "@hyperdx/common-utils": "npm:^0.28.1" + "@hyperdx/common-utils": "npm:^0.29.0" "@hyperdx/node-opentelemetry": "npm:^0.9.0" "@hyperdx/passport-local-mongoose": "npm:^9.0.1" "@modelcontextprotocol/sdk": "npm:^1.27.1" @@ -4717,7 +4717,7 @@ __metadata: "@hookform/resolvers": "npm:^3.9.0" "@hyperdx/api": "workspace:*" "@hyperdx/browser": "npm:^0.22.1" - "@hyperdx/common-utils": "npm:^0.28.1" + "@hyperdx/common-utils": "npm:^0.29.0" "@hyperdx/node-opentelemetry": "npm:^0.9.0" "@mantine/core": "npm:^9.0.0" "@mantine/dates": "npm:^9.0.0" @@ -4844,7 +4844,7 @@ __metadata: resolution: "@hyperdx/cli@workspace:packages/cli" dependencies: "@clickhouse/client": "npm:1.23.0-head.fae5998.1" - "@hyperdx/common-utils": "npm:^0.28.1" + "@hyperdx/common-utils": "npm:^0.29.0" "@jest/globals": "npm:^30.2.0" "@types/crypto-js": "npm:^4.2.2" "@types/jest": "npm:^29.5.14" @@ -4871,7 +4871,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperdx/common-utils@npm:^0.28.1, @hyperdx/common-utils@workspace:packages/common-utils": +"@hyperdx/common-utils@npm:^0.29.0, @hyperdx/common-utils@workspace:packages/common-utils": version: 0.0.0-use.local resolution: "@hyperdx/common-utils@workspace:packages/common-utils" dependencies: From 91283e845b990c4f1b9bdf64ed3bad08bb2d8f4c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 18 Sep 2026 17:53:55 +0000 Subject: [PATCH 2/2] chore(release): update root CHANGELOG.md --- CHANGELOG.md | 291 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e26ac94b3f..5e246f0557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,297 @@ PR — keep the `hyperdx-release-notes` comment marker intact when editing so yo edits survive regeneration. Per-package detail lives in each `packages/*/CHANGELOG.md`. +## v2.39.0 — 2026-09-18 + + + +**Chart alerts and required dashboard filters** + +You can now create an alert directly from a chart in the explorer, without first +saving it to a dashboard tile or a saved search — build the query, add an alert, +and it carries its own chart config with it. Dashboard filters can now be +configured as required rather than optional, so a dashboard that is only +meaningful once it has been scoped can say so, and a dashboard's time range can +now be saved as a relative window such as the last hour instead of the dates it +happened to be showing. The bundled OpenTelemetry collector also ships with the +spanmetrics connector compiled in, so you can derive call-count and duration +metrics from the spans it already receives — that matters most if you ingest +Datadog Agent traces through the existing `datadogreceiver`, where until now +there was no way to get RED metrics out of that trace data once it had landed. +Alert webhooks now report the condition an alert actually evaluates, so +`{{sourceQuery}}` no longer arrives empty for alerts defined by a per-series +condition, and an incident.io webhook left without a body is no longer rejected +on every delivery; the LLM dashboard gains an end-user filter and much faster +tiles. If you run PromQL against your own ClickHouse, read the breaking change +below before you upgrade. + +### 💥 Breaking Changes + +- **PromQL against ClickHouse now goes through its Prometheus HTTP API**: + ClickHouse-backed `/query` and `/query_range` proxy to the connection's + `/prometheus/api/v1/*` endpoint — the `prometheus_api_v1` HTTP handler, which + arrived in ClickHouse 26.8 — rather than calling the + `prometheusQuery`/`prometheusQueryRange` table functions. ClickHouse holds the + Prometheus API forward-compatible while the TimeSeries engine is in preview, + which the table functions and the inner-table schema are not, so this is the + durable path. The bundled ClickHouse images move to 26.8; if your ClickHouse + connection points at a server you run yourself, get it onto 26.8 or newer with + that handler enabled before upgrading, or ClickHouse-backed PromQL queries + will fail. The `database` and `table` query parameters are forwarded, so one + handler serves any TimeSeries table (#3142, thanks @knudtty!). + +### ✨ New Features + +- **Alerts straight from the chart explorer**: build a chart on `/chart` — logs, + traces or metrics, in the builder or in raw SQL — then add an alert, name it + and create it, with no saved search or dashboard tile in the way. The alert + persists its own chart config; on the alerts page it shows its name with a + chart icon and links back to the explorer seeded with its query, and its detail + page renders that query and edits both the alert's fields and the chart behind + it in the full chart editor (#3069). +- **Dashboard filters can be marked required**: a dashboard filter can now be + configured as required instead of optional, so a dashboard that only makes + sense scoped to a particular value carries that expectation with it. The + setting is carried through the schemas and APIs as well as the app, so a + filter defined this way survives a round trip through the API (#3078, thanks + @pulpdrew!). +- **Dashboards remember relative date ranges**: a dashboard can now be saved + with a relative time range — the last hour, the last day — rather than the + fixed dates it was showing when you saved it, so it reopens on a window + anchored to now every time you come back to it. Like the filter settings, the + range is carried through the schemas and APIs as well as the app (#3073, + thanks @knudtty!). +- **Span metrics from your own collector pipeline**: the OTel collector now has + the `spanmetricsconnector` compiled in, ready to reference from a custom + pipeline supplied via `CUSTOM_OTELCOL_CONFIG_FILE` to compute call-count and + duration (RED) metrics from spans — including the Datadog Agent traces the + `datadogreceiver` ingests. The change is purely additive: no default pipeline + or behaviour changes on its own (#3044, thanks @arj22!). +- **Filter the LLM dashboard by end user**: a user filter now sits alongside the + existing session filter, listing the distinct users seen on LLM spans in the + searched range and scoping every tab to the one you pick, including the Errors + tab's correlated log events. Users are resolved the same way the "Top Users" + chart groups them, so a value from the dropdown always matches the rows that + produced it, and the selection lives in the URL so a filtered view can be + shared (#3104). +- **Pick rows out of the search results table**: you can now select rows in the + search results table and copy or download just the ones you selected, rather + than taking the whole result set (#3140, thanks @pulpdrew!). +- **Stored third-party tokens can be encrypted at rest**: a pluggable token + encryption service now sits in front of the third-party tokens HyperDX keeps. + Set `TOKEN_ENCRYPTION_KEY` to a 32-byte key, in base64 or hex, and they are + encrypted with AES-256-GCM; leave it unset and they continue to be stored + unencrypted as before (#3112, thanks @jordan-simonovski!). +- **A getting-started checklist in the sidebar**: recently created teams now get + a context-aware checklist in the sidebar. It walks through the setup steps — + connecting ClickHouse and getting data in — and then tracks the product + milestones that follow: exploring your data, building a dashboard, setting up + an alert and using the MCP server. Progress is recorded server-side, so a step + counts whether you took it in the UI, through the REST API v2 or from an MCP + tool, and the card can be dismissed, reappearing only if a new task joins the + list. The checklist's has-data probe escapes the table and database names of + the source it reads, so a maliciously named data source cannot inject SQL + through it (#3084, #3128). +- **Dashboard tile alerts can be exported to Terraform**: the bulk export and the + per-alert menu now include alerts attached to a dashboard tile, rather than + skipping every alert that is not a saved-search one — + `clickhouse_clickstack_alert` gained `source = "tile"` with + `dashboard_id`/`tile_id` in provider 3.28.0. A file carrying a tile alert asks + for `>= 3.28.0` and explains the hand edit its generated config needs; an + export without one still installs on 3.25.x. A tile alert is withheld when its + tile has a blank or duplicated name, since the provider's `tile_ids` map is + keyed by tile name and omits those, or when its dashboard is provisioned, since + provisioning rewrites those tiles wholesale (#3076, thanks + @jordan-simonovski!). + +### 🔧 Improvements + +- **The metric name picker fills from the table's primary index**: opening the + metric select no longer waits on an aggregation over your metrics data — names + now stream out of the table's sparse primary index, so on a source reporting + around 4,900 gauge metrics the first options appear in about 30ms rather than + 770ms, and the rest arrive progressively while a small spinner sits in place of + the dropdown chevron. Browsing shows a subset weighted towards the metrics that + actually carry data; typing switches to the exhaustive, relevance-ranked + search, so any metric the index leaves out is still reachable by name. Where + the index cannot be read at all — a server older than 24.2, a Distributed or + non-MergeTree metrics table, or a primary key without `MetricName` — the picker + falls back to the exhaustive listing, so no deployment loses it (#3025, thanks + @MikeShi42!). +- **Tile previews can follow the dashboard's filters**: the tile editor can now + apply the dashboard's current filter selections to its preview, so you edit a + tile against the data the dashboard is actually showing rather than an + unfiltered version of it (#3093, thanks @pulpdrew!). +- **Service map nodes are coloured by absolute error rate**: node colour no + longer scales against the worst service on the graph, where a map whose worst + service sat at 0.3% errors painted it the same deep red as one at 60% and a + healthy service was indistinguishable from one with a trace of errors. A + service with no errors now renders neutral grey rather than a pale red, the + rest fall into three fixed buckets — under 1%, 1–5%, and 5% or above — and a + service the map has no error data for, one that only calls others with no + incoming requests in the window, renders hollow rather than filled, so + "nothing measured" no longer looks like "nothing wrong". The error-rate legend + now shows those four discrete steps, marks the 1% and 5% boundaries and adds a + key for the hollow state; latency and throughput colouring is unchanged + (#3125, thanks @MikeShi42!). +- **"Send test" fills in every webhook template variable**: the test payload now + carries a sample value for every variable the form lists, so a body template + can be checked before an alert ever fires. The documented guard for an + optional number is now `{{#unless (eq thresholdMax undefined)}}` rather than + `{{#if thresholdMax}}`, which treats a legitimate bound of `0` as absent + (#3068, thanks @jordan-simonovski!). +- **Grouped alerts fetch their example log lines once per window**: a + saved-search alert puts a handful of example log lines into the notification it + sends, and that second query was being made while each message was built — so + an alert grouped by service asked ClickHouse for the same lines once per + breaching service, ten identical queries over the same data for ten services, + because the query only ever filters by the saved search and the time window and + never by the group. It now runs once and every notification for that window + shares the answer. An alert catching up on skipped ticks still fetches lines + for each window it backfills, since those genuinely differ, and ungrouped + alerts are unaffected (#3111, thanks @jordan-simonovski!). +- **Alert tags are in the tags API, and tags can be asked for by resource + type**: the tags applied to an alert are now returned by the tags API + alongside the tags on your other resources, so they are listed and suggested + wherever tags are. The endpoint is also scoped by resource type now, so a + request can ask for the tags used on one kind of resource rather than every + tag the team has (#3092, #3139, thanks @pulpdrew!). +- **Existing alerts are given a name and tags to match**: alerts gained a display + name and tags of their own in the last release, and the API now backfills both + on startup from the saved search or dashboard an alert watches, so alerts + created before then are named and labelled without you revisiting each one. An + alert that already carries a display name or tags is left untouched (#3029, + thanks @SpencerTorres!). +- **The alerts page is paginated, and pages server-side**: `GET /alerts` now + takes paging and filter parameters and applies them in the database, and the + alerts page reads it a page at a time rather than pulling down every alert a + team has and narrowing the list in the browser (#3134, #3136, thanks + @pulpdrew!). +- **Newly seeded trace schemas come with text indexes**: the collector now adds + text indexes when it seeds the trace schema, so searches over trace text are + backed by an index from the start. Seeding only runs when the schema is + created, so a deployment whose trace tables already exist is unchanged + (#3096, thanks @knudtty!). +- **Much faster LLM dashboard tiles**: the dashboard tested whether a span + attribute was present in a way no ClickHouse skip index could serve, so every + tile scanned the whole table; those filters now lead with `mapContains`, which + the trace schema's key index answers. On a staging trace table the LLM span + predicate went from a 36 second planning stall to 7ms, and a two-key filter + dropped from 1,306 granules to 3. One behaviour change comes with it: a span + carrying `gen_ai.system` at all now counts as an LLM span, whatever the value. + Tables with operator-created materialised columns for those attributes were + never affected by the planning cost and trade that rewrite for index pruning; + JSON attribute columns are unchanged (#3104). +- **Queries no longer stall in planning on ClickHouse 26.3 and later**: + ClickHouse 26.3 turns on + `allow_calculating_subcolumns_sizes_for_merge_tree_reading` by default, which + makes PREWHERE planning fetch per-part sizes for every map key a query + references — on SharedMergeTree that is one S3 GET per key and active part, it + runs before a single row is read, and `max_execution_time` does not interrupt + it, so a query touching many attribute keys (the LLM dashboard reads around 64) + could spend minutes getting started. Queries now send that setting as `0` + wherever the server supports it (#3102). + +### 🐛 Bug Fixes + +- **incident.io webhooks send a body incident.io accepts**: a webhook saved + without a body was sent the generic `{"text": ...}` payload, which has neither + of the two fields incident.io requires — so every delivery was rejected and no + alert was ever raised. It now gets an incident.io payload carrying a + deduplication key that is stable across a firing and its resolve, so + incident.io closes the alert it opened, plus the alert id, status, condition + and evaluation window in `metadata` for routing. The webhook body editor and + its list of template variables are also available when incident.io is the + selected service, not only for Generic, so the payload can be tailored to an + alert source's configured fields (#3144, thanks @jordan-simonovski!). +- **`{{sourceQuery}}` reports the condition the alert evaluates**: the webhook + template variable read only a chart's top-level `where`, so an alert defined + by a per-series `aggCondition` — a common shape — rendered it empty. It now + reports every part of the condition the alert query applies: a chart's `where` + plus the `aggCondition` of the series the alert reads, and a saved search's + `where` plus its pinned filters, truncated at 2000 characters. A chart's + pinned filters are deliberately excluded, since a tile or inline alert does + not apply them. Editing an alert off a `between` or `outside` comparator also + clears the stored `thresholdMax` now, instead of leaving a bound that the + alerts APIs would go on serving to advertise a range that no longer fires + (#3068, thanks @jordan-simonovski!). +- **An alert's notification duration times the delivery alone**: the duration on + an alert's evaluation list was timing everything the alert does once it decides + to fire — building the message title and links, querying the log lines for the + body, rendering the template, then delivering it — so the column read in + seconds while the webhook underneath it answered in milliseconds, disagreeing + with its own per-target breakdown over work that says nothing about how fast + the target responded. It now times the delivery alone; evaluations already + recorded keep their old figure and will read high (#3110, thanks + @jordan-simonovski!). +- **An alert's example log lines come from the rows it counted**: the sample + lines attached to a saved-search alert's notification were queried without the + saved search's pinned filters, so they could show rows the alert never counted. + The sample query now applies those filters too, so the lines in the + notification come from the same row set that breached (#3106, thanks + @Tyagiquamar!). +- **The default time range no longer goes stale in a long-lived tab**: the + default search time range was resolved once when the app first loaded, so a + browser tab left open for hours kept falling back to a window anchored to that + original load. It is now resolved against the current time instead (#3087, + thanks @Aryainguz!). +- **The Help menu stops sparkling on every deploy**: the "you haven't read the + latest release notes" indicator compared your browser's last acknowledgement + against `NEXT_PUBLIC_APP_VERSION`, so any deployment that stamps a build id + into it — a git short SHA, a CI build number — nudged every user on every + deploy whether a release had been published or not. It now keys on the newest + release in the changelog and nudges only when that release is strictly newer + than the one you have already acknowledged, so a rollback no longer re-nudges + everyone either (#3042, thanks @jordan-simonovski!). +- **`/api/sources` responses stay stable for older sources**: a source whose + stored `metadataMaterializedViews` carries no nested `_id` is now returned + consistently from one request to the next, so tooling that reads the sources + API no longer sees the same source come back looking different each time + (#3099, thanks @pulpdrew!). +- **Metric tiles pass validation on the dashboards API**: a tile that aggregates + a metric names its value with `metricName` and has no value expression to + give, so `/api/v2/dashboards/validate` was turning away dashboards the editor + itself writes and Terraform could not import them. Metric selects without a + value expression are now accepted (#3143, thanks @jordan-simonovski!). +- **LLM dashboard scope filters stay clearable**: the session and user dropdowns + were disabled whenever their list of distinct values was loading or had + failed, leaving you looking at a scope you could see but could not remove — + permanently, if the query kept failing. They now stay interactive whenever a + value is applied (#3105). +- **Disabled sources no longer run queries on page load**: a source you have + turned off is now excluded from the metadata and field autocomplete lookups + and the dashboard filter-value lookups that fire as a page loads, so opening a + page no longer issues source-settings queries — `SELECT name, value FROM + system.settings` and the like — against sources that are switched off (#3107). +- **Row details open on columns whose names need quoting**: the side panel now + quotes column identifiers when it builds the query behind a row, so opening a + row whose columns are hyphenated, or whose aliases arrived backticked, works + the way every other column already did (#3115, thanks @pulpdrew!). +- **A multi-line query stays visible when you click away from it**: multi-line + SQL fields collapsed back to a single line whenever they lost focus, hiding + everything past the first line until you clicked into them again — and + focusing one opened it as a floating overlay that covered the content beneath + it, which in a container sized to its own content could shrink the field to a + sliver one character wide. Any field that allows multiple lines now simply + sizes to its content whether focused or not, growing the layout rather than + floating over it, up to 150px before it scrolls: the search WHERE, SELECT and + ORDER BY fields, the chart editor's SQL fields and PromQL, with Lucene search + fields growing up to four lines. The language switch beside the search bar + stretches to match, and both languages now take their height from one shared + table, so the bar no longer changes height when you switch between them + (#3150, thanks @elizabetdev!). + + + +### 📦 Package changelogs + +- `@hyperdx/api` 2.38.0 → 2.39.0 — [changelog](https://github.com/hyperdxio/hyperdx/blob/main/packages/api/CHANGELOG.md#2390) +- `@hyperdx/app` 2.38.0 → 2.39.0 — [changelog](https://github.com/hyperdxio/hyperdx/blob/main/packages/app/CHANGELOG.md#2390) +- `@hyperdx/common-utils` 0.28.1 → 0.29.0 — [changelog](https://github.com/hyperdxio/hyperdx/blob/main/packages/common-utils/CHANGELOG.md#0290) +- `@hyperdx/otel-collector` 2.38.0 → 2.39.0 — [changelog](https://github.com/hyperdxio/hyperdx/blob/main/packages/otel-collector/CHANGELOG.md#2390) + + + ## v2.38.0 — 2026-09-04