@hyperdx/app@2.39.0
Minor Changes
-
e31e5d8: Offer dashboard tile alerts for Terraform import.
clickhouse_clickstack_alert
gainedsource = "tile"withdashboard_id/tile_idin 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.0and 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'stile_idsmap 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. -
41eee7d: 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. -
34d829c: 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. -
f007c37: 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. -
264f216: feat: Select rows in the search results table to copy or download them
-
3876d6b: 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
MetricNameout of the sparse primary index via themergeTreeIndextable 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-rankedGROUP BYsearch, 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.MetadatagainsstreamDistinctIndexValues, an async generator generic over table and column, so any primary-key column (ServiceName, for instance) can be listed the same way.streamToAsyncIteratormoves frompackages/app's session code intocommon-utilsbeside the ClickHouse client, and a newuseStreamingQueryhook accumulates an async iterable into a React Query cache entry, publishing partial results on a throttle. -
972634d: Report the whole alert condition in the
{{sourceQuery}}webhook template
variable. It read only a chart's top-levelwhere, so an alert defined by a
per-seriesaggCondition— a common shape — still rendered empty. The variable
now reports every part of the condition the alert query actually applies: a
chart'swhereplus theaggConditionof the series the alert reads, and a
saved search'swhereplus 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
betweenoroutsidecomparator now clears the stored
thresholdMaxinstead 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, whichbuildWebhookTemplateVariablesis
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 of0as absent.
Patch Changes
-
ab15643: Fix default time range resolution for long-lived sessions
-
71d792a: 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. -
34d829c: 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'smapKeys(SpanAttributes)
index only answersmapContains, and!= ''normalizes tonotEmpty(). 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 inindexHint— 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 carryinggen_ai.systemat 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, andmapContainsis 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. -
482d2cb: feat: Paginate the alerts page
-
5311d63: 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 inmetadatafor 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. -
96ac6b1: fix: disable per-part subcolumn size calculation on ClickHouse 26.3+
ClickHouse 26.3 turned on
allow_calculating_subcolumns_sizes_for_merge_tree_readingby 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, andmax_execution_timedoes not interrupt it.
Queries referencing many attribute keys — the LLM dashboard reads ~64 — could
spend minutes in planning. Queries now send the setting as0when the server
supports it. -
4db7397: 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.
-
84c67f4: 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.
-
c98be91: Escape source table/database names in the onboarding checklist's has-data probe to prevent SQL injection via a maliciously named data source.
-
cfacdbe: feat: relative date ranges for dashboards can now be saved
-
f7ae72c: refactor: Extract PromQL functions, PromqlExpressionEditor, and ChartSeriesControls
-
fda038d: fix: keep the LLM dashboard scope filters clearable when their options fail to
loadThe 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. -
b1e48b9: fix: Quote column identifiers when opening row details
-
78a33ba: feat: Allow configuring dashboard filters as required
-
db708a8: 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. -
bef61fb: feat: Scope tags endpoint by resource type
-
b484057: feat: Optionally apply the dashboard's filter selections to the tile editor preview
-
25695c1: 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 [4d18cb0]
-
Updated dependencies [c8cc8e5]
-
Updated dependencies [ff1e77c]
-
Updated dependencies [482d2cb]
-
Updated dependencies [e31e5d8]
-
Updated dependencies [5311d63]
-
Updated dependencies [96ac6b1]
-
Updated dependencies [6b39171]
-
Updated dependencies [84c67f4]
-
Updated dependencies [f007c37]
-
Updated dependencies [cfacdbe]
-
Updated dependencies [806d242]
-
Updated dependencies [b19fa12]
-
Updated dependencies [f7ae72c]
-
Updated dependencies [b217430]
-
Updated dependencies [b1e48b9]
-
Updated dependencies [78a33ba]
-
Updated dependencies [edb693a]
-
Updated dependencies [0a37198]
-
Updated dependencies [3876d6b]
-
Updated dependencies [bef61fb]
-
Updated dependencies [b484057]
-
Updated dependencies [6c85ca0]
-
Updated dependencies [972634d]
- @hyperdx/api@2.39.0
- @hyperdx/common-utils@0.29.0