graph_widget_view.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. var App = require('app');
  19. var fileUtils = require('utils/file_utils');
  20. App.GraphWidgetView = Em.View.extend(App.WidgetMixin, App.ExportMetricsMixin, {
  21. templateName: require('templates/common/widget/graph_widget'),
  22. /**
  23. * type of metric query from which the widget is comprised
  24. */
  25. metricType: 'TEMPORAL',
  26. /**
  27. * common metrics container
  28. * @type {Array}
  29. */
  30. metrics: [],
  31. /**
  32. * 3600 sec in 1 hour
  33. * @const
  34. */
  35. TIME_FACTOR: 3600,
  36. /**
  37. * custom time range, set when graph opened in popup
  38. * @type {number|null}
  39. */
  40. customTimeRange: null,
  41. /**
  42. * value in seconds
  43. * @type {number}
  44. */
  45. timeRange: function () {
  46. var timeRange = parseInt(this.get('content.properties.time_range'));
  47. if (isNaN(timeRange)) {
  48. //1h - default time range
  49. timeRange = 1;
  50. }
  51. return this.get('customTimeRange') || timeRange * this.get('TIME_FACTOR');
  52. }.property('content.properties.time_range', 'customTimeRange'),
  53. /**
  54. * value in ms
  55. * @type {number}
  56. */
  57. timeStep: 15,
  58. /**
  59. * @type {Array}
  60. */
  61. data: [],
  62. drawWidget: function () {
  63. if (this.get('isLoaded')) {
  64. this.set('data', this.calculateValues());
  65. }
  66. },
  67. /**
  68. * calculate series datasets for graph widgets
  69. */
  70. calculateValues: function () {
  71. var metrics = this.get('metrics');
  72. var seriesData = [];
  73. if (this.get('content.values')) {
  74. this.get('content.values').forEach(function (value) {
  75. var expression = this.extractExpressions(value)[0];
  76. var computedData;
  77. var datasetKey;
  78. if (expression) {
  79. datasetKey = value.value.match(this.get('EXPRESSION_REGEX'))[0];
  80. computedData = this.computeExpression(expression, metrics)[datasetKey];
  81. //exclude empty datasets
  82. if (computedData.length > 0) {
  83. seriesData.push({
  84. name: value.name,
  85. data: computedData
  86. });
  87. }
  88. }
  89. }, this);
  90. }
  91. return seriesData;
  92. },
  93. /**
  94. * compute expression
  95. *
  96. * @param {string} expression
  97. * @param {object} metrics
  98. * @returns {object}
  99. */
  100. computeExpression: function (expression, metrics) {
  101. var validExpression = true,
  102. value = [],
  103. dataLinks = {},
  104. dataLength = -1,
  105. beforeCompute,
  106. result = {},
  107. isDataCorrupted = false,
  108. isPointNull = false;
  109. //replace values with metrics data
  110. expression.match(this.get('VALUE_NAME_REGEX')).forEach(function (match) {
  111. if (isNaN(match)) {
  112. if (metrics.someProperty('name', match)) {
  113. dataLinks[match] = metrics.findProperty('name', match).data;
  114. if (!isDataCorrupted) {
  115. isDataCorrupted = (dataLength !== -1 && dataLength !== dataLinks[match].length);
  116. }
  117. dataLength = (dataLinks[match].length > dataLength) ? dataLinks[match].length : dataLength;
  118. } else {
  119. validExpression = false;
  120. console.warn('Metrics with name "' + match + '" not found to compute expression');
  121. }
  122. }
  123. });
  124. if (validExpression) {
  125. if (isDataCorrupted) {
  126. this.adjustData(dataLinks, dataLength);
  127. }
  128. for (var i = 0, timestamp; i < dataLength; i++) {
  129. isPointNull = false;
  130. beforeCompute = expression.replace(this.get('VALUE_NAME_REGEX'), function (match) {
  131. if (isNaN(match)) {
  132. timestamp = dataLinks[match][i][1];
  133. isPointNull = (isPointNull) ? true : (Em.isNone(dataLinks[match][i][0]));
  134. return dataLinks[match][i][0];
  135. } else {
  136. return match;
  137. }
  138. });
  139. var dataLinkPointValue = isPointNull ? null : Number(window.eval(beforeCompute));
  140. // expression resulting into `0/0` will produce NaN Object which is not a valid series data value for RickShaw graphs
  141. if (isNaN(dataLinkPointValue)) {
  142. dataLinkPointValue = 0;
  143. }
  144. value.push([dataLinkPointValue, timestamp]);
  145. }
  146. }
  147. result['${' + expression + '}'] = value;
  148. return result;
  149. },
  150. /**
  151. * add missing points, with zero value, to series
  152. *
  153. * @param {object} dataLinks
  154. * @param {number} length
  155. */
  156. adjustData: function(dataLinks, length) {
  157. //series with full data taken as original
  158. var original = [];
  159. var substituteValue = null;
  160. for (var i in dataLinks) {
  161. if (dataLinks[i].length === length) {
  162. original = dataLinks[i];
  163. break;
  164. }
  165. }
  166. original.forEach(function(point, index) {
  167. for (var i in dataLinks) {
  168. if (!dataLinks[i][index] || dataLinks[i][index][1] !== point[1]) {
  169. dataLinks[i].splice(index, 0, [substituteValue, point[1]]);
  170. }
  171. }
  172. }, this);
  173. },
  174. /**
  175. * add time properties
  176. * @param {Array} metricPaths
  177. * @returns {Array} result
  178. */
  179. addTimeProperties: function (metricPaths) {
  180. var toSeconds = Math.round(App.dateTime() / 1000);
  181. var fromSeconds = toSeconds - this.get('timeRange');
  182. var step = this.get('timeStep');
  183. var result = [];
  184. metricPaths.forEach(function (metricPath) {
  185. result.push(metricPath + '[' + fromSeconds + ',' + toSeconds + ',' + step + ']');
  186. }, this);
  187. return result;
  188. },
  189. /**
  190. * @type {Em.View}
  191. * @class
  192. */
  193. graphView: App.ChartLinearTimeView.extend({
  194. noTitleUnderGraph: true,
  195. inWidget: true,
  196. description: function () {
  197. return this.get('parentView.content.description');
  198. }.property('parentView.content.description'),
  199. isPreview: function () {
  200. return this.get('parentView.isPreview');
  201. }.property('parentView.isPreview'),
  202. displayUnit: function () {
  203. return this.get('parentView.content.properties.display_unit');
  204. }.property('parentView.content.properties.display_unit'),
  205. setYAxisFormatter: function () {
  206. var self = this;
  207. if (this.get('displayUnit')) {
  208. this.set('yAxisFormatter', function (value) {
  209. return App.ChartLinearTimeView.DisplayUnitFormatter(value, self.get('displayUnit'));
  210. });
  211. }
  212. }.observes('displayUnit'),
  213. /**
  214. * set custom time range for graph widget
  215. */
  216. setTimeRange: function () {
  217. if (this.get('isPopup')) {
  218. this.set('parentView.customTimeRange', this.get('timeUnitSeconds'));
  219. } else {
  220. this.set('parentView.customTimeRange', null);
  221. }
  222. }.observes('isPopup', 'timeUnitSeconds'),
  223. /**
  224. * graph height
  225. * @type {number}
  226. */
  227. height: 95,
  228. /**
  229. * @type {string}
  230. */
  231. id: function () {
  232. return 'widget_'+ this.get('parentView.content.id') + '_graph';
  233. }.property('parentView.content.id'),
  234. /**
  235. * @type {string}
  236. */
  237. renderer: function () {
  238. return this.get('parentView.content.properties.graph_type') === 'STACK' ? 'area' : 'line';
  239. }.property('parentView.content.properties.graph_type'),
  240. title: function () {
  241. return this.get('parentView.content.widgetName');
  242. }.property('parentView.content.widgetName'),
  243. transformToSeries: function (seriesData) {
  244. var seriesArray = [];
  245. seriesData.forEach(function (_series) {
  246. seriesArray.push(this.transformData(_series.data, _series.name));
  247. }, this);
  248. return seriesArray;
  249. },
  250. loadData: function () {
  251. var self = this;
  252. Em.run.next(function () {
  253. self._refreshGraph(self.get('parentView.data'))
  254. });
  255. },
  256. didInsertElement: function () {
  257. var self = this;
  258. this.$().closest('.graph-widget').on('mouseleave', function () {
  259. self.set('parentView.isMenuHidden', true);
  260. });
  261. this.setYAxisFormatter();
  262. this.loadData();
  263. var self = this;
  264. Em.run.next(function () {
  265. if (self.get('isPreview')) {
  266. App.tooltip(this.$("[rel='ZoomInTooltip']"), 'disable');
  267. } else {
  268. App.tooltip(this.$("[rel='ZoomInTooltip']"), {
  269. placement: 'left',
  270. template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner graph-tooltip"></div></div>'
  271. });
  272. }
  273. });
  274. }.observes('parentView.data')
  275. }),
  276. exportGraphData: function (event) {
  277. this._super();
  278. var data,
  279. isCSV = !!event.context,
  280. fileType = isCSV ? 'csv' : 'json',
  281. fileName = 'data.' + fileType,
  282. metrics = this.get('content.metrics'),
  283. data = isCSV ? this.prepareCSV(metrics) : this.prepareJSON(metrics);
  284. fileUtils.downloadTextFile(data, fileType, fileName);
  285. }
  286. });