grafana/public/app/plugins/panel/graph/module.ts
Dominik Prokop c38804216c
DataLinks: Enable multiple data links per panel (#18434)
In response to #18282

DataLinksEditor does not limit amount of links by default now (it was 1 link before, unless maxLinks prop was specified). Also, links that do not have label specified, are not rendered anymore.
2019-08-07 14:13:19 +02:00

346 lines
9.6 KiB
TypeScript

import './graph';
import './series_overrides_ctrl';
import './thresholds_form';
import './time_regions_form';
import template from './template';
import _ from 'lodash';
import { MetricsPanelCtrl } from 'app/plugins/sdk';
import { DataProcessor } from './data_processor';
import { axesEditorComponent } from './axes_editor';
import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2';
import { DataFrame, DataLink } from '@grafana/data';
import { getColorFromHexRgbOrName, LegacyResponseData, VariableSuggestion } from '@grafana/ui';
import { getProcessedDataFrames } from 'app/features/dashboard/state/PanelQueryState';
import { PanelQueryRunnerFormat } from 'app/features/dashboard/state/PanelQueryRunner';
import { GraphContextMenuCtrl } from './GraphContextMenuCtrl';
import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
import { auto } from 'angular';
import { AnnotationsSrv } from 'app/features/annotations/all';
class GraphCtrl extends MetricsPanelCtrl {
static template = template;
renderError: boolean;
hiddenSeries: any = {};
seriesList: TimeSeries[] = [];
dataList: DataFrame[] = [];
annotations: any = [];
alertState: any;
annotationsPromise: any;
dataWarning: any;
colors: any = [];
subTabIndex: number;
processor: DataProcessor;
contextMenuCtrl: GraphContextMenuCtrl;
linkVariableSuggestions: VariableSuggestion[] = getDataLinksVariableSuggestions();
panelDefaults: any = {
// datasource name, null = default datasource
datasource: null,
// sets client side (flot) or native graphite png renderer (png)
renderer: 'flot',
yaxes: [
{
label: null,
show: true,
logBase: 1,
min: null,
max: null,
format: 'short',
},
{
label: null,
show: true,
logBase: 1,
min: null,
max: null,
format: 'short',
},
],
xaxis: {
show: true,
mode: 'time',
name: null,
values: [],
buckets: null,
},
yaxis: {
align: false,
alignLevel: null,
},
// show/hide lines
lines: true,
// fill factor
fill: 1,
// fill factor
fillGradient: 0,
// line width in pixels
linewidth: 1,
// show/hide dashed line
dashes: false,
// length of a dash
dashLength: 10,
// length of space between two dashes
spaceLength: 10,
// show hide points
points: false,
// point radius in pixels
pointradius: 2,
// show hide bars
bars: false,
// enable/disable stacking
stack: false,
// stack percentage mode
percentage: false,
// legend options
legend: {
show: true, // disable/enable legend
values: false, // disable/enable legend values
min: false,
max: false,
current: false,
total: false,
avg: false,
},
// how null points should be handled
nullPointMode: 'null',
// staircase line mode
steppedLine: false,
// tooltip options
tooltip: {
value_type: 'individual',
shared: true,
sort: 0,
},
// time overrides
timeFrom: null,
timeShift: null,
// metric queries
targets: [{}],
// series color overrides
aliasColors: {},
// other style overrides
seriesOverrides: [],
thresholds: [],
timeRegions: [],
options: {
dataLinks: [],
},
};
/** @ngInject */
constructor($scope: any, $injector: auto.IInjectorService, private annotationsSrv: AnnotationsSrv) {
super($scope, $injector);
_.defaults(this.panel, this.panelDefaults);
_.defaults(this.panel.tooltip, this.panelDefaults.tooltip);
_.defaults(this.panel.legend, this.panelDefaults.legend);
_.defaults(this.panel.xaxis, this.panelDefaults.xaxis);
_.defaults(this.panel.options, this.panelDefaults.options);
this.dataFormat = PanelQueryRunnerFormat.frames;
this.processor = new DataProcessor(this.panel);
this.contextMenuCtrl = new GraphContextMenuCtrl($scope);
this.events.on('render', this.onRender.bind(this));
this.events.on('data-received', this.onDataReceived.bind(this));
this.events.on('data-error', this.onDataError.bind(this));
this.events.on('data-snapshot-load', this.onDataSnapshotLoad.bind(this));
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
this.events.on('init-panel-actions', this.onInitPanelActions.bind(this));
this.onDataLinksChange = this.onDataLinksChange.bind(this);
}
onInitEditMode() {
this.addEditorTab('Display options', 'public/app/plugins/panel/graph/tab_display.html');
this.addEditorTab('Axes', axesEditorComponent);
this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html');
this.addEditorTab('Thresholds & Time Regions', 'public/app/plugins/panel/graph/tab_thresholds_time_regions.html');
this.addEditorTab('Data links', 'public/app/plugins/panel/graph/tab_drilldown_links.html');
this.subTabIndex = 0;
}
onInitPanelActions(actions: any[]) {
actions.push({ text: 'Export CSV', click: 'ctrl.exportCsv()' });
actions.push({ text: 'Toggle legend', click: 'ctrl.toggleLegend()', shortcut: 'p l' });
}
issueQueries(datasource: any) {
this.annotationsPromise = this.annotationsSrv.getAnnotations({
dashboard: this.dashboard,
panel: this.panel,
range: this.range,
});
/* Wait for annotationSrv requests to get datasources to
* resolve before issuing queries. This allows the annotations
* service to fire annotations queries before graph queries
* (but not wait for completion). This resolves
* issue 11806.
*/
return this.annotationsSrv.datasourcePromises.then((r: any) => {
return super.issueQueries(datasource);
});
}
zoomOut(evt: any) {
this.publishAppEvent('zoom-out', 2);
}
onDataSnapshotLoad(snapshotData: any) {
this.annotationsPromise = this.annotationsSrv.getAnnotations({
dashboard: this.dashboard,
panel: this.panel,
range: this.range,
});
this.onDataReceived(snapshotData);
}
onDataError(err: any) {
this.seriesList = [];
this.annotations = [];
this.render([]);
}
// This should only be called from the snapshot callback
onDataReceived(dataList: LegacyResponseData[]) {
this.handleDataFrames(getProcessedDataFrames(dataList));
}
// Directly support DataFrame skipping event callbacks
handleDataFrames(data: DataFrame[]) {
super.handleDataFrames(data);
this.dataList = data;
this.seriesList = this.processor.getSeriesList({
dataList: this.dataList,
range: this.range,
});
this.dataWarning = null;
const datapointsCount = this.seriesList.reduce((prev, series) => {
return prev + series.datapoints.length;
}, 0);
if (datapointsCount === 0) {
this.dataWarning = {
title: 'No data points',
tip: 'No datapoints returned from data query',
};
} else {
for (const series of this.seriesList) {
if (series.isOutsideRange) {
this.dataWarning = {
title: 'Data points outside time range',
tip: 'Can be caused by timezone mismatch or missing time filter in query',
};
break;
}
}
}
this.annotationsPromise.then(
(result: { alertState: any; annotations: any }) => {
this.loading = false;
this.alertState = result.alertState;
this.annotations = result.annotations;
this.render(this.seriesList);
},
() => {
this.loading = false;
this.render(this.seriesList);
}
);
}
onRender() {
if (!this.seriesList) {
return;
}
for (const series of this.seriesList) {
series.applySeriesOverrides(this.panel.seriesOverrides);
if (series.unit) {
this.panel.yaxes[series.yaxis - 1].format = series.unit;
}
}
}
onColorChange = (series: any, color: string) => {
series.setColor(getColorFromHexRgbOrName(color, config.theme.type));
this.panel.aliasColors[series.alias] = color;
this.render();
};
onToggleSeries = (hiddenSeries: any) => {
this.hiddenSeries = hiddenSeries;
this.render();
};
onToggleSort = (sortBy: any, sortDesc: any) => {
this.panel.legend.sort = sortBy;
this.panel.legend.sortDesc = sortDesc;
this.render();
};
onToggleAxis = (info: { alias: any; yaxis: any }) => {
let override: any = _.find(this.panel.seriesOverrides, { alias: info.alias });
if (!override) {
override = { alias: info.alias };
this.panel.seriesOverrides.push(override);
}
override.yaxis = info.yaxis;
this.render();
};
onDataLinksChange(dataLinks: DataLink[]) {
this.panel.updateOptions({
...this.panel.options,
dataLinks,
});
}
addSeriesOverride(override: any) {
this.panel.seriesOverrides.push(override || {});
}
removeSeriesOverride(override: any) {
this.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override);
this.render();
}
toggleLegend() {
this.panel.legend.show = !this.panel.legend.show;
this.render();
}
legendValuesOptionChanged() {
const legend = this.panel.legend;
legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
this.render();
}
exportCsv() {
const scope = this.$scope.$new(true);
scope.seriesList = this.seriesList;
this.publishAppEvent('show-modal', {
templateHtml: '<export-data-modal data="seriesList"></export-data-modal>',
scope,
modalClass: 'modal--narrow',
});
}
onContextMenuClose = () => {
this.contextMenuCtrl.toggleMenu();
};
}
export { GraphCtrl, GraphCtrl as PanelCtrl };