From eff16152d3d4e7ea6c2512467b23b85bd1e9a549 Mon Sep 17 00:00:00 2001 From: xob0t <5348245@gmail.com> Date: Wed, 15 Jul 2026 15:53:40 +0300 Subject: [PATCH 1/2] feat(dashboards): add replay search action --- .changeset/dashboard-replay-search.md | 5 +++ packages/app/src/DBDashboardPage.tsx | 57 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .changeset/dashboard-replay-search.md diff --git a/.changeset/dashboard-replay-search.md b/.changeset/dashboard-replay-search.md new file mode 100644 index 0000000000..039bbbaab5 --- /dev/null +++ b/.changeset/dashboard-replay-search.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +feat(dashboards): add a Replay search action to dashboard tiles backed by event-search-compatible chart configurations. The action opens a new Search tab with the tile's source, query, filters, and dashboard time range preserved. diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index bf5ba80740..6519634493 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -24,6 +24,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import RGL, { WidthProvider } from 'react-grid-layout'; import { useForm, useWatch } from 'react-hook-form'; import { TableConnection } from '@hyperdx/common-utils/dist/core/metadata'; +import { isMetricChartConfig } from '@hyperdx/common-utils/dist/core/renderChartConfig'; import { convertToDashboardTemplate, displayTypeSupportsBuilderAlerts, @@ -684,6 +685,30 @@ const Tile = forwardRef( ); }, [filters, queriedConfig, source]); + const replaySearchUrl = useMemo(() => { + if (!queriedConfig || !source || !isBuilderChartConfig(queriedConfig)) { + return null; + } + + // Metric charts drill into their associated log source. Do not call the + // URL builder when that association is missing because it would surface + // a notification while rendering the dashboard. + if ( + isMetricChartConfig(queriedConfig) && + ((source.kind !== SourceKind.Metric && + source.kind !== SourceKind.Trace) || + !source.logSourceId) + ) { + return null; + } + + return buildEventsSearchUrl({ + source, + config: queriedConfig, + dateRange, + }); + }, [dateRange, queriedConfig, source]); + const hoverToolbar = useMemo(() => { const isRawSql = isRawSqlSavedChartConfig(chart.config); const isPromQL = isPromqlSavedChartConfig(chart.config); @@ -702,6 +727,25 @@ const Tile = forwardRef( key="hover-toolbar" my={2} // Margin to ensure that the Alert Indicator doesn't clip on non-Line/Bar display types > + {replaySearchUrl && ( + + + + + + )} + {displayTypeSupportsAlerts && (alert ? ( // Existing alert: bell with a colored status dot indicator. @@ -851,6 +895,7 @@ const Tile = forwardRef( alertIndicatorColor, alertTooltip, moveTargets, + replaySearchUrl, chart.config, chart.id, chart.containerId, @@ -877,6 +922,17 @@ const Tile = forwardRef( onMoveToGroup && moveTargets && moveTargets.length > 0; return ( <> + {replaySearchUrl && ( + } + > + Replay search + + )} {showAlerts && ( <> Date: Wed, 15 Jul 2026 16:29:19 +0300 Subject: [PATCH 2/2] fix(dashboards): restrict replay search eligibility --- .changeset/dashboard-replay-search.md | 2 +- packages/app/src/DBDashboardPage.tsx | 30 ++++++++++++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.changeset/dashboard-replay-search.md b/.changeset/dashboard-replay-search.md index 039bbbaab5..5b6324f2b1 100644 --- a/.changeset/dashboard-replay-search.md +++ b/.changeset/dashboard-replay-search.md @@ -2,4 +2,4 @@ "@hyperdx/app": patch --- -feat(dashboards): add a Replay search action to dashboard tiles backed by event-search-compatible chart configurations. The action opens a new Search tab with the tile's source, query, filters, and dashboard time range preserved. +feat(dashboards): add a Replay search action to log and trace dashboard tiles whose event query can be faithfully reconstructed. The action opens a new Search tab with the tile's source, query, filters, and dashboard time range preserved. diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index 6519634493..994301e75a 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -24,7 +24,6 @@ import { ErrorBoundary } from 'react-error-boundary'; import RGL, { WidthProvider } from 'react-grid-layout'; import { useForm, useWatch } from 'react-hook-form'; import { TableConnection } from '@hyperdx/common-utils/dist/core/metadata'; -import { isMetricChartConfig } from '@hyperdx/common-utils/dist/core/renderChartConfig'; import { convertToDashboardTemplate, displayTypeSupportsBuilderAlerts, @@ -50,6 +49,7 @@ import { Filter, getSampleWeightExpression, isLogSource, + isSearchableSource, isTraceSource, SearchCondition, SearchConditionLanguage, @@ -690,18 +690,28 @@ const Tile = forwardRef( return null; } - // Metric charts drill into their associated log source. Do not call the - // URL builder when that association is missing because it would surface - // a notification while rendering the dashboard. - if ( - isMetricChartConfig(queriedConfig) && - ((source.kind !== SourceKind.Metric && - source.kind !== SourceKind.Trace) || - !source.logSourceId) - ) { + if (queriedConfig.metricTables != null || !isSearchableSource(source)) { return null; } + if (Array.isArray(queriedConfig.select)) { + const hasPerSeriesCondition = queriedConfig.select.some( + select => + typeof select !== 'string' && + select.aggCondition != null && + select.aggCondition.trim().length > 0, + ); + const canPromoteSingleSeriesCondition = + queriedConfig.select.length === 1 && queriedConfig.where.length === 0; + + // buildEventsSearchUrl can promote one per-series condition into the + // event query, but cannot faithfully replay multiple conditions or + // combine a series condition with a global where clause. + if (hasPerSeriesCondition && !canPromoteSingleSeriesCondition) { + return null; + } + } + return buildEventsSearchUrl({ source, config: queriedConfig,