mirror of https://github.com/grafana/grafana
Scenes: Add support for shared query results of other panel (#65413)
* Scene: Add support for shared query results of other panel * Update * Fixing dashboardpull/65324/head
parent
4ded937c79
commit
3af8f3246a
@ -0,0 +1,54 @@ |
||||
import { getDefaultTimeRange, LoadingState } from '@grafana/data'; |
||||
import { |
||||
SceneDataNode, |
||||
SceneFlexItem, |
||||
SceneFlexLayout, |
||||
sceneGraph, |
||||
SceneObjectBase, |
||||
SceneObjectState, |
||||
} from '@grafana/scenes'; |
||||
|
||||
import { ShareQueryDataProvider } from './ShareQueryDataProvider'; |
||||
import { activateFullSceneTree, getVizPanelKeyForPanelId } from './utils'; |
||||
|
||||
export class SceneDummyPanel extends SceneObjectBase<SceneObjectState> {} |
||||
|
||||
describe('ShareQueryDataProvider', () => { |
||||
it('Should find and subscribe to another VizPanels data provider', () => { |
||||
const panel = new SceneDummyPanel({ |
||||
key: getVizPanelKeyForPanelId(2), |
||||
$data: new ShareQueryDataProvider({ |
||||
query: { refId: 'A', panelId: 1 }, |
||||
}), |
||||
}); |
||||
|
||||
const sourceData = new SceneDataNode({ |
||||
data: { |
||||
series: [], |
||||
state: LoadingState.Done, |
||||
timeRange: getDefaultTimeRange(), |
||||
structureRev: 11, |
||||
}, |
||||
}); |
||||
|
||||
const scene = new SceneFlexLayout({ |
||||
children: [ |
||||
new SceneFlexItem({ |
||||
body: new SceneDummyPanel({ |
||||
key: getVizPanelKeyForPanelId(1), |
||||
$data: sourceData, |
||||
}), |
||||
}), |
||||
new SceneFlexItem({ body: panel }), |
||||
], |
||||
}); |
||||
|
||||
activateFullSceneTree(scene); |
||||
|
||||
expect(sceneGraph.getData(panel).state.data?.structureRev).toBe(11); |
||||
|
||||
sourceData.setState({ data: { ...sourceData.state.data!, structureRev: 12 } }); |
||||
|
||||
expect(sceneGraph.getData(panel).state.data?.structureRev).toBe(12); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,103 @@ |
||||
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<ShareQueryDataProviderState> implements SceneDataProvider { |
||||
private _querySub: Unsubscribable | undefined; |
||||
private _sourceDataDeactivationHandler?: SceneDeactivationHandler; |
||||
|
||||
public 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; |
||||
} |
||||
@ -0,0 +1,26 @@ |
||||
import { SceneDeactivationHandler, SceneObject } from '@grafana/scenes'; |
||||
|
||||
export function getVizPanelKeyForPanelId(panelId: number) { |
||||
return `panel-${panelId}`; |
||||
} |
||||
|
||||
/** |
||||
* Useful from tests to simulate mounting a full scene. Children are activated before parents to simulate the real order |
||||
* of React mount order and useEffect ordering. |
||||
* |
||||
*/ |
||||
export function activateFullSceneTree(scene: SceneObject): SceneDeactivationHandler { |
||||
const deactivationHandlers: SceneDeactivationHandler[] = []; |
||||
|
||||
scene.forEachChild((child) => { |
||||
deactivationHandlers.push(activateFullSceneTree(child)); |
||||
}); |
||||
|
||||
deactivationHandlers.push(scene.activate()); |
||||
|
||||
return () => { |
||||
for (const handler of deactivationHandlers) { |
||||
handler(); |
||||
} |
||||
}; |
||||
} |
||||
Loading…
Reference in new issue