diff --git a/.changeset/dashboard-replay-search.md b/.changeset/dashboard-replay-search.md new file mode 100644 index 0000000000..5b6324f2b1 --- /dev/null +++ b/.changeset/dashboard-replay-search.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +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 bf5ba80740..994301e75a 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -49,6 +49,7 @@ import { Filter, getSampleWeightExpression, isLogSource, + isSearchableSource, isTraceSource, SearchCondition, SearchConditionLanguage, @@ -684,6 +685,40 @@ const Tile = forwardRef( ); }, [filters, queriedConfig, source]); + const replaySearchUrl = useMemo(() => { + if (!queriedConfig || !source || !isBuilderChartConfig(queriedConfig)) { + return null; + } + + 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, + dateRange, + }); + }, [dateRange, queriedConfig, source]); + const hoverToolbar = useMemo(() => { const isRawSql = isRawSqlSavedChartConfig(chart.config); const isPromQL = isPromqlSavedChartConfig(chart.config); @@ -702,6 +737,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 +905,7 @@ const Tile = forwardRef( alertIndicatorColor, alertTooltip, moveTargets, + replaySearchUrl, chart.config, chart.id, chart.containerId, @@ -877,6 +932,17 @@ const Tile = forwardRef( onMoveToGroup && moveTargets && moveTargets.length > 0; return ( <> + {replaySearchUrl && ( + } + > + Replay search + + )} {showAlerts && ( <>