import { Unsubscribable } from 'rxjs'; import { SceneDataProvider, SceneDataState, SceneDataTransformer, SceneDeactivationHandler, SceneObject, SceneObjectBase, } from '@grafana/scenes'; import { DashboardQuery } from 'app/plugins/datasource/dashboard/types'; import { getVizPanelKeyForPanelId } from './utils'; export interface ShareQueryDataProviderState extends SceneDataState { query: DashboardQuery; } export class ShareQueryDataProvider extends SceneObjectBase implements SceneDataProvider { private _querySub: Unsubscribable | undefined; private _sourceDataDeactivationHandler?: SceneDeactivationHandler; constructor(state: ShareQueryDataProviderState) { super(state); this.addActivationHandler(() => { // TODO handle changes to query model (changed panelId / withTransforms) //this.subscribeToState(this._onStateChanged); this._subscribeToSource(); return () => { if (this._querySub) { this._querySub.unsubscribe(); } if (this._sourceDataDeactivationHandler) { this._sourceDataDeactivationHandler(); } }; }); } private _subscribeToSource() { const { query } = this.state; if (this._querySub) { this._querySub.unsubscribe(); } if (!query.panelId) { return; } const keyToFind = getVizPanelKeyForPanelId(query.panelId); const source = findObjectInScene(this.getRoot(), (scene: SceneObject) => scene.state.key === keyToFind); if (!source) { console.log('Shared dashboard query refers to a panel that does not exist in the scene'); return; } let sourceData = source.state.$data; if (!sourceData) { console.log('No source data found for shared dashboard query'); return; } // This will activate if sourceData is part of hidden panel // Also make sure the sourceData is not deactivated if hidden later this._sourceDataDeactivationHandler = sourceData.activate(); if (sourceData instanceof SceneDataTransformer) { if (!query.withTransforms) { if (!sourceData.state.$data) { throw new Error('No source inner query runner found in data transformer'); } sourceData = sourceData.state.$data; } } this._querySub = sourceData.subscribeToState((state) => this.setState({ data: state.data })); // Copy the initial state this.setState({ data: sourceData.state.data }); } } export function findObjectInScene(scene: SceneObject, check: (scene: SceneObject) => boolean): SceneObject | null { if (check(scene)) { return scene; } let found: SceneObject | null = null; scene.forEachChild((child) => { let maybe = findObjectInScene(child, check); if (maybe) { found = maybe; } }); return found; }