diff --git a/packages/scenes-react/src/hooks/useQueryRunner.ts b/packages/scenes-react/src/hooks/useQueryRunner.ts index f2069d673..0d89e7ca6 100644 --- a/packages/scenes-react/src/hooks/useQueryRunner.ts +++ b/packages/scenes-react/src/hooks/useQueryRunner.ts @@ -1,10 +1,9 @@ import { useEffect } from 'react'; -import { SceneDataQuery, SceneQueryRunner } from '@grafana/scenes'; +import { SceneDataQuery, SceneQueryRunner, SceneTimeRangeLike } from '@grafana/scenes'; import { DataSourceRef } from '@grafana/schema'; import { isEqual } from 'lodash'; import { CacheKey } from '../caching/SceneObjectCache'; import { useSceneObject } from './useSceneObject'; - export interface UseQueryOptions { queries: SceneDataQuery[]; maxDataPoints?: number; @@ -12,6 +11,9 @@ export interface UseQueryOptions { cacheKey?: CacheKey; liveStreaming?: boolean; maxDataPointsFromWidth?: boolean; + // Optional timeRange to use instead of sceneGraph timeRange + runQueriesMode?: 'auto' | 'manual'; + timeRange?: SceneTimeRangeLike; } /** @@ -31,6 +33,8 @@ export function useQueryRunner(options: UseQueryOptions): SceneQueryRunner { datasource: options.datasource, liveStreaming: options.liveStreaming, maxDataPointsFromWidth: options.maxDataPointsFromWidth, + runQueriesMode: options.runQueriesMode, + timeRange: options.timeRange, }), objectConstructor: SceneQueryRunner, cacheKey: options.cacheKey, diff --git a/packages/scenes/src/querying/SceneQueryRunner.test.ts b/packages/scenes/src/querying/SceneQueryRunner.test.ts index 4ced41794..3338d5a0d 100644 --- a/packages/scenes/src/querying/SceneQueryRunner.test.ts +++ b/packages/scenes/src/querying/SceneQueryRunner.test.ts @@ -2754,6 +2754,97 @@ describe.each(['11.1.2', '11.1.1'])('SceneQueryRunner', (v) => { expect(sentRequest?.scopes).toBeUndefined(); }); + + describe('when timeRange is provided as prop', () => { + it('should use provided timeRange instead of sceneGraph timeRange', async () => { + const customTimeRange = new SceneTimeRange({ + from: '2023-02-01', + to: '2023-02-02', + }); + + const queryRunner = new SceneQueryRunner({ + queries: [{ refId: 'A' }], + timeRange: customTimeRange, + }); + + const scene = new TestScene({ + $timeRange: new SceneTimeRange({ + from: '2023-03-01', + to: '2023-03-02', + }), + $data: queryRunner, + }); + + scene.activate(); + queryRunner.activate(); + + await new Promise((r) => setTimeout(r, 1)); + + expect(sentRequest?.range).toEqual(customTimeRange.state.value); + }); + + it('should not subscribe to sceneGraph timeRange changes when using prop timeRange', async () => { + const customTimeRange = new SceneTimeRange({ + from: '2023-01-01', + to: '2023-01-02', + }); + + const queryRunner = new SceneQueryRunner({ + queries: [{ refId: 'A' }], + timeRange: customTimeRange, + }); + + const sceneTimeRange = new SceneTimeRange({ + from: '2023-02-01', + to: '2023-02-02', + }); + + const scene = new TestScene({ + $timeRange: sceneTimeRange, + $data: queryRunner, + }); + + scene.activate(); + queryRunner.activate(); + + await new Promise((r) => setTimeout(r, 1)); + + // Change scene time range + sceneTimeRange.setState({ + from: '2023-03-01', + to: '2023-03-02', + }); + sceneTimeRange.onRefresh(); + + await new Promise((r) => setTimeout(r, 1)); + + // Should still use custom time range + expect(sentRequest?.range).toEqual(customTimeRange.state.value); + }); + + it('should fall back to sceneGraph timeRange when prop timeRange is not provided', async () => { + const sceneTimeRange = new SceneTimeRange({ + from: '2023-01-01', + to: '2023-01-02', + }); + + const queryRunner = new SceneQueryRunner({ + queries: [{ refId: 'A' }], + }); + + const scene = new TestScene({ + $timeRange: sceneTimeRange, + $data: queryRunner, + }); + + scene.activate(); + queryRunner.activate(); + + await new Promise((r) => setTimeout(r, 1)); + + expect(sentRequest?.range).toEqual(sceneTimeRange.state.value); + }); + }); }); class CustomDataSource extends RuntimeDataSource { diff --git a/packages/scenes/src/querying/SceneQueryRunner.ts b/packages/scenes/src/querying/SceneQueryRunner.ts index 62a685767..ad8c6c33a 100644 --- a/packages/scenes/src/querying/SceneQueryRunner.ts +++ b/packages/scenes/src/querying/SceneQueryRunner.ts @@ -75,6 +75,8 @@ export interface QueryRunnerState extends SceneObjectState { dataLayerFilter?: DataLayerFilter; // Private runtime state _hasFetchedData?: boolean; + // Optional timeRange to use instead of sceneGraph timeRange + timeRange?: SceneTimeRangeLike; } export interface DataQueryExtended extends DataQuery { @@ -140,9 +142,14 @@ export class SceneQueryRunner extends SceneObjectBase implemen this.addActivationHandler(() => this._onActivate()); } + private getTimeRange(): SceneTimeRangeLike { + // If timeRange is provided in state, use that, otherwise fall back to sceneGraph + return this.state.timeRange ?? sceneGraph.getTimeRange(this); + } + private _onActivate() { if (this.isQueryModeAuto()) { - const timeRange = sceneGraph.getTimeRange(this); + const timeRange = this.getTimeRange(); const scopesBridge = sceneGraph.getScopesBridge(this); // Add subscriptions to any extra providers so that they rerun queries @@ -188,7 +195,7 @@ export class SceneQueryRunner extends SceneObjectBase implemen } private _onLayersReceived(results: Iterable) { - const timeRange = sceneGraph.getTimeRange(this); + const timeRange = this.getTimeRange(); const { dataLayerFilter } = this.state; let annotations: DataFrame[] = []; @@ -312,7 +319,7 @@ export class SceneQueryRunner extends SceneObjectBase implemen } private _isDataTimeRangeStale(data: PanelData) { - const timeRange = sceneGraph.getTimeRange(this); + const timeRange = this.getTimeRange(); const stateTimeRange = timeRange.state.value; const dataTimeRange = data.timeRange; @@ -390,13 +397,17 @@ export class SceneQueryRunner extends SceneObjectBase implemen this._scopesSubBridge = scopesBridge; this._scopesSub = scopesBridge.subscribeToValue(() => { - this.runWithTimeRangeAndScopes(sceneGraph.getTimeRange(this), scopesBridge); + this.runWithTimeRangeAndScopes(this.getTimeRange(), scopesBridge); }); } private subscribeToTimeRangeChanges(timeRange: SceneTimeRangeLike) { + // Only subscribe to time range changes if we're not using a prop + if (this.state.timeRange) { + return; + } + if (this._timeSubRange === timeRange) { - // Nothing to do, already subscribed return; } @@ -411,7 +422,7 @@ export class SceneQueryRunner extends SceneObjectBase implemen } public runQueries() { - const timeRange = sceneGraph.getTimeRange(this); + const timeRange = this.getTimeRange(); const scopesBridge = sceneGraph.getScopesBridge(this); if (this.isQueryModeAuto()) { this.subscribeToTimeRangeChanges(timeRange);