From d3c673fca009d1fc7bb43cef907bfe61408ccd15 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Thu, 5 Mar 2026 17:28:05 +0100 Subject: [PATCH] SceneQueryController: Do not mark query as complete on PartialResult state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, registerQueryWithController called queryCompleted() on the first non-Loading emission, including LoadingState.Streaming and the new LoadingState.PartialResult. This caused SceneRenderProfiler to fire "All queries completed" as soon as the first intermediate chunk arrived from split queries (e.g. Loki's querySplitting), rather than waiting for the final Done. PartialResult (added in grafana/grafana#118796) is a new state used by Loki's querySplitting and shardQuerySplitting to signal that more data is still incoming and a final Done will follow. Unlike Streaming (which is used for live-forever tail queries), PartialResult always terminates. This change skips queryCompleted() when the state is PartialResult, so the profiler only records completion after the final Done arrives. Streaming is left unchanged — it still completes immediately, preserving existing behaviour for live tail queries. Co-Authored-By: Claude Sonnet 4.6 --- .../behaviors/SceneQueryController.test.ts | 35 +++++++++++++++++++ .../querying/registerQueryWithController.ts | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/scenes/src/behaviors/SceneQueryController.test.ts b/packages/scenes/src/behaviors/SceneQueryController.test.ts index 79d9f6f1e..f34cd8fd1 100644 --- a/packages/scenes/src/behaviors/SceneQueryController.test.ts +++ b/packages/scenes/src/behaviors/SceneQueryController.test.ts @@ -59,6 +59,41 @@ describe('SceneQueryController', () => { expect((window as any).__grafanaRunningQueryCount).toBe(0); }); + it('should NOT mark query as complete on PartialResult, only on Done', async () => { + const { query, streamFuncs } = registerQuery(scene); + let next = jest.fn(); + + query.subscribe({ next }); + + expect((window as any).__grafanaRunningQueryCount).toBe(1); + expect(controller.state.isRunning).toBe(true); + + // PartialResult emissions should not complete the query + streamFuncs.next({ state: LoadingState.PartialResult }); + expect((window as any).__grafanaRunningQueryCount).toBe(1); + expect(controller.state.isRunning).toBe(true); + + streamFuncs.next({ state: LoadingState.PartialResult }); + expect((window as any).__grafanaRunningQueryCount).toBe(1); + expect(controller.state.isRunning).toBe(true); + + // Final Done should complete it + streamFuncs.next({ state: LoadingState.Done }); + expect((window as any).__grafanaRunningQueryCount).toBe(0); + expect(controller.state.isRunning).toBe(false); + }); + + it('should still mark query as complete on Streaming state', async () => { + const { query, streamFuncs } = registerQuery(scene); + + query.subscribe(() => {}); + + expect(controller.state.isRunning).toBe(true); + + streamFuncs.next({ state: LoadingState.Streaming }); + expect(controller.state.isRunning).toBe(false); + }); + it('Last unsubscribe should set running to false', async () => { const { query: query1 } = registerQuery(scene); diff --git a/packages/scenes/src/querying/registerQueryWithController.ts b/packages/scenes/src/querying/registerQueryWithController.ts index 6fb4f3400..29851f9e6 100644 --- a/packages/scenes/src/querying/registerQueryWithController.ts +++ b/packages/scenes/src/querying/registerQueryWithController.ts @@ -69,7 +69,7 @@ export function registerQueryWithController( const sub = queryStream.subscribe({ next: (v) => { - if (!markedAsCompleted && v.state !== LoadingState.Loading) { + if (!markedAsCompleted && v.state !== LoadingState.Loading && v.state !== LoadingState.PartialResult) { markedAsCompleted = true; queryControler.queryCompleted(entry); endQueryCallback?.(performance.now()); // Success case - no error