From b7887605e733b4342d8b4c8b4becc440a2947a75 Mon Sep 17 00:00:00 2001 From: Andreas Gerstmayr Date: Wed, 15 Oct 2025 13:08:45 +0200 Subject: [PATCH] NO-JIRA: Do not fetch filter bar values on every re-render, and prevent flicker Signed-off-by: Andreas Gerstmayr --- web/src/hooks/useTagValues.ts | 5 ++++ web/src/pages/TracesPage/QueryBrowser.tsx | 17 ++++++------ .../TracesPage/Toolbar/AttributeFilters.tsx | 26 +++++++++---------- .../TracesPage/Toolbar/FilterToolbar.tsx | 6 ++--- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/web/src/hooks/useTagValues.ts b/web/src/hooks/useTagValues.ts index a7b264f..8b35586 100644 --- a/web/src/hooks/useTagValues.ts +++ b/web/src/hooks/useTagValues.ts @@ -26,5 +26,10 @@ export function useTagValues( .sort((a, b) => a.value.localeCompare(b.value)); }, staleTime: 60 * 1000, // cache tag value response for 1m + // Keep the previous query result during loading. + // Without this setting, the select boxes in the filter bar flicker on every selection, + // because the select box data goes from list of values -> undefined (during loading state) -> list of values. + // The select box values are refreshed because the absolute time range changes. + keepPreviousData: true, }); } diff --git a/web/src/pages/TracesPage/QueryBrowser.tsx b/web/src/pages/TracesPage/QueryBrowser.tsx index f668c15..9ac16c1 100644 --- a/web/src/pages/TracesPage/QueryBrowser.tsx +++ b/web/src/pages/TracesPage/QueryBrowser.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import { Divider, PageSection, Split, SplitItem, Stack, Title } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; import { TimeRangeSelect } from '../../components/TimeRangeSelect'; @@ -31,11 +31,18 @@ export function QueryBrowserBody() { const [query, setQuery] = useQueryParam('q', withDefault(StringParam, '{}')); const [limit, setLimit] = useQueryParam('limit', withDefault(NumberParam, DEFAULT_LIMIT)); const { refresh } = useTimeRange(); + const isInitialRender = useRef(true); // Refresh query if Tempo instance or tenant changes. // The Perses data source is selected via a mock DatasourceApiImpl implementation in , // therefore Perses doesn't refresh automatically if the Tempo instance changes. useEffect(() => { + // Only call refresh() if the Tempo instance changed, not on the initial render of this component. + if (isInitialRender.current) { + isInitialRender.current = false; + return; + } + refresh(); }, [tempo, refresh]); @@ -61,13 +68,7 @@ export function QueryBrowserBody() { - + void; + runQuery: (query: string) => void; } const statusOptions = [ @@ -59,44 +59,42 @@ const statusOptions = [ export function AttributeFilters(props: AttributeFiltersProps) { const { t } = useTranslation('plugin__distributed-tracing-console-plugin'); - const { tempo, query, setQuery } = props; + const { tempo, query, runQuery } = props; const [activeFilter, setActiveFilter] = useState( attributeFilterOptions[0].value, ); const filter = traceQLToFilter(query); const setFilter = (filter: Filter) => { - setQuery(filterToTraceQL(filter)); + runQuery(filterToTraceQL(filter)); }; - const { timeRange } = useTimeRange(); - const absTimeRange = !isAbsoluteTimeRange(timeRange) ? toAbsoluteTimeRange(timeRange) : timeRange; - const startTime = Math.round(absTimeRange.start.getTime() / 1000); - const endTime = Math.round(absTimeRange.end.getTime() / 1000); + const { absoluteTimeRange } = useTimeRange(); + const { start, end } = getUnixTimeRange(absoluteTimeRange); const { data: serviceNameOptions } = useTagValues( tempo, 'resource.service.name', filterToTraceQL({ ...filter, serviceName: [] }), activeFilter === serviceNameFilter.value, - startTime, - endTime, + start, + end, ); const { data: spanNameOptions } = useTagValues( tempo, 'name', filterToTraceQL({ ...filter, spanName: [] }), activeFilter === spanNameFilter.value, - startTime, - endTime, + start, + end, ); const { data: namespaceOptions } = useTagValues( tempo, 'resource.k8s.namespace.name', filterToTraceQL({ ...filter, namespace: [] }), activeFilter === namespaceFilter.value, - startTime, - endTime, + start, + end, ); return ( diff --git a/web/src/pages/TracesPage/Toolbar/FilterToolbar.tsx b/web/src/pages/TracesPage/Toolbar/FilterToolbar.tsx index 4d7a63c..5d677e3 100644 --- a/web/src/pages/TracesPage/Toolbar/FilterToolbar.tsx +++ b/web/src/pages/TracesPage/Toolbar/FilterToolbar.tsx @@ -22,15 +22,13 @@ interface FilterToolbarProps { tempo: TempoInstance | undefined; setTempo: (tempo?: TempoInstance) => void; query: string; - /** Update the query. Fetches the search results only if the query changed. */ - setQuery: (query: string) => void; /** Update and execute the query. Always fetches the search results. */ runQuery: (query: string) => void; } export function FilterToolbar(props: FilterToolbarProps) { const { t } = useTranslation('plugin__distributed-tracing-console-plugin'); - const { tempo, setTempo, query, setQuery, runQuery } = props; + const { tempo, setTempo, query, runQuery } = props; const [showAttributeFilters, setShowAttributeFilters] = useState(isSimpleTraceQLQuery(query)); return ( @@ -39,7 +37,7 @@ export function FilterToolbar(props: FilterToolbarProps) { } breakpoint="xl"> {showAttributeFilters ? ( - + ) : ( )}