graph_widget_view.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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. App.GraphWidgetView = Em.View.extend(App.WidgetMixin, {
  20. templateName: require('templates/common/widget/graph_widget'),
  21. /**
  22. * common metrics container
  23. * @type {Array}
  24. */
  25. metrics: [],
  26. /**
  27. * 3600 sec in 1 hour
  28. * @const
  29. */
  30. TIME_FACTOR: 3600,
  31. /**
  32. * custom time range, set when graph opened in popup
  33. * @type {number|null}
  34. */
  35. customTimeRange: null,
  36. /**
  37. * value in seconds
  38. * @type {number}
  39. */
  40. timeRange: function () {
  41. var timeRange = parseInt(this.get('content.properties.time_range'));
  42. if (isNaN(timeRange)) {
  43. //1h - default time range
  44. timeRange = 1;
  45. }
  46. return this.get('customTimeRange') || timeRange * this.get('TIME_FACTOR');
  47. }.property('content.properties.time_range', 'customTimeRange'),
  48. /**
  49. * value in ms
  50. * @type {number}
  51. */
  52. timeStep: 15,
  53. /**
  54. * @type {Array}
  55. */
  56. data: [],
  57. drawWidget: function () {
  58. if (this.get('isLoaded')) {
  59. this.set('data', this.calculateValues());
  60. }
  61. },
  62. /**
  63. * calculate series datasets for graph widgets
  64. */
  65. calculateValues: function () {
  66. var metrics = this.get('metrics');
  67. var seriesData = [];
  68. if (this.get('content.values')) {
  69. this.get('content.values').forEach(function (value) {
  70. var expression = this.extractExpressions(value)[0];
  71. var computedData;
  72. var datasetKey;
  73. if (expression) {
  74. datasetKey = value.value.match(this.get('EXPRESSION_REGEX'))[0];
  75. computedData = this.computeExpression(expression, metrics)[datasetKey];
  76. //exclude empty datasets
  77. if (computedData.length > 0) {
  78. seriesData.push({
  79. name: value.name,
  80. data: computedData
  81. });
  82. }
  83. }
  84. }, this);
  85. }
  86. return seriesData;
  87. },
  88. /**
  89. * compute expression
  90. *
  91. * @param {string} expression
  92. * @param {object} metrics
  93. * @returns {object}
  94. */
  95. computeExpression: function (expression, metrics) {
  96. var validExpression = true,
  97. value = [],
  98. dataLinks = {},
  99. dataLength = -1,
  100. beforeCompute,
  101. result = {},
  102. isDataCorrupted = false,
  103. isPointNull = false;
  104. //replace values with metrics data
  105. expression.match(this.get('VALUE_NAME_REGEX')).forEach(function (match) {
  106. if (isNaN(match)) {
  107. if (metrics.someProperty('name', match)) {
  108. dataLinks[match] = metrics.findProperty('name', match).data;
  109. if (!isDataCorrupted) {
  110. isDataCorrupted = (dataLength !== -1 && dataLength !== dataLinks[match].length);
  111. }
  112. dataLength = (dataLinks[match].length > dataLength) ? dataLinks[match].length : dataLength;
  113. } else {
  114. validExpression = false;
  115. console.warn('Metrics with name "' + match + '" not found to compute expression');
  116. }
  117. }
  118. });
  119. if (validExpression) {
  120. if (isDataCorrupted) {
  121. this.adjustData(dataLinks, dataLength);
  122. }
  123. for (var i = 0, timestamp; i < dataLength; i++) {
  124. isPointNull = false;
  125. beforeCompute = expression.replace(this.get('VALUE_NAME_REGEX'), function (match) {
  126. if (isNaN(match)) {
  127. timestamp = dataLinks[match][i][1];
  128. isPointNull = (isPointNull) ? true : (Em.isNone(dataLinks[match][i][0]));
  129. return dataLinks[match][i][0];
  130. } else {
  131. return match;
  132. }
  133. });
  134. var dataLinkPointValue = isPointNull ? null : Number(window.eval(beforeCompute));
  135. // expression resulting into `0/0` will produce NaN Object which is not a valid series data value for RickShaw graphs
  136. if (isNaN(dataLinkPointValue)) {
  137. dataLinkPointValue = 0;
  138. }
  139. value.push([dataLinkPointValue, timestamp]);
  140. }
  141. }
  142. result['${' + expression + '}'] = value;
  143. return result;
  144. },
  145. /**
  146. * add missing points, with zero value, to series
  147. *
  148. * @param {object} dataLinks
  149. * @param {number} length
  150. */
  151. adjustData: function(dataLinks, length) {
  152. //series with full data taken as original
  153. var original = [];
  154. var substituteValue = null;
  155. for (var i in dataLinks) {
  156. if (dataLinks[i].length === length) {
  157. original = dataLinks[i];
  158. break;
  159. }
  160. }
  161. original.forEach(function(point, index) {
  162. for (var i in dataLinks) {
  163. if (!dataLinks[i][index] || dataLinks[i][index][1] !== point[1]) {
  164. dataLinks[i].splice(index, 0, [substituteValue, point[1]]);
  165. }
  166. }
  167. }, this);
  168. },
  169. /**
  170. * make GET call to server in order to fetch service-component metrics
  171. * @param {object} request
  172. * @returns {$.ajax}
  173. */
  174. getServiceComponentMetrics: function (request) {
  175. return App.ajax.send({
  176. name: 'widgets.serviceComponent.metrics.get',
  177. sender: this,
  178. data: {
  179. serviceName: request.service_name,
  180. componentName: request.component_name,
  181. metricPaths: this.addTimeProperties(request.metric_paths).join(',')
  182. },
  183. success: 'getMetricsSuccessCallback'
  184. });
  185. },
  186. /**
  187. * make GET call to server in order to fetch host-component metrics
  188. * @param {object} request
  189. * @returns {$.ajax}
  190. */
  191. getHostComponentMetrics: function (request) {
  192. return App.ajax.send({
  193. name: 'widgets.hostComponent.metrics.get',
  194. sender: this,
  195. data: {
  196. componentName: request.component_name,
  197. metricPaths: this.addTimeProperties(request.metric_paths).join(','),
  198. hostComponentCriteria: this.computeHostComponentCriteria(request)
  199. },
  200. success: 'getHostComponentMetricsSuccessCallback'
  201. });
  202. },
  203. /**
  204. * add time properties
  205. * @param {Array} metricPaths
  206. * @returns {Array} result
  207. */
  208. addTimeProperties: function (metricPaths) {
  209. var toSeconds = Math.round(App.dateTime() / 1000);
  210. var fromSeconds = toSeconds - this.get('timeRange');
  211. var step = this.get('timeStep');
  212. var result = [];
  213. metricPaths.forEach(function (metricPath) {
  214. result.push(metricPath + '[' + fromSeconds + ',' + toSeconds + ',' + step + ']');
  215. }, this);
  216. return result;
  217. },
  218. /**
  219. * @type {Em.View}
  220. * @class
  221. */
  222. graphView: App.ChartLinearTimeView.extend({
  223. noTitleUnderGraph: true,
  224. inWidget: true,
  225. description: function () {
  226. return this.get('parentView.content.description');
  227. }.property('parentView.content.description'),
  228. isPreview: function () {
  229. return this.get('parentView.isPreview');
  230. }.property('parentView.isPreview'),
  231. displayUnit: function () {
  232. return this.get('parentView.content.properties.display_unit');
  233. }.property('parentView.content.properties.display_unit'),
  234. /**
  235. * set custom time range for graph widget
  236. */
  237. setTimeRange: function () {
  238. if (this.get('isPopup')) {
  239. this.set('parentView.customTimeRange', this.get('timeUnitSeconds'));
  240. } else {
  241. this.set('parentView.customTimeRange', null);
  242. }
  243. }.observes('isPopup', 'timeUnitSeconds'),
  244. /**
  245. * graph height
  246. * @type {number}
  247. */
  248. height: 95,
  249. /**
  250. * @type {string}
  251. */
  252. id: function () {
  253. return 'widget_'+ this.get('parentView.content.id') + '_graph';
  254. }.property('parentView.content.id'),
  255. /**
  256. * @type {string}
  257. */
  258. renderer: function () {
  259. return this.get('parentView.content.properties.graph_type') === 'STACK' ? 'area' : 'line';
  260. }.property('parentView.content.properties.graph_type'),
  261. title: function () {
  262. return this.get('parentView.content.widgetName');
  263. }.property('parentView.content.widgetName'),
  264. transformToSeries: function (seriesData) {
  265. var seriesArray = [];
  266. seriesData.forEach(function (_series) {
  267. seriesArray.push(this.transformData(_series.data, _series.name));
  268. }, this);
  269. return seriesArray;
  270. },
  271. loadData: function () {
  272. var self = this;
  273. Em.run.next(function () {
  274. self._refreshGraph(self.get('parentView.data'))
  275. });
  276. },
  277. didInsertElement: function () {
  278. this.loadData();
  279. var self = this;
  280. Em.run.next(function () {
  281. if (self.get('isPreview')) {
  282. App.tooltip(this.$("[rel='ZoomInTooltip']"), 'disable');
  283. } else {
  284. App.tooltip(this.$("[rel='ZoomInTooltip']"), {placement : 'left'});
  285. }
  286. });
  287. }.observes('parentView.data')
  288. })
  289. });