module.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import './graph';
  2. import './series_overrides_ctrl';
  3. import './thresholds_form';
  4. import './time_regions_form';
  5. import template from './template';
  6. import _ from 'lodash';
  7. import { MetricsPanelCtrl } from 'app/plugins/sdk';
  8. import { DataProcessor } from './data_processor';
  9. import { axesEditorComponent } from './axes_editor';
  10. import config from 'app/core/config';
  11. import TimeSeries from 'app/core/time_series2';
  12. import { DataFrame, DataLink, DateTimeInput } from '@grafana/data';
  13. import { getColorFromHexRgbOrName, LegacyResponseData, VariableSuggestion } from '@grafana/ui';
  14. import { getProcessedDataFrames } from 'app/features/dashboard/state/PanelQueryState';
  15. import { PanelQueryRunnerFormat } from 'app/features/dashboard/state/PanelQueryRunner';
  16. import { GraphContextMenuCtrl } from './GraphContextMenuCtrl';
  17. import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
  18. import { auto } from 'angular';
  19. import { AnnotationsSrv } from 'app/features/annotations/all';
  20. class GraphCtrl extends MetricsPanelCtrl {
  21. static template = template;
  22. renderError: boolean;
  23. hiddenSeries: any = {};
  24. seriesList: TimeSeries[] = [];
  25. dataList: DataFrame[] = [];
  26. annotations: any = [];
  27. alertState: any;
  28. annotationsPromise: any;
  29. dataWarning: any;
  30. colors: any = [];
  31. subTabIndex: number;
  32. processor: DataProcessor;
  33. contextMenuCtrl: GraphContextMenuCtrl;
  34. linkVariableSuggestions: VariableSuggestion[] = getDataLinksVariableSuggestions();
  35. panelDefaults: any = {
  36. // datasource name, null = default datasource
  37. datasource: null,
  38. // sets client side (flot) or native graphite png renderer (png)
  39. renderer: 'flot',
  40. yaxes: [
  41. {
  42. label: null,
  43. show: true,
  44. logBase: 1,
  45. min: null,
  46. max: null,
  47. format: 'short',
  48. },
  49. {
  50. label: null,
  51. show: true,
  52. logBase: 1,
  53. min: null,
  54. max: null,
  55. format: 'short',
  56. },
  57. ],
  58. xaxis: {
  59. show: true,
  60. mode: 'time',
  61. name: null,
  62. values: [],
  63. buckets: null,
  64. },
  65. yaxis: {
  66. align: false,
  67. alignLevel: null,
  68. },
  69. // show/hide lines
  70. lines: true,
  71. // fill factor
  72. fill: 1,
  73. // fill factor
  74. fillGradient: 0,
  75. // line width in pixels
  76. linewidth: 1,
  77. // show/hide dashed line
  78. dashes: false,
  79. // length of a dash
  80. dashLength: 10,
  81. // length of space between two dashes
  82. spaceLength: 10,
  83. // show hide points
  84. points: false,
  85. // point radius in pixels
  86. pointradius: 2,
  87. // show hide bars
  88. bars: false,
  89. // enable/disable stacking
  90. stack: false,
  91. // stack percentage mode
  92. percentage: false,
  93. // legend options
  94. legend: {
  95. show: true, // disable/enable legend
  96. values: false, // disable/enable legend values
  97. min: false,
  98. max: false,
  99. current: false,
  100. total: false,
  101. avg: false,
  102. },
  103. // how null points should be handled
  104. nullPointMode: 'null',
  105. // staircase line mode
  106. steppedLine: false,
  107. // tooltip options
  108. tooltip: {
  109. value_type: 'individual',
  110. shared: true,
  111. sort: 0,
  112. },
  113. // time overrides
  114. timeFrom: null,
  115. timeShift: null,
  116. // metric queries
  117. targets: [{}],
  118. // series color overrides
  119. aliasColors: {},
  120. // other style overrides
  121. seriesOverrides: [],
  122. thresholds: [],
  123. timeRegions: [],
  124. options: {
  125. dataLinks: [],
  126. },
  127. };
  128. /** @ngInject */
  129. constructor($scope: any, $injector: auto.IInjectorService, private annotationsSrv: AnnotationsSrv) {
  130. super($scope, $injector);
  131. _.defaults(this.panel, this.panelDefaults);
  132. _.defaults(this.panel.tooltip, this.panelDefaults.tooltip);
  133. _.defaults(this.panel.legend, this.panelDefaults.legend);
  134. _.defaults(this.panel.xaxis, this.panelDefaults.xaxis);
  135. _.defaults(this.panel.options, this.panelDefaults.options);
  136. this.dataFormat = PanelQueryRunnerFormat.frames;
  137. this.processor = new DataProcessor(this.panel);
  138. this.contextMenuCtrl = new GraphContextMenuCtrl($scope);
  139. this.events.on('render', this.onRender.bind(this));
  140. this.events.on('data-received', this.onDataReceived.bind(this));
  141. this.events.on('data-error', this.onDataError.bind(this));
  142. this.events.on('data-snapshot-load', this.onDataSnapshotLoad.bind(this));
  143. this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
  144. this.events.on('init-panel-actions', this.onInitPanelActions.bind(this));
  145. this.onDataLinksChange = this.onDataLinksChange.bind(this);
  146. }
  147. onInitEditMode() {
  148. this.addEditorTab('Display options', 'public/app/plugins/panel/graph/tab_display.html');
  149. this.addEditorTab('Axes', axesEditorComponent);
  150. this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html');
  151. this.addEditorTab('Thresholds & Time Regions', 'public/app/plugins/panel/graph/tab_thresholds_time_regions.html');
  152. this.addEditorTab('Data links', 'public/app/plugins/panel/graph/tab_drilldown_links.html');
  153. this.subTabIndex = 0;
  154. }
  155. onInitPanelActions(actions: any[]) {
  156. actions.push({ text: 'Export CSV', click: 'ctrl.exportCsv()' });
  157. actions.push({ text: 'Toggle legend', click: 'ctrl.toggleLegend()', shortcut: 'p l' });
  158. }
  159. issueQueries(datasource: any) {
  160. this.annotationsPromise = this.annotationsSrv.getAnnotations({
  161. dashboard: this.dashboard,
  162. panel: this.panel,
  163. range: this.range,
  164. });
  165. /* Wait for annotationSrv requests to get datasources to
  166. * resolve before issuing queries. This allows the annotations
  167. * service to fire annotations queries before graph queries
  168. * (but not wait for completion). This resolves
  169. * issue 11806.
  170. */
  171. return this.annotationsSrv.datasourcePromises.then((r: any) => {
  172. return super.issueQueries(datasource);
  173. });
  174. }
  175. zoomOut(evt: any) {
  176. this.publishAppEvent('zoom-out', 2);
  177. }
  178. onDataSnapshotLoad(snapshotData: any) {
  179. this.annotationsPromise = this.annotationsSrv.getAnnotations({
  180. dashboard: this.dashboard,
  181. panel: this.panel,
  182. range: this.range,
  183. });
  184. this.onDataReceived(snapshotData);
  185. }
  186. onDataError(err: any) {
  187. this.seriesList = [];
  188. this.annotations = [];
  189. this.render([]);
  190. }
  191. // This should only be called from the snapshot callback
  192. onDataReceived(dataList: LegacyResponseData[]) {
  193. this.handleDataFrames(getProcessedDataFrames(dataList));
  194. }
  195. // Directly support DataFrame skipping event callbacks
  196. handleDataFrames(data: DataFrame[]) {
  197. super.handleDataFrames(data);
  198. this.dataList = data;
  199. this.seriesList = this.processor.getSeriesList({
  200. dataList: this.dataList,
  201. range: this.range,
  202. });
  203. this.dataWarning = null;
  204. const datapointsCount = this.seriesList.reduce((prev, series) => {
  205. return prev + series.datapoints.length;
  206. }, 0);
  207. if (datapointsCount === 0) {
  208. this.dataWarning = {
  209. title: 'No data points',
  210. tip: 'No datapoints returned from data query',
  211. };
  212. } else {
  213. for (const series of this.seriesList) {
  214. if (series.isOutsideRange) {
  215. this.dataWarning = {
  216. title: 'Data points outside time range',
  217. tip: 'Can be caused by timezone mismatch or missing time filter in query',
  218. };
  219. break;
  220. }
  221. }
  222. }
  223. this.annotationsPromise.then(
  224. (result: { alertState: any; annotations: any }) => {
  225. this.loading = false;
  226. this.alertState = result.alertState;
  227. this.annotations = result.annotations;
  228. this.render(this.seriesList);
  229. },
  230. () => {
  231. this.loading = false;
  232. this.render(this.seriesList);
  233. }
  234. );
  235. }
  236. onRender() {
  237. if (!this.seriesList) {
  238. return;
  239. }
  240. for (const series of this.seriesList) {
  241. series.applySeriesOverrides(this.panel.seriesOverrides);
  242. if (series.unit) {
  243. this.panel.yaxes[series.yaxis - 1].format = series.unit;
  244. }
  245. }
  246. }
  247. onColorChange = (series: any, color: string) => {
  248. series.setColor(getColorFromHexRgbOrName(color, config.theme.type));
  249. this.panel.aliasColors[series.alias] = color;
  250. this.render();
  251. };
  252. onToggleSeries = (hiddenSeries: any) => {
  253. this.hiddenSeries = hiddenSeries;
  254. this.render();
  255. };
  256. onToggleSort = (sortBy: any, sortDesc: any) => {
  257. this.panel.legend.sort = sortBy;
  258. this.panel.legend.sortDesc = sortDesc;
  259. this.render();
  260. };
  261. onToggleAxis = (info: { alias: any; yaxis: any }) => {
  262. let override: any = _.find(this.panel.seriesOverrides, { alias: info.alias });
  263. if (!override) {
  264. override = { alias: info.alias };
  265. this.panel.seriesOverrides.push(override);
  266. }
  267. override.yaxis = info.yaxis;
  268. this.render();
  269. };
  270. onDataLinksChange(dataLinks: DataLink[]) {
  271. this.panel.updateOptions({
  272. ...this.panel.options,
  273. dataLinks,
  274. });
  275. }
  276. addSeriesOverride(override: any) {
  277. this.panel.seriesOverrides.push(override || {});
  278. }
  279. removeSeriesOverride(override: any) {
  280. this.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override);
  281. this.render();
  282. }
  283. toggleLegend() {
  284. this.panel.legend.show = !this.panel.legend.show;
  285. this.render();
  286. }
  287. legendValuesOptionChanged() {
  288. const legend = this.panel.legend;
  289. legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
  290. this.render();
  291. }
  292. exportCsv() {
  293. const scope = this.$scope.$new(true);
  294. scope.seriesList = this.seriesList;
  295. this.publishAppEvent('show-modal', {
  296. templateHtml: '<export-data-modal data="seriesList"></export-data-modal>',
  297. scope,
  298. modalClass: 'modal--narrow',
  299. });
  300. }
  301. onContextMenuClose = () => {
  302. this.contextMenuCtrl.toggleMenu();
  303. };
  304. formatDate = (date: DateTimeInput, format?: string) => {
  305. return this.dashboard.formatDate.apply(this.dashboard, [date, format]);
  306. };
  307. }
  308. export { GraphCtrl, GraphCtrl as PanelCtrl };