mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #15368 from grafana/15217-panels-without-queries
POC: Panels without queries can skip DataPanel
This commit is contained in:
@@ -145,6 +145,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
|
||||
"info": panel.Info,
|
||||
"hideFromList": panel.HideFromList,
|
||||
"sort": getPanelSort(panel.Id),
|
||||
"dataFormats": panel.DataFormats,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import "encoding/json"
|
||||
|
||||
type PanelPlugin struct {
|
||||
FrontendPluginBase
|
||||
DataFormats []string `json:"dataFormats"`
|
||||
}
|
||||
|
||||
func (p *PanelPlugin) Load(decoder *json.Decoder, pluginDir string) error {
|
||||
@@ -15,6 +16,10 @@ func (p *PanelPlugin) Load(decoder *json.Decoder, pluginDir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.DataFormats == nil {
|
||||
p.DataFormats = []string{"time_series", "table"}
|
||||
}
|
||||
|
||||
Panels[p.Id] = p
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,14 +10,14 @@ import { PanelHeader } from './PanelHeader/PanelHeader';
|
||||
import { DataPanel } from './DataPanel';
|
||||
|
||||
// Utils
|
||||
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
|
||||
import { applyPanelTimeOverrides, snapshotDataToPanelData } from 'app/features/dashboard/utils/panel';
|
||||
import { PANEL_HEADER_HEIGHT } from 'app/core/constants';
|
||||
import { profiler } from 'app/core/profiler';
|
||||
|
||||
// Types
|
||||
import { DashboardModel, PanelModel } from '../state';
|
||||
import { PanelPlugin } from 'app/types';
|
||||
import { TimeRange, LoadingState } from '@grafana/ui';
|
||||
import { TimeRange, LoadingState, PanelData } from '@grafana/ui';
|
||||
|
||||
import variables from 'sass/_variables.scss';
|
||||
import templateSrv from 'app/features/templating/template_srv';
|
||||
@@ -94,7 +94,20 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
return !this.props.dashboard.otherPanelInFullscreen(this.props.panel);
|
||||
}
|
||||
|
||||
renderPanel(loading, panelData, width, height): JSX.Element {
|
||||
get hasPanelSnapshot() {
|
||||
const { panel } = this.props;
|
||||
return panel.snapshotData && panel.snapshotData.length;
|
||||
}
|
||||
|
||||
get needsQueryExecution() {
|
||||
return this.hasPanelSnapshot || this.props.plugin.dataFormats.length > 0;
|
||||
}
|
||||
|
||||
get getDataForPanel() {
|
||||
return this.hasPanelSnapshot ? snapshotDataToPanelData(this.props.panel) : null;
|
||||
}
|
||||
|
||||
renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element {
|
||||
const { panel, plugin } = this.props;
|
||||
const { timeRange, renderCounter } = this.state;
|
||||
const PanelComponent = plugin.exports.Panel;
|
||||
@@ -121,11 +134,39 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { panel, dashboard } = this.props;
|
||||
const { refreshCounter, timeRange, timeInfo } = this.state;
|
||||
renderPanelBody = (width: number, height: number): JSX.Element => {
|
||||
const { panel } = this.props;
|
||||
const { refreshCounter, timeRange } = this.state;
|
||||
const { datasource, targets } = panel;
|
||||
return (
|
||||
<>
|
||||
{this.needsQueryExecution ? (
|
||||
<DataPanel
|
||||
panelId={panel.id}
|
||||
datasource={datasource}
|
||||
queries={targets}
|
||||
timeRange={timeRange}
|
||||
isVisible={this.isVisible}
|
||||
widthPixels={width}
|
||||
refreshCounter={refreshCounter}
|
||||
onDataResponse={this.onDataResponse}
|
||||
>
|
||||
{({ loading, panelData }) => {
|
||||
return this.renderPanelPlugin(loading, panelData, width, height);
|
||||
}}
|
||||
</DataPanel>
|
||||
) : (
|
||||
this.renderPanelPlugin(LoadingState.Done, this.getDataForPanel, width, height)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard, panel } = this.props;
|
||||
const { timeInfo } = this.state;
|
||||
const { transparent } = panel;
|
||||
|
||||
const { datasource, targets, transparent } = panel;
|
||||
const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`;
|
||||
return (
|
||||
<AutoSizer>
|
||||
@@ -145,24 +186,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
scopedVars={panel.scopedVars}
|
||||
links={panel.links}
|
||||
/>
|
||||
{panel.snapshotData ? (
|
||||
this.renderPanel(false, panel.snapshotData, width, height)
|
||||
) : (
|
||||
<DataPanel
|
||||
panelId={panel.id}
|
||||
datasource={datasource}
|
||||
queries={targets}
|
||||
timeRange={timeRange}
|
||||
isVisible={this.isVisible}
|
||||
widthPixels={width}
|
||||
refreshCounter={refreshCounter}
|
||||
onDataResponse={this.onDataResponse}
|
||||
>
|
||||
{({ loading, panelData }) => {
|
||||
return this.renderPanel(loading, panelData, width, height);
|
||||
}}
|
||||
</DataPanel>
|
||||
)}
|
||||
{this.renderPanelBody(width, height)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -46,6 +46,7 @@ export function getPanelPluginNotFound(id: string): PanelPlugin {
|
||||
sort: 100,
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
dataFormats: [],
|
||||
info: {
|
||||
author: {
|
||||
name: '',
|
||||
|
||||
@@ -30,6 +30,32 @@ interface PanelEditorTab {
|
||||
text: string;
|
||||
}
|
||||
|
||||
enum PanelEditorTabIds {
|
||||
Queries = 'queries',
|
||||
Visualization = 'visualization',
|
||||
Advanced = 'advanced',
|
||||
Alert = 'alert',
|
||||
}
|
||||
|
||||
interface PanelEditorTab {
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const panelEditorTabTexts = {
|
||||
[PanelEditorTabIds.Queries]: 'Queries',
|
||||
[PanelEditorTabIds.Visualization]: 'Visualization',
|
||||
[PanelEditorTabIds.Advanced]: 'Panel Options',
|
||||
[PanelEditorTabIds.Alert]: 'Alert',
|
||||
};
|
||||
|
||||
const getPanelEditorTab = (tabId: PanelEditorTabIds): PanelEditorTab => {
|
||||
return {
|
||||
id: tabId,
|
||||
text: panelEditorTabTexts[tabId],
|
||||
};
|
||||
};
|
||||
|
||||
export class PanelEditor extends PureComponent<PanelEditorProps> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -72,31 +98,26 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
|
||||
|
||||
render() {
|
||||
const { plugin } = this.props;
|
||||
let activeTab = store.getState().location.query.tab || 'queries';
|
||||
let activeTab: PanelEditorTabIds = store.getState().location.query.tab || PanelEditorTabIds.Queries;
|
||||
|
||||
const tabs: PanelEditorTab[] = [
|
||||
{ id: 'queries', text: 'Queries' },
|
||||
{ id: 'visualization', text: 'Visualization' },
|
||||
{ id: 'advanced', text: 'Panel Options' },
|
||||
getPanelEditorTab(PanelEditorTabIds.Queries),
|
||||
getPanelEditorTab(PanelEditorTabIds.Visualization),
|
||||
getPanelEditorTab(PanelEditorTabIds.Advanced),
|
||||
];
|
||||
|
||||
// handle panels that do not have queries tab
|
||||
if (plugin.exports.PanelCtrl) {
|
||||
if (!plugin.exports.PanelCtrl.prototype.onDataReceived) {
|
||||
// remove queries tab
|
||||
tabs.shift();
|
||||
// switch tab
|
||||
if (activeTab === 'queries') {
|
||||
activeTab = 'visualization';
|
||||
}
|
||||
if (plugin.dataFormats.length === 0) {
|
||||
// remove queries tab
|
||||
tabs.shift();
|
||||
// switch tab
|
||||
if (activeTab === PanelEditorTabIds.Queries) {
|
||||
activeTab = PanelEditorTabIds.Visualization;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.alertingEnabled && plugin.id === 'graph') {
|
||||
tabs.push({
|
||||
id: 'alert',
|
||||
text: 'Alert',
|
||||
});
|
||||
tabs.push(getPanelEditorTab(PanelEditorTabIds.Alert));
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,8 @@ import store from 'app/core/store';
|
||||
// Models
|
||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
import { TimeRange } from '@grafana/ui';
|
||||
import { PanelData, TimeRange, TimeSeries } from '@grafana/ui';
|
||||
import { TableData } from '@grafana/ui/src';
|
||||
|
||||
// Utils
|
||||
import { isString as _isString } from 'lodash';
|
||||
@@ -168,3 +169,19 @@ export function getResolution(panel: PanelModel): number {
|
||||
|
||||
return panel.maxDataPoints ? panel.maxDataPoints : Math.ceil(width * (panel.gridPos.w / 24));
|
||||
}
|
||||
|
||||
const isTimeSeries = (data: any): data is TimeSeries => data && data.hasOwnProperty('datapoints');
|
||||
const isTableData = (data: any): data is TableData => data && data.hasOwnProperty('columns');
|
||||
export const snapshotDataToPanelData = (panel: PanelModel): PanelData => {
|
||||
const snapshotData = panel.snapshotData;
|
||||
if (isTimeSeries(snapshotData[0])) {
|
||||
return {
|
||||
timeSeries: snapshotData
|
||||
} as PanelData;
|
||||
} else if (isTableData(snapshotData[0])) {
|
||||
return {
|
||||
tableData: snapshotData[0]
|
||||
} as PanelData;
|
||||
}
|
||||
throw new Error('snapshotData is invalid:' + snapshotData.toString());
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Plugin, PanelPlugin } from 'app/types';
|
||||
import { Plugin, PanelPlugin, PanelDataFormat } from 'app/types';
|
||||
|
||||
export const getMockPlugins = (amount: number): Plugin[] => {
|
||||
const plugins = [];
|
||||
@@ -38,6 +38,7 @@ export const getPanelPlugin = (options: { id: string; sort?: number; hideFromLis
|
||||
id: options.id,
|
||||
name: options.id,
|
||||
sort: options.sort || 1,
|
||||
dataFormats: [PanelDataFormat.TimeSeries],
|
||||
info: {
|
||||
author: {
|
||||
name: options.id + 'name',
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"name": "Alert List",
|
||||
"id": "alertlist",
|
||||
|
||||
"dataFormats": [],
|
||||
|
||||
"info": {
|
||||
"description": "Shows list of alerts and their current status",
|
||||
"author": {
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
"name": "Dashboard list",
|
||||
"id": "dashlist",
|
||||
|
||||
"dataFormats": [],
|
||||
|
||||
"info": {
|
||||
"description": "List of dynamic links to other dashboards",
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-dashlist-panel.svg",
|
||||
"large": "img/icn-dashlist-panel.svg"
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"name": "Gauge",
|
||||
"id": "gauge",
|
||||
|
||||
"dataFormats": ["time_series"],
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
"name": "Graph",
|
||||
"id": "graph",
|
||||
|
||||
"dataFormats": ["time_series", "table"],
|
||||
|
||||
"info": {
|
||||
"description": "Graph Panel for Grafana",
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-graph-panel.svg",
|
||||
"large": "img/icn-graph-panel.svg"
|
||||
@@ -16,4 +18,3 @@
|
||||
"version": "5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { processTimeSeries } from '@grafana/ui/src/utils';
|
||||
import { Graph } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { PanelProps, NullValueMode } from '@grafana/ui/src/types';
|
||||
import { PanelProps, NullValueMode, TimeSeriesVMs } from '@grafana/ui/src/types';
|
||||
import { Options } from './types';
|
||||
|
||||
interface Props extends PanelProps<Options> {}
|
||||
@@ -19,7 +19,7 @@ export class GraphPanel extends PureComponent<Props> {
|
||||
const { panelData, timeRange, width, height } = this.props;
|
||||
const { showLines, showBars, showPoints } = this.props.options;
|
||||
|
||||
let vmSeries;
|
||||
let vmSeries: TimeSeriesVMs;
|
||||
if (panelData.timeSeries) {
|
||||
vmSeries = processTimeSeries({
|
||||
timeSeries: panelData.timeSeries,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"type": "panel",
|
||||
"name": "React Graph",
|
||||
"id": "graph2",
|
||||
|
||||
"state": "alpha",
|
||||
|
||||
"info": {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"name": "Heatmap",
|
||||
"id": "heatmap",
|
||||
|
||||
"dataFormats": ["time_series"],
|
||||
|
||||
"info": {
|
||||
"description": "Heatmap Panel for Grafana",
|
||||
"author": {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"name": "Plugin list",
|
||||
"id": "pluginlist",
|
||||
|
||||
"dataFormats": [],
|
||||
|
||||
"info": {
|
||||
"description": "Plugin List for Grafana",
|
||||
"author": {
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
"name": "Singlestat",
|
||||
"id": "singlestat",
|
||||
|
||||
"dataFormats": ["time_series", "table"],
|
||||
|
||||
"info": {
|
||||
"description": "Singlestat Panel for Grafana",
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-singlestat-panel.svg",
|
||||
"large": "img/icn-singlestat-panel.svg"
|
||||
@@ -16,4 +18,3 @@
|
||||
"version": "5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
"name": "Table",
|
||||
"id": "table",
|
||||
|
||||
"dataFormats": ["table", "time_series"],
|
||||
|
||||
"info": {
|
||||
"description": "Table Panel for Grafana",
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-table-panel.svg",
|
||||
"large": "img/icn-table-panel.svg"
|
||||
@@ -16,4 +18,3 @@
|
||||
"version": "5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
"name": "Text",
|
||||
"id": "text",
|
||||
|
||||
"dataFormats": [],
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-text-panel.svg",
|
||||
"large": "img/icn-text-panel.svg"
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import { PanelProps } from '@grafana/ui';
|
||||
|
||||
export class Text2 extends PureComponent<PanelProps> {
|
||||
constructor(props) {
|
||||
constructor(props: PanelProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
"type": "panel",
|
||||
"name": "Text v2",
|
||||
"id": "text2",
|
||||
|
||||
"state": "alpha",
|
||||
|
||||
"dataFormats": [],
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
|
||||
@@ -9,6 +9,12 @@ export interface PanelPlugin {
|
||||
info: any;
|
||||
sort: number;
|
||||
exports?: PluginExports;
|
||||
dataFormats: PanelDataFormat[];
|
||||
}
|
||||
|
||||
export enum PanelDataFormat {
|
||||
Table = 'table',
|
||||
TimeSeries = 'time_series',
|
||||
}
|
||||
|
||||
export interface Plugin {
|
||||
|
||||
Reference in New Issue
Block a user