From 4ec220dc474ec6d0441ba27e3a770a10c1bf2d51 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 19 Dec 2025 16:04:53 -0500 Subject: [PATCH] Try this --- .../PanelDataTransformationsTab.tsx | 25 ++++++---------- .../panel-edit/PanelDataPane/utils.ts | 30 +++++++++++++++++++ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx index 425eb792109..1f70f648fed 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformationsTab.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import { DragDropContext, DropResult, Droppable } from '@hello-pangea/dnd'; import { useCallback, useMemo, useState } from 'react'; -import { DataTransformerConfig, GrafanaTheme2, PanelData } from '@grafana/data'; +import { DataTransformerConfig, getNextRefId, GrafanaTheme2, PanelData } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; import { @@ -25,9 +25,7 @@ import { PanelDataPane } from './PanelDataPane'; import { PanelDataQueriesTab } from './PanelDataQueriesTab'; import { TransformationsDrawer } from './TransformationsDrawer'; import { PanelDataPaneTab, TabId, PanelDataTabHeaderProps } from './types'; -import { scrollToQueryRow } from './utils'; - -const SET_TIMEOUT = 750; +import { scrollToQueryRowWhenReady } from './utils'; interface PanelDataTransformationsTabState extends SceneObjectState { panelRef: SceneObjectRef; @@ -100,23 +98,18 @@ export function PanelDataTransformationsTabRendered({ model }: SceneComponentPro return; } - // Always create a new SQL expression (it will be added to the end of the queries array) + // Calculate the refId that will be assigned to the new query before adding it + const currentQueries = queriesTab.getQueries(); + const newRefId = getNextRefId(currentQueries); + + // Create a new SQL expression (it will be added to the end of the queries array) queriesTab.onAddExpressionOfType(ExpressionQueryType.sql); // Navigate to the Queries tab parent.onChangeTab(queriesTab); - // Scroll to the newly created SQL query after tab renders - setTimeout(() => { - const queries = queriesTab.getQueries(); - // The newly added query is the last one in the array - if (queries.length > 0) { - const newQuery = queries[queries.length - 1]; - if (newQuery?.refId) { - scrollToQueryRow(newQuery.refId); - } - } - }, SET_TIMEOUT); + // Scroll to the newly created SQL query when it appears in the DOM + scrollToQueryRowWhenReady(newRefId); }, [model]); const onAddTransformation = useCallback( diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/utils.ts b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/utils.ts index b95a8076874..c4d44ee643c 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/utils.ts +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/utils.ts @@ -13,3 +13,33 @@ export function scrollToQueryRow(refId: string) { } } } + +/** + * Waits for a query row element to appear in the DOM, then scrolls to it. + * Uses MutationObserver for reliable detection instead of setTimeout. + * + * @param refId - The refId of the query row to scroll to + * @param timeoutMs - Maximum time to wait before giving up (default: 5000ms) + */ +export function scrollToQueryRowWhenReady(refId: string, timeoutMs = 5000): void { + // First check if the element already exists + const existingElement = document.querySelector(`[aria-controls^="${refId}_"]`); + if (existingElement) { + scrollToQueryRow(refId); + return; + } + + // Use MutationObserver to watch for the element to appear + const observer = new MutationObserver((_mutations, obs) => { + const queryRowHeader = document.querySelector(`[aria-controls^="${refId}_"]`); + if (queryRowHeader) { + obs.disconnect(); + scrollToQueryRow(refId); + } + }); + + observer.observe(document.body, { childList: true, subtree: true }); + + // Safety timeout to prevent indefinite observation + setTimeout(() => observer.disconnect(), timeoutMs); +}