module.ts 9.6 KB

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