mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardDatasource: reuse query results within a dashboard (#16660)
* move queryRunner to panelModel * remove isEditing from PanelChrome * move listener to QueriesTab * add shared query datasource * expose getDashboardSrv to react * no changes to panel chrome * issue queries when in fullscreen * moved to regular QueryEditor interface * moved to regular QueryEditor interface * lower limit * add dashboard query * no changes to editor row * fix sort order * fix sort order * make it an alpha panel * make panelId a getter * fix angular constructor * rename SeriesData to DataFrame * merge with master * use series * add simple tests * check unsubscribe * Minor code cleanup, creating Subjects look cheap and does not need to be lazy, simplifies code * minor refactor * Minor refacforing, renames * added test dashboard
This commit is contained in:
parent
8ce509f3b4
commit
e1924608a2
322
devenv/dev-dashboards/panel-common/shared_queries.json
Normal file
322
devenv/dev-dashboards/panel-common/shared_queries.json
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": "-- Grafana --",
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"gnetId": null,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"links": [],
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"aliasColors": {},
|
||||||
|
"bars": false,
|
||||||
|
"dashLength": 10,
|
||||||
|
"dashes": false,
|
||||||
|
"fill": 0,
|
||||||
|
"fillGradient": 6,
|
||||||
|
"gridPos": {
|
||||||
|
"h": 15,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"legend": {
|
||||||
|
"avg": false,
|
||||||
|
"current": false,
|
||||||
|
"max": false,
|
||||||
|
"min": false,
|
||||||
|
"show": true,
|
||||||
|
"total": false,
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"lines": true,
|
||||||
|
"linewidth": 1,
|
||||||
|
"nullPointMode": "null",
|
||||||
|
"options": {
|
||||||
|
"dataLinks": []
|
||||||
|
},
|
||||||
|
"percentage": false,
|
||||||
|
"pointradius": 2,
|
||||||
|
"points": true,
|
||||||
|
"renderer": "flot",
|
||||||
|
"seriesOverrides": [],
|
||||||
|
"spaceLength": 10,
|
||||||
|
"stack": false,
|
||||||
|
"steppedLine": false,
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "csv_metric_values",
|
||||||
|
"stringInput": "1,20,90,30,5,0,100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "B",
|
||||||
|
"scenarioId": "csv_metric_values",
|
||||||
|
"stringInput": "1,20,90,30,5,-100,200"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "C",
|
||||||
|
"scenarioId": "csv_metric_values",
|
||||||
|
"stringInput": "2.5,3.5,4.5,10.5,20.5,21.5,19.5"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"thresholds": [],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeRegions": [],
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Raw Data Graph",
|
||||||
|
"tooltip": {
|
||||||
|
"shared": true,
|
||||||
|
"sort": 0,
|
||||||
|
"value_type": "individual"
|
||||||
|
},
|
||||||
|
"type": "graph",
|
||||||
|
"xaxis": {
|
||||||
|
"buckets": null,
|
||||||
|
"mode": "time",
|
||||||
|
"name": null,
|
||||||
|
"show": true,
|
||||||
|
"values": []
|
||||||
|
},
|
||||||
|
"yaxes": [
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "short",
|
||||||
|
"label": null,
|
||||||
|
"logBase": 1,
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"yaxis": {
|
||||||
|
"align": false,
|
||||||
|
"alignLevel": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "-- Dashboard --",
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 4,
|
||||||
|
"options": {
|
||||||
|
"fieldOptions": {
|
||||||
|
"calcs": ["lastNotNull"],
|
||||||
|
"defaults": {
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"override": {},
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"orientation": "auto",
|
||||||
|
"showThresholdLabels": false,
|
||||||
|
"showThresholdMarkers": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "6.4.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"panelId": 2,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Last non nulll",
|
||||||
|
"type": "gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "-- Dashboard --",
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 5
|
||||||
|
},
|
||||||
|
"id": 6,
|
||||||
|
"options": {
|
||||||
|
"fieldOptions": {
|
||||||
|
"calcs": ["min"],
|
||||||
|
"defaults": {
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"override": {},
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"orientation": "auto",
|
||||||
|
"showThresholdLabels": false,
|
||||||
|
"showThresholdMarkers": true
|
||||||
|
},
|
||||||
|
"pluginVersion": "6.4.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"panelId": 2,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "min",
|
||||||
|
"type": "gauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "-- Dashboard --",
|
||||||
|
"gridPos": {
|
||||||
|
"h": 5,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 10
|
||||||
|
},
|
||||||
|
"id": 5,
|
||||||
|
"options": {
|
||||||
|
"displayMode": "basic",
|
||||||
|
"fieldOptions": {
|
||||||
|
"calcs": ["max"],
|
||||||
|
"defaults": {
|
||||||
|
"mappings": [],
|
||||||
|
"max": 200,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "blue",
|
||||||
|
"value": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 120
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"override": {},
|
||||||
|
"values": false
|
||||||
|
},
|
||||||
|
"orientation": "vertical"
|
||||||
|
},
|
||||||
|
"pluginVersion": "6.4.0-pre",
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"panelId": 2,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Max",
|
||||||
|
"type": "bargauge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": [],
|
||||||
|
"datasource": "-- Dashboard --",
|
||||||
|
"fontSize": "100%",
|
||||||
|
"gridPos": {
|
||||||
|
"h": 10,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 15
|
||||||
|
},
|
||||||
|
"id": 8,
|
||||||
|
"options": {},
|
||||||
|
"pageSize": null,
|
||||||
|
"showHeader": true,
|
||||||
|
"sort": {
|
||||||
|
"col": 0,
|
||||||
|
"desc": true
|
||||||
|
},
|
||||||
|
"styles": [
|
||||||
|
{
|
||||||
|
"alias": "Time",
|
||||||
|
"dateFormat": "YYYY-MM-DD HH:mm:ss",
|
||||||
|
"pattern": "Time",
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": "",
|
||||||
|
"colorMode": null,
|
||||||
|
"colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
|
||||||
|
"decimals": 2,
|
||||||
|
"pattern": "/.*/",
|
||||||
|
"thresholds": [],
|
||||||
|
"type": "number",
|
||||||
|
"unit": "short"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"panelId": 2,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Panel Title",
|
||||||
|
"transform": "timeseries_to_columns",
|
||||||
|
"type": "table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schemaVersion": 19,
|
||||||
|
"style": "dark",
|
||||||
|
"tags": ["gdev", "datasource-test"],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-6h",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {
|
||||||
|
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
|
||||||
|
},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Datasource tests - Shared Queries",
|
||||||
|
"uid": "ZqZnVvFZz",
|
||||||
|
"version": 10
|
||||||
|
}
|
@ -23,6 +23,8 @@ import { LoadingState } from '@grafana/data';
|
|||||||
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
|
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
|
||||||
import { PanelQueryRunnerFormat } from '../state/PanelQueryRunner';
|
import { PanelQueryRunnerFormat } from '../state/PanelQueryRunner';
|
||||||
import { Unsubscribable } from 'rxjs';
|
import { Unsubscribable } from 'rxjs';
|
||||||
|
import { isSharedDashboardQuery } from 'app/plugins/datasource/dashboard/SharedQueryRunner';
|
||||||
|
import { DashboardQueryEditor } from 'app/plugins/datasource/dashboard/DashboardQueryEditor';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
@ -166,12 +168,13 @@ export class QueriesTab extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
renderToolbar = () => {
|
renderToolbar = () => {
|
||||||
const { currentDS, isAddingMixed } = this.state;
|
const { currentDS, isAddingMixed } = this.state;
|
||||||
|
const showAddButton = !(isAddingMixed || isSharedDashboardQuery(currentDS.name));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />
|
<DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />
|
||||||
<div className="flex-grow-1" />
|
<div className="flex-grow-1" />
|
||||||
{!isAddingMixed && (
|
{showAddButton && (
|
||||||
<button className="btn navbar-button" onClick={this.onAddQueryClick}>
|
<button className="btn navbar-button" onClick={this.onAddQueryClick}>
|
||||||
Add Query
|
Add Query
|
||||||
</button>
|
</button>
|
||||||
@ -236,28 +239,32 @@ export class QueriesTab extends PureComponent<Props, State> {
|
|||||||
setScrollTop={this.setScrollTop}
|
setScrollTop={this.setScrollTop}
|
||||||
scrollTop={scrollTop}
|
scrollTop={scrollTop}
|
||||||
>
|
>
|
||||||
<>
|
{isSharedDashboardQuery(currentDS.name) ? (
|
||||||
<div className="query-editor-rows">
|
<DashboardQueryEditor panel={panel} panelData={data} onChange={query => this.onQueryChange(query, 0)} />
|
||||||
{panel.targets.map((query, index) => (
|
) : (
|
||||||
<QueryEditorRow
|
<>
|
||||||
dataSourceValue={query.datasource || panel.datasource}
|
<div className="query-editor-rows">
|
||||||
key={query.refId}
|
{panel.targets.map((query, index) => (
|
||||||
panel={panel}
|
<QueryEditorRow
|
||||||
dashboard={dashboard}
|
dataSourceValue={query.datasource || panel.datasource}
|
||||||
data={data}
|
key={query.refId}
|
||||||
query={query}
|
panel={panel}
|
||||||
onChange={query => this.onQueryChange(query, index)}
|
dashboard={dashboard}
|
||||||
onRemoveQuery={this.onRemoveQuery}
|
data={data}
|
||||||
onAddQuery={this.onAddQuery}
|
query={query}
|
||||||
onMoveQuery={this.onMoveQuery}
|
onChange={query => this.onQueryChange(query, index)}
|
||||||
inMixedMode={currentDS.meta.mixed}
|
onRemoveQuery={this.onRemoveQuery}
|
||||||
/>
|
onAddQuery={this.onAddQuery}
|
||||||
))}
|
onMoveQuery={this.onMoveQuery}
|
||||||
</div>
|
inMixedMode={currentDS.meta.mixed}
|
||||||
<PanelOptionsGroup>
|
/>
|
||||||
<QueryOptions panel={panel} datasource={currentDS} />
|
))}
|
||||||
</PanelOptionsGroup>
|
</div>
|
||||||
</>
|
<PanelOptionsGroup>
|
||||||
|
<QueryOptions panel={panel} datasource={currentDS} />
|
||||||
|
</PanelOptionsGroup>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</EditorTabBody>
|
</EditorTabBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -326,7 +326,7 @@ export class PanelModel {
|
|||||||
|
|
||||||
getQueryRunner(): PanelQueryRunner {
|
getQueryRunner(): PanelQueryRunner {
|
||||||
if (!this.queryRunner) {
|
if (!this.queryRunner) {
|
||||||
this.queryRunner = new PanelQueryRunner();
|
this.queryRunner = new PanelQueryRunner(this.id);
|
||||||
}
|
}
|
||||||
return this.queryRunner;
|
return this.queryRunner;
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,47 @@
|
|||||||
import { PanelQueryRunner } from './PanelQueryRunner';
|
import { PanelQueryRunner, QueryRunnerOptions } from './PanelQueryRunner';
|
||||||
import { PanelData, DataQueryRequest, DataStreamObserver, DataStreamState, ScopedVars } from '@grafana/ui';
|
import { PanelData, DataQueryRequest, DataStreamObserver, DataStreamState, ScopedVars } from '@grafana/ui';
|
||||||
|
|
||||||
import { LoadingState, DataFrameHelper } from '@grafana/data';
|
import { LoadingState, DataFrameHelper } from '@grafana/data';
|
||||||
import { dateTime } from '@grafana/data';
|
import { dateTime } from '@grafana/data';
|
||||||
|
import { SHARED_DASHBODARD_QUERY } from 'app/plugins/datasource/dashboard/SharedQueryRunner';
|
||||||
|
import { DashboardQuery } from 'app/plugins/datasource/dashboard/types';
|
||||||
|
import { PanelModel } from './PanelModel';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
jest.mock('app/core/services/backend_srv');
|
jest.mock('app/core/services/backend_srv');
|
||||||
|
|
||||||
|
// Defined within setup functions
|
||||||
|
const panelsForCurrentDashboardMock: { [key: number]: PanelModel } = {};
|
||||||
|
jest.mock('app/features/dashboard/services/DashboardSrv', () => ({
|
||||||
|
getDashboardSrv: () => {
|
||||||
|
return {
|
||||||
|
getCurrent: () => {
|
||||||
|
return {
|
||||||
|
getPanelById: (id: number) => {
|
||||||
|
return panelsForCurrentDashboardMock[id];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
interface ScenarioContext {
|
interface ScenarioContext {
|
||||||
setup: (fn: () => void) => void;
|
setup: (fn: () => void) => void;
|
||||||
|
|
||||||
|
// Options used in setup
|
||||||
maxDataPoints?: number | null;
|
maxDataPoints?: number | null;
|
||||||
widthPixels: number;
|
widthPixels: number;
|
||||||
dsInterval?: string;
|
dsInterval?: string;
|
||||||
minInterval?: string;
|
minInterval?: string;
|
||||||
|
scopedVars: ScopedVars;
|
||||||
|
|
||||||
|
// Filled in by the Scenario runner
|
||||||
events?: PanelData[];
|
events?: PanelData[];
|
||||||
res?: PanelData;
|
res?: PanelData;
|
||||||
queryCalledWith?: DataQueryRequest;
|
queryCalledWith?: DataQueryRequest;
|
||||||
observer: DataStreamObserver;
|
observer: DataStreamObserver;
|
||||||
runner: PanelQueryRunner;
|
runner: PanelQueryRunner;
|
||||||
scopedVars: ScopedVars;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScenarioFn = (ctx: ScenarioContext) => void;
|
type ScenarioFn = (ctx: ScenarioContext) => void;
|
||||||
@ -31,7 +55,7 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
|
|||||||
scopedVars: {
|
scopedVars: {
|
||||||
server: { text: 'Server1', value: 'server-1' },
|
server: { text: 'Server1', value: 'server-1' },
|
||||||
},
|
},
|
||||||
runner: new PanelQueryRunner(),
|
runner: new PanelQueryRunner(1),
|
||||||
observer: (args: any) => {},
|
observer: (args: any) => {},
|
||||||
setup: (fn: () => void) => {
|
setup: (fn: () => void) => {
|
||||||
setupFn = fn;
|
setupFn = fn;
|
||||||
@ -39,7 +63,7 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
|
|||||||
};
|
};
|
||||||
|
|
||||||
const response: any = {
|
const response: any = {
|
||||||
data: [{ target: 'hello', datapoints: [] }],
|
data: [{ target: 'hello', datapoints: [[1, 1000], [2, 2000]] }],
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -67,17 +91,24 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
|
|||||||
to: dateTime(),
|
to: dateTime(),
|
||||||
raw: { from: '1h', to: 'now' },
|
raw: { from: '1h', to: 'now' },
|
||||||
},
|
},
|
||||||
panelId: 0,
|
panelId: 1,
|
||||||
queries: [{ refId: 'A', test: 1 }],
|
queries: [{ refId: 'A', test: 1 }],
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.runner = new PanelQueryRunner();
|
ctx.runner = new PanelQueryRunner(1);
|
||||||
ctx.runner.subscribe({
|
ctx.runner.subscribe({
|
||||||
next: (data: PanelData) => {
|
next: (data: PanelData) => {
|
||||||
ctx.events.push(data);
|
ctx.events.push(data);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
panelsForCurrentDashboardMock[1] = {
|
||||||
|
id: 1,
|
||||||
|
getQueryRunner: () => {
|
||||||
|
return ctx.runner;
|
||||||
|
},
|
||||||
|
} as PanelModel;
|
||||||
|
|
||||||
ctx.events = [];
|
ctx.events = [];
|
||||||
ctx.res = await ctx.runner.run(args);
|
ctx.res = await ctx.runner.run(args);
|
||||||
});
|
});
|
||||||
@ -201,4 +232,60 @@ describe('PanelQueryRunner', () => {
|
|||||||
expect(isUnsubbed).toBe(true);
|
expect(isUnsubbed).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describeQueryRunnerScenario('Shared query request', ctx => {
|
||||||
|
ctx.setup(() => {});
|
||||||
|
|
||||||
|
it('should get the same results as the original', async () => {
|
||||||
|
// Get the results from
|
||||||
|
const q: DashboardQuery = { refId: 'Z', panelId: 1 };
|
||||||
|
const myPanelId = 7;
|
||||||
|
|
||||||
|
const runnerWantingSharedResults = new PanelQueryRunner(myPanelId);
|
||||||
|
panelsForCurrentDashboardMock[myPanelId] = {
|
||||||
|
id: myPanelId,
|
||||||
|
getQueryRunner: () => {
|
||||||
|
return runnerWantingSharedResults;
|
||||||
|
},
|
||||||
|
} as PanelModel;
|
||||||
|
|
||||||
|
const res = await runnerWantingSharedResults.run({
|
||||||
|
datasource: SHARED_DASHBODARD_QUERY,
|
||||||
|
queries: [q],
|
||||||
|
|
||||||
|
// Same query setup
|
||||||
|
scopedVars: ctx.scopedVars,
|
||||||
|
minInterval: ctx.minInterval,
|
||||||
|
widthPixels: ctx.widthPixels,
|
||||||
|
maxDataPoints: ctx.maxDataPoints,
|
||||||
|
timeRange: {
|
||||||
|
from: dateTime().subtract(1, 'days'),
|
||||||
|
to: dateTime(),
|
||||||
|
raw: { from: '1h', to: 'now' },
|
||||||
|
},
|
||||||
|
panelId: myPanelId, // Not 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = res.request;
|
||||||
|
expect(req.panelId).toBe(1); // The source panel
|
||||||
|
expect(req.targets[0].datasource).toBe('TestDB');
|
||||||
|
expect(res.series.length).toBe(1);
|
||||||
|
expect(res.series[0].length).toBe(2);
|
||||||
|
|
||||||
|
// Get the private subject and check that someone is listening
|
||||||
|
const subject = (ctx.runner as any).subject as Subject<PanelData>;
|
||||||
|
expect(subject.observers.length).toBe(2);
|
||||||
|
|
||||||
|
// Now change the query and we should stop listening
|
||||||
|
try {
|
||||||
|
runnerWantingSharedResults.run({
|
||||||
|
datasource: 'unknown-datasource',
|
||||||
|
panelId: myPanelId, // Not 1
|
||||||
|
} as QueryRunnerOptions);
|
||||||
|
} catch {}
|
||||||
|
// runnerWantingSharedResults subject is now unsubscribed
|
||||||
|
// the test listener is still subscribed
|
||||||
|
expect(subject.observers.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,7 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
|||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import templateSrv from 'app/features/templating/template_srv';
|
import templateSrv from 'app/features/templating/template_srv';
|
||||||
import { PanelQueryState } from './PanelQueryState';
|
import { PanelQueryState } from './PanelQueryState';
|
||||||
|
import { isSharedDashboardQuery, SharedQueryRunner } from 'app/plugins/datasource/dashboard/SharedQueryRunner';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { PanelData, DataQuery, ScopedVars, DataQueryRequest, DataSourceApi, DataSourceJsonData } from '@grafana/ui';
|
import { PanelData, DataQuery, ScopedVars, DataQueryRequest, DataSourceApi, DataSourceJsonData } from '@grafana/ui';
|
||||||
@ -49,8 +50,16 @@ export class PanelQueryRunner {
|
|||||||
|
|
||||||
private state = new PanelQueryState();
|
private state = new PanelQueryState();
|
||||||
|
|
||||||
constructor() {
|
// Listen to another panel for changes
|
||||||
|
private sharedQueryRunner: SharedQueryRunner;
|
||||||
|
|
||||||
|
constructor(private panelId: number) {
|
||||||
this.state.onStreamingDataUpdated = this.onStreamingDataUpdated;
|
this.state.onStreamingDataUpdated = this.onStreamingDataUpdated;
|
||||||
|
this.subject = new Subject();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPanelId() {
|
||||||
|
return this.panelId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,10 +67,6 @@ export class PanelQueryRunner {
|
|||||||
* the results will be immediatly passed to the observer
|
* the results will be immediatly passed to the observer
|
||||||
*/
|
*/
|
||||||
subscribe(observer: PartialObserver<PanelData>, format = PanelQueryRunnerFormat.frames): Unsubscribable {
|
subscribe(observer: PartialObserver<PanelData>, format = PanelQueryRunnerFormat.frames): Unsubscribable {
|
||||||
if (!this.subject) {
|
|
||||||
this.subject = new Subject(); // Delay creating a subject until someone is listening
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format === PanelQueryRunnerFormat.legacy) {
|
if (format === PanelQueryRunnerFormat.legacy) {
|
||||||
this.state.sendLegacy = true;
|
this.state.sendLegacy = true;
|
||||||
} else if (format === PanelQueryRunnerFormat.both) {
|
} else if (format === PanelQueryRunnerFormat.both) {
|
||||||
@ -79,11 +84,25 @@ export class PanelQueryRunner {
|
|||||||
return this.subject.subscribe(observer);
|
return this.subject.subscribe(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(options: QueryRunnerOptions): Promise<PanelData> {
|
/**
|
||||||
if (!this.subject) {
|
* Subscribe one runner to another
|
||||||
this.subject = new Subject();
|
*/
|
||||||
|
chain(runner: PanelQueryRunner): Unsubscribable {
|
||||||
|
const { sendLegacy, sendFrames } = runner.state;
|
||||||
|
let format = sendFrames ? PanelQueryRunnerFormat.frames : PanelQueryRunnerFormat.legacy;
|
||||||
|
|
||||||
|
if (sendLegacy) {
|
||||||
|
format = PanelQueryRunnerFormat.both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this.subscribe(runner.subject, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentData(): PanelData {
|
||||||
|
return this.state.validateStreamsAndGetPanelData();
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(options: QueryRunnerOptions): Promise<PanelData> {
|
||||||
const { state } = this;
|
const { state } = this;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -102,6 +121,17 @@ export class PanelQueryRunner {
|
|||||||
delayStateNotification,
|
delayStateNotification,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
|
// Support shared queries
|
||||||
|
if (isSharedDashboardQuery(datasource)) {
|
||||||
|
if (!this.sharedQueryRunner) {
|
||||||
|
this.sharedQueryRunner = new SharedQueryRunner(this);
|
||||||
|
}
|
||||||
|
return this.sharedQueryRunner.process(options);
|
||||||
|
} else if (this.sharedQueryRunner) {
|
||||||
|
this.sharedQueryRunner.disconnect();
|
||||||
|
this.sharedQueryRunner = null;
|
||||||
|
}
|
||||||
|
|
||||||
const request: DataQueryRequest = {
|
const request: DataQueryRequest = {
|
||||||
requestId: getNextRequestId(),
|
requestId: getNextRequestId(),
|
||||||
timezone,
|
timezone,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import * as graphitePlugin from 'app/plugins/datasource/graphite/module';
|
import * as graphitePlugin from 'app/plugins/datasource/graphite/module';
|
||||||
import * as cloudwatchPlugin from 'app/plugins/datasource/cloudwatch/module';
|
import * as cloudwatchPlugin from 'app/plugins/datasource/cloudwatch/module';
|
||||||
|
import * as dashboardDSPlugin from 'app/plugins/datasource/dashboard/module';
|
||||||
import * as elasticsearchPlugin from 'app/plugins/datasource/elasticsearch/module';
|
import * as elasticsearchPlugin from 'app/plugins/datasource/elasticsearch/module';
|
||||||
import * as opentsdbPlugin from 'app/plugins/datasource/opentsdb/module';
|
import * as opentsdbPlugin from 'app/plugins/datasource/opentsdb/module';
|
||||||
import * as grafanaPlugin from 'app/plugins/datasource/grafana/module';
|
import * as grafanaPlugin from 'app/plugins/datasource/grafana/module';
|
||||||
@ -39,6 +40,7 @@ import * as exampleApp from 'app/plugins/app/example-app/module';
|
|||||||
const builtInPlugins: any = {
|
const builtInPlugins: any = {
|
||||||
'app/plugins/datasource/graphite/module': graphitePlugin,
|
'app/plugins/datasource/graphite/module': graphitePlugin,
|
||||||
'app/plugins/datasource/cloudwatch/module': cloudwatchPlugin,
|
'app/plugins/datasource/cloudwatch/module': cloudwatchPlugin,
|
||||||
|
'app/plugins/datasource/dashboard/module': dashboardDSPlugin,
|
||||||
'app/plugins/datasource/elasticsearch/module': elasticsearchPlugin,
|
'app/plugins/datasource/elasticsearch/module': elasticsearchPlugin,
|
||||||
'app/plugins/datasource/opentsdb/module': opentsdbPlugin,
|
'app/plugins/datasource/opentsdb/module': opentsdbPlugin,
|
||||||
'app/plugins/datasource/grafana/module': grafanaPlugin,
|
'app/plugins/datasource/grafana/module': grafanaPlugin,
|
||||||
|
@ -125,8 +125,10 @@ export class DatasourceSrv implements DataSourceService {
|
|||||||
//Make sure grafana and mixed are sorted at the bottom
|
//Make sure grafana and mixed are sorted at the bottom
|
||||||
if (value.meta.id === 'grafana') {
|
if (value.meta.id === 'grafana') {
|
||||||
metricSource.sort = String.fromCharCode(253);
|
metricSource.sort = String.fromCharCode(253);
|
||||||
} else if (value.meta.id === 'mixed') {
|
} else if (value.meta.id === 'dashboard') {
|
||||||
metricSource.sort = String.fromCharCode(254);
|
metricSource.sort = String.fromCharCode(254);
|
||||||
|
} else if (value.meta.id === 'mixed') {
|
||||||
|
metricSource.sort = String.fromCharCode(255);
|
||||||
}
|
}
|
||||||
|
|
||||||
metricSources.push(metricSource);
|
metricSources.push(metricSource);
|
||||||
|
193
public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx
Normal file
193
public/app/plugins/datasource/dashboard/DashboardQueryEditor.tsx
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// Libraries
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { Select, DataQuery, DataQueryError, PanelData } from '@grafana/ui';
|
||||||
|
import { DataFrame, SelectableValue } from '@grafana/data';
|
||||||
|
import { DashboardQuery } from './types';
|
||||||
|
import config from 'app/core/config';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
|
import { PanelModel } from 'app/features/dashboard/state';
|
||||||
|
import { SHARED_DASHBODARD_QUERY } from './SharedQueryRunner';
|
||||||
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
|
import { filterPanelDataToQuery } from 'app/features/dashboard/panel_editor/QueryEditorRow';
|
||||||
|
|
||||||
|
type ResultInfo = {
|
||||||
|
img: string; // The Datasource
|
||||||
|
refId: string;
|
||||||
|
query: string; // As text
|
||||||
|
data: DataFrame[];
|
||||||
|
error?: DataQueryError;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getQueryDisplayText(query: DataQuery): string {
|
||||||
|
return JSON.stringify(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
panel: PanelModel;
|
||||||
|
panelData: PanelData;
|
||||||
|
onChange: (query: DashboardQuery) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
defaultDatasource: string;
|
||||||
|
results: ResultInfo[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DashboardQueryEditor extends PureComponent<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
defaultDatasource: '',
|
||||||
|
results: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getQuery(): DashboardQuery {
|
||||||
|
const { panel } = this.props;
|
||||||
|
return panel.targets[0] as DashboardQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
this.componentDidUpdate(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidUpdate(prevProps: Props) {
|
||||||
|
const { panelData } = this.props;
|
||||||
|
|
||||||
|
if (!prevProps || prevProps.panelData !== panelData) {
|
||||||
|
const query = this.props.panel.targets[0] as DashboardQuery;
|
||||||
|
const defaultDS = await getDatasourceSrv().get(null);
|
||||||
|
const dashboard = getDashboardSrv().getCurrent();
|
||||||
|
const panel = dashboard.getPanelById(query.panelId);
|
||||||
|
|
||||||
|
if (!panel) {
|
||||||
|
this.setState({ defaultDatasource: defaultDS.name });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainDS = await getDatasourceSrv().get(panel.datasource);
|
||||||
|
const info: ResultInfo[] = [];
|
||||||
|
|
||||||
|
for (const query of panel.targets) {
|
||||||
|
const ds = query.datasource ? await getDatasourceSrv().get(query.datasource) : mainDS;
|
||||||
|
const fmt = ds.getQueryDisplayText ? ds.getQueryDisplayText : getQueryDisplayText;
|
||||||
|
|
||||||
|
const qData = filterPanelDataToQuery(panelData, query.refId);
|
||||||
|
const queryData = qData ? qData : panelData;
|
||||||
|
|
||||||
|
info.push({
|
||||||
|
refId: query.refId,
|
||||||
|
query: fmt(query),
|
||||||
|
img: ds.meta.info.logos.small,
|
||||||
|
data: queryData.series,
|
||||||
|
error: queryData.error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ defaultDatasource: defaultDS.name, results: info });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPanelChanged = (id: number) => {
|
||||||
|
const { onChange } = this.props;
|
||||||
|
const query = this.getQuery();
|
||||||
|
query.panelId = id;
|
||||||
|
onChange(query);
|
||||||
|
|
||||||
|
// Update the
|
||||||
|
this.props.panel.refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
renderQueryData(editURL: string) {
|
||||||
|
const { results } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{results.map((target, index) => {
|
||||||
|
return (
|
||||||
|
<div className="query-editor-row__header" key={index}>
|
||||||
|
<div className="query-editor-row__ref-id">
|
||||||
|
<img src={target.img} width={16} className={css({ marginRight: '8px' })} />
|
||||||
|
{target.refId}:
|
||||||
|
</div>
|
||||||
|
<div className="query-editor-row__collapsed-text">
|
||||||
|
<a href={editURL}>
|
||||||
|
{target.query}
|
||||||
|
|
||||||
|
<i className="fa fa-external-link" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPanelDescription = (panel: PanelModel): string => {
|
||||||
|
const { defaultDatasource } = this.state;
|
||||||
|
const dsname = panel.datasource ? panel.datasource : defaultDatasource;
|
||||||
|
|
||||||
|
if (panel.targets.length === 1) {
|
||||||
|
return '1 query to ' + dsname;
|
||||||
|
}
|
||||||
|
|
||||||
|
return panel.targets.length + ' queries to ' + dsname;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const dashboard = getDashboardSrv().getCurrent();
|
||||||
|
const query = this.getQuery();
|
||||||
|
|
||||||
|
let selected: SelectableValue<number>;
|
||||||
|
const panels: Array<SelectableValue<number>> = [];
|
||||||
|
|
||||||
|
for (const panel of dashboard.panels) {
|
||||||
|
if (panel.targets && panel.datasource !== SHARED_DASHBODARD_QUERY) {
|
||||||
|
const plugin = config.panels[panel.type];
|
||||||
|
const item = {
|
||||||
|
value: panel.id,
|
||||||
|
label: panel.title ? panel.title : 'Panel ' + panel.id,
|
||||||
|
description: this.getPanelDescription(panel),
|
||||||
|
imgUrl: plugin.info.logos.small,
|
||||||
|
};
|
||||||
|
|
||||||
|
panels.push(item);
|
||||||
|
|
||||||
|
if (query.panelId === panel.id) {
|
||||||
|
selected = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panels.length < 1) {
|
||||||
|
return (
|
||||||
|
<div className={css({ padding: '10px' })}>
|
||||||
|
This dashboard does not have other panels. Add queries to other panels and try again
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as current URL, but different panelId
|
||||||
|
const editURL = `d/${dashboard.uid}/${dashboard.title}?&fullscreen&edit&panelId=${query.panelId}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="gf-form">
|
||||||
|
<div className="gf-form-label">Use results from panel</div>
|
||||||
|
<Select
|
||||||
|
placeholder="Choose Panel"
|
||||||
|
isSearchable={true}
|
||||||
|
options={panels}
|
||||||
|
value={selected}
|
||||||
|
onChange={item => this.onPanelChanged(item.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={css({ padding: '16px' })}>{query.panelId && this.renderQueryData(editURL)}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
4
public/app/plugins/datasource/dashboard/README.md
Normal file
4
public/app/plugins/datasource/dashboard/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Dashboard Datasource - Native Plugin
|
||||||
|
|
||||||
|
This is a **built in** datasource that lets you reuse the query from other panels in the
|
||||||
|
same dashboard.
|
@ -0,0 +1,22 @@
|
|||||||
|
import { isSharedDashboardQuery } from './SharedQueryRunner';
|
||||||
|
import { DataSourceApi } from '@grafana/ui';
|
||||||
|
|
||||||
|
describe('SharedQueryRunner', () => {
|
||||||
|
it('should identify shared queries', () => {
|
||||||
|
expect(isSharedDashboardQuery('-- Dashboard --')).toBe(true);
|
||||||
|
|
||||||
|
expect(isSharedDashboardQuery('')).toBe(false);
|
||||||
|
expect(isSharedDashboardQuery(undefined)).toBe(false);
|
||||||
|
expect(isSharedDashboardQuery(null)).toBe(false);
|
||||||
|
|
||||||
|
const ds = {
|
||||||
|
meta: {
|
||||||
|
name: '-- Dashboard --',
|
||||||
|
},
|
||||||
|
} as DataSourceApi;
|
||||||
|
expect(isSharedDashboardQuery(ds)).toBe(true);
|
||||||
|
|
||||||
|
ds.meta.name = 'something else';
|
||||||
|
expect(isSharedDashboardQuery(ds)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
115
public/app/plugins/datasource/dashboard/SharedQueryRunner.ts
Normal file
115
public/app/plugins/datasource/dashboard/SharedQueryRunner.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { DataSourceApi, DataQuery, PanelData } from '@grafana/ui';
|
||||||
|
import { PanelQueryRunner, QueryRunnerOptions } from 'app/features/dashboard/state/PanelQueryRunner';
|
||||||
|
import { toDataQueryError } from 'app/features/dashboard/state/PanelQueryState';
|
||||||
|
import { DashboardQuery } from './types';
|
||||||
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
|
import { Unsubscribable } from 'rxjs';
|
||||||
|
import { PanelModel } from 'app/features/dashboard/state';
|
||||||
|
import { LoadingState } from '@grafana/data';
|
||||||
|
|
||||||
|
export const SHARED_DASHBODARD_QUERY = '-- Dashboard --';
|
||||||
|
|
||||||
|
export function isSharedDashboardQuery(datasource: string | DataSourceApi) {
|
||||||
|
if (!datasource) {
|
||||||
|
// default datasource
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (datasource === SHARED_DASHBODARD_QUERY) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const ds = datasource as DataSourceApi;
|
||||||
|
return ds.meta && ds.meta.name === SHARED_DASHBODARD_QUERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SharedQueryRunner {
|
||||||
|
private containerPanel: PanelModel;
|
||||||
|
private listenToPanelId: number;
|
||||||
|
private listenToPanel: PanelModel;
|
||||||
|
private listenToRunner: PanelQueryRunner;
|
||||||
|
private subscription: Unsubscribable;
|
||||||
|
|
||||||
|
constructor(private runner: PanelQueryRunner) {
|
||||||
|
this.containerPanel = getDashboardSrv()
|
||||||
|
.getCurrent()
|
||||||
|
.getPanelById(runner.getPanelId());
|
||||||
|
}
|
||||||
|
|
||||||
|
process(options: QueryRunnerOptions): Promise<PanelData> {
|
||||||
|
const panelId = getPanelIdFromQuery(options.queries);
|
||||||
|
|
||||||
|
if (!panelId) {
|
||||||
|
this.disconnect();
|
||||||
|
return getQueryError('Missing panel reference ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
// The requested panel changed
|
||||||
|
if (this.listenToPanelId !== panelId) {
|
||||||
|
this.disconnect();
|
||||||
|
|
||||||
|
this.listenToPanel = getDashboardSrv()
|
||||||
|
.getCurrent()
|
||||||
|
.getPanelById(panelId);
|
||||||
|
|
||||||
|
if (!this.listenToPanel) {
|
||||||
|
return getQueryError('Unknown Panel: ' + panelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listenToPanelId = panelId;
|
||||||
|
this.listenToRunner = this.listenToPanel.getQueryRunner();
|
||||||
|
this.subscription = this.listenToRunner.chain(this.runner);
|
||||||
|
console.log('Connecting panel: ', this.containerPanel.id, 'to:', this.listenToPanelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the target has refreshed recently, use the exising data
|
||||||
|
const data = this.listenToRunner.getCurrentData();
|
||||||
|
if (data.request && data.request.startTime) {
|
||||||
|
const elapsed = Date.now() - data.request.startTime;
|
||||||
|
if (elapsed < 150) {
|
||||||
|
return Promise.resolve(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When fullscreen run with the current panel settings
|
||||||
|
if (this.containerPanel.fullscreen) {
|
||||||
|
const { datasource, targets } = this.listenToPanel;
|
||||||
|
const modified = {
|
||||||
|
...options,
|
||||||
|
panelId,
|
||||||
|
datasource,
|
||||||
|
queries: targets,
|
||||||
|
};
|
||||||
|
return this.listenToRunner.run(modified);
|
||||||
|
} else {
|
||||||
|
this.listenToPanel.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if (this.subscription) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
this.subscription = null;
|
||||||
|
}
|
||||||
|
if (this.listenToPanel) {
|
||||||
|
this.listenToPanel = null;
|
||||||
|
}
|
||||||
|
this.listenToPanelId = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPanelIdFromQuery(queries: DataQuery[]): number | undefined {
|
||||||
|
if (!queries || !queries.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return (queries[0] as DashboardQuery).panelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryError(msg: string): Promise<PanelData> {
|
||||||
|
return Promise.resolve({
|
||||||
|
state: LoadingState.Error,
|
||||||
|
series: [],
|
||||||
|
legacy: [],
|
||||||
|
error: toDataQueryError(msg),
|
||||||
|
});
|
||||||
|
}
|
23
public/app/plugins/datasource/dashboard/datasource.ts
Normal file
23
public/app/plugins/datasource/dashboard/datasource.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { DataSourceApi, DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/ui';
|
||||||
|
import { DashboardQuery } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should not really be called
|
||||||
|
*/
|
||||||
|
export class DashboardDatasource extends DataSourceApi<DashboardQuery> {
|
||||||
|
constructor(instanceSettings: DataSourceInstanceSettings) {
|
||||||
|
super(instanceSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCollapsedText(query: DashboardQuery) {
|
||||||
|
return `Dashboard Reference: ${query.panelId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
query(options: DataQueryRequest<DashboardQuery>): Promise<DataQueryResponse> {
|
||||||
|
return Promise.reject('This should not be called directly');
|
||||||
|
}
|
||||||
|
|
||||||
|
testDatasource() {
|
||||||
|
return Promise.resolve({});
|
||||||
|
}
|
||||||
|
}
|
4
public/app/plugins/datasource/dashboard/module.ts
Normal file
4
public/app/plugins/datasource/dashboard/module.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { DashboardDatasource } from './datasource';
|
||||||
|
import { DataSourcePlugin } from '@grafana/ui';
|
||||||
|
|
||||||
|
export const plugin = new DataSourcePlugin(DashboardDatasource);
|
9
public/app/plugins/datasource/dashboard/plugin.json
Normal file
9
public/app/plugins/datasource/dashboard/plugin.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"type": "datasource",
|
||||||
|
"name": "-- Dashboard --",
|
||||||
|
"id": "dashboard",
|
||||||
|
"state": "alpha",
|
||||||
|
|
||||||
|
"builtIn": true,
|
||||||
|
"metrics": true
|
||||||
|
}
|
5
public/app/plugins/datasource/dashboard/types.ts
Normal file
5
public/app/plugins/datasource/dashboard/types.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { DataQuery } from '@grafana/ui/src/types';
|
||||||
|
|
||||||
|
export interface DashboardQuery extends DataQuery {
|
||||||
|
panelId?: number;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user