From 8959951226f65bae9ce72f33ced961e85fa8fc78 Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Mon, 24 Nov 2025 16:58:44 -0500 Subject: [PATCH] feat(logs): Without chart data when table is empty This withholds the chart data when the table is empty. It happens when the user is searching for a log closer to the start of the query period. The chart query successfully finds the results, but because the table query is using the flex time strategy, it may not have scanned that point in time just yet. It will if the user clicks on `Continue Scanning` a few times but until the table finds the log, we have a weird state that is difficult to explain. To address this, we opted to withhold the chart data and make it look like the chart is still loading when the user clicks on `Continue Scanning`. And only when the table finds some results, will the chart finally render. --- .../views/explore/logs/confidenceFooter.tsx | 9 ++++++ static/app/views/explore/logs/logsGraph.tsx | 32 ++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/static/app/views/explore/logs/confidenceFooter.tsx b/static/app/views/explore/logs/confidenceFooter.tsx index e02da34d464467..e71f1fc09d06f9 100644 --- a/static/app/views/explore/logs/confidenceFooter.tsx +++ b/static/app/views/explore/logs/confidenceFooter.tsx @@ -16,6 +16,7 @@ interface ConfidenceFooterProps { hasUserQuery: boolean; isLoading: boolean; rawLogCounts: RawCounts; + disabled?: boolean; } export function ConfidenceFooter({ @@ -23,6 +24,7 @@ export function ConfidenceFooter({ hasUserQuery, isLoading, rawLogCounts, + disabled, }: ConfidenceFooterProps) { return ( @@ -35,6 +37,7 @@ export function ConfidenceFooter({ isSampled={chartInfo.isSampled} sampleCount={chartInfo.sampleCount} topEvents={chartInfo.topEvents} + disabled={disabled} /> ); @@ -46,6 +49,7 @@ interface ConfidenceMessageProps { rawLogCounts: RawCounts; confidence?: Confidence; dataScanned?: 'full' | 'partial'; + disabled?: boolean; isSampled?: boolean | null; sampleCount?: number; topEvents?: number; @@ -56,11 +60,16 @@ function ConfidenceMessage({ sampleCount, dataScanned, confidence: _confidence, + disabled, topEvents, hasUserQuery, isLoading, isSampled, }: ConfidenceMessageProps) { + if (disabled) { + return ; + } + if (isLoading || !defined(sampleCount)) { return ; } diff --git a/static/app/views/explore/logs/logsGraph.tsx b/static/app/views/explore/logs/logsGraph.tsx index 6de6db15a2fba3..cd5fdaf033229e 100644 --- a/static/app/views/explore/logs/logsGraph.tsx +++ b/static/app/views/explore/logs/logsGraph.tsx @@ -27,6 +27,8 @@ import { import {Widget} from 'sentry/views/dashboards/widgets/widget/widget'; import {handleAddQueryToDashboard} from 'sentry/views/discover/utils'; import {ChartVisualization} from 'sentry/views/explore/components/chart/chartVisualization'; +import type {ChartInfo} from 'sentry/views/explore/components/chart/types'; +import {useLogsPageDataQueryResult} from 'sentry/views/explore/contexts/logs/logsPageData'; import {formatSort} from 'sentry/views/explore/contexts/pageParamsContext/sortBys'; import { ChartIntervalUnspecifiedStrategy, @@ -119,6 +121,8 @@ function Graph({ timeseriesResult, visualize, }: GraphProps) { + const {isEmpty: tableIsEmpty, isPending: tableIsPending} = useLogsPageDataQueryResult(); + const aggregate = visualize.yAxis; const userQuery = useQueryParamsQuery(); const topEventsLimit = useQueryParamsTopEventsLimit(); @@ -127,14 +131,23 @@ function Graph({ unspecifiedStrategy: ChartIntervalUnspecifiedStrategy.USE_SMALLEST, }); - const chartInfo = useMemo(() => { - const series = timeseriesResult.data[aggregate] ?? []; + const chartInfo: ChartInfo = useMemo(() => { + // If the table is empty or pending, we want to withhold the chart data. + // This is to avoid a state where there is data in the chart but not in + // the table which is very weird. By withholding the chart data, we create + // the illusion the 2 are being queries in sync. + const withholdData = tableIsEmpty || tableIsPending; + + const series = withholdData ? [] : (timeseriesResult.data[aggregate] ?? []); const isTopEvents = defined(topEventsLimit); const samplingMeta = determineSeriesSampleCountAndIsSampled(series, isTopEvents); return { chartType: visualize.chartType, series, - timeseriesResult, + timeseriesResult: { + ...timeseriesResult, + isPending: timeseriesResult.isPending || tableIsPending, + } as ChartInfo['timeseriesResult'], yAxis: aggregate, confidence: combineConfidenceForSeries(series), dataScanned: samplingMeta.dataScanned, @@ -143,7 +156,14 @@ function Graph({ samplingMode: undefined, topEvents: isTopEvents ? TOP_EVENTS_LIMIT : undefined, }; - }, [visualize.chartType, timeseriesResult, aggregate, topEventsLimit]); + }, [ + visualize.chartType, + timeseriesResult, + aggregate, + topEventsLimit, + tableIsEmpty, + tableIsPending, + ]); const Title = ( @@ -204,9 +224,11 @@ function Graph({ visualize.visible && ( ) }