widget_mixin.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  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.WidgetMixin = Ember.Mixin.create({
  20. /**
  21. * @type {RegExp}
  22. * @const
  23. */
  24. EXPRESSION_REGEX: /\$\{([\w\s\.\,\+\-\*\/\(\)\:\=\[\]]*)\}/g,
  25. /**
  26. * @type {RegExp}
  27. * @const
  28. */
  29. MATH_EXPRESSION_REGEX: /^[\d\s\+\-\*\/\(\)\.]+$/,
  30. /**
  31. * @type {RegExp}
  32. * @const
  33. */
  34. VALUE_NAME_REGEX: /[\w\.\,\:\=\[\]]+/g,
  35. /**
  36. * common metrics container
  37. * @type {Array}
  38. */
  39. metrics: [],
  40. /**
  41. * @type {boolean}
  42. */
  43. isLoaded: false,
  44. /**
  45. * @type {App.Widget}
  46. * @default null
  47. */
  48. content: null,
  49. beforeRender: function () {
  50. this.get('metrics').clear();
  51. this.loadMetrics();
  52. },
  53. /**
  54. * load metrics
  55. */
  56. loadMetrics: function () {
  57. var requestData = this.getRequestData(this.get('content.metrics')),
  58. request,
  59. requestCounter = 0,
  60. self = this;
  61. for (var i in requestData) {
  62. request = requestData[i];
  63. requestCounter++;
  64. if (this.get('content.widgetType') === 'HEATMAP'){
  65. if (request.service_name === 'STACK') {
  66. this.getHostsMetrics(request).complete(function () {
  67. requestCounter--;
  68. if (requestCounter === 0) self.onMetricsLoaded();
  69. });
  70. } else {
  71. this.getHostComponentsMetrics(request).complete(function () {
  72. requestCounter--;
  73. if (requestCounter === 0) self.onMetricsLoaded();
  74. });
  75. }
  76. } else if (request.host_component_criteria) {
  77. this.getHostComponentMetrics(request).always(function () {
  78. requestCounter--;
  79. if (requestCounter === 0) self.onMetricsLoaded();
  80. });
  81. } else {
  82. this.getServiceComponentMetrics(request).complete(function () {
  83. requestCounter--;
  84. if (requestCounter === 0) self.onMetricsLoaded();
  85. });
  86. }
  87. }
  88. },
  89. /**
  90. * get data formatted for request
  91. * @param {Array} metrics
  92. */
  93. getRequestData: function (metrics) {
  94. var requestsData = {};
  95. if (metrics) {
  96. metrics.forEach(function (metric, index) {
  97. var key;
  98. if (metric.host_component_criteria) {
  99. this.tweakHostComponentCriteria(metric);
  100. key = metric.service_name + '_' + metric.component_name + '_' + metric.host_component_criteria;
  101. } else {
  102. key = metric.service_name + '_' + metric.component_name;
  103. }
  104. var requestMetric = $.extend({}, metric);
  105. if (requestsData[key]) {
  106. requestsData[key]["metric_paths"].push(requestMetric["metric_path"]);
  107. } else {
  108. requestMetric["metric_paths"] = [requestMetric["metric_path"]];
  109. delete requestMetric["metric_path"];
  110. requestsData[key] = requestMetric;
  111. }
  112. }, this);
  113. }
  114. return requestsData;
  115. },
  116. /**
  117. * Tweak necessary host component criteria
  118. * NameNode HA host component criteria is applicable only in HA mode
  119. */
  120. tweakHostComponentCriteria: function (metric) {
  121. switch (metric.component_name) {
  122. case 'NAMENODE':
  123. if (metric.host_component_criteria === 'host_components/metrics/dfs/FSNamesystem/HAState=active') {
  124. //if (metric.host_component_criteria)
  125. var hdfs = App.HDFSService.find().objectAt(0);
  126. var activeNNHostName = !hdfs.get('snameNode') && hdfs.get('activeNameNode');
  127. if (!activeNNHostName) {
  128. metric.host_component_criteria = 'host_components/HostRoles/component_name=NAMENODE';
  129. }
  130. }
  131. break;
  132. }
  133. },
  134. /**
  135. * make GET call to server in order to fetch service-component metrics
  136. * @param {object} request
  137. * @returns {$.ajax}
  138. */
  139. getServiceComponentMetrics: function (request) {
  140. return App.ajax.send({
  141. name: 'widgets.serviceComponent.metrics.get',
  142. sender: this,
  143. data: {
  144. serviceName: request.service_name,
  145. componentName: request.component_name,
  146. metricPaths: request.metric_paths.join(',')
  147. },
  148. success: 'getMetricsSuccessCallback'
  149. });
  150. },
  151. /**
  152. * make GET call to server in order to fetch specifc host-component metrics
  153. * @param {object} request
  154. * @returns {$.Deferred}
  155. */
  156. getHostComponentMetrics: function (request) {
  157. var dfd;
  158. var self = this;
  159. dfd = $.Deferred();
  160. this.getHostComponentName(request).done(function (data) {
  161. if (data) {
  162. request.host_name = data.host_components[0].HostRoles.host_name;
  163. App.ajax.send({
  164. name: 'widgets.hostComponent.metrics.get',
  165. sender: self,
  166. data: {
  167. componentName: request.component_name,
  168. hostName: request.host_name,
  169. metricPaths: request.metric_paths.join(',')
  170. }
  171. }).done(function(metricData) {
  172. self.getMetricsSuccessCallback(metricData);
  173. dfd.resolve();
  174. }).fail(function(data){
  175. dfd.reject();
  176. });
  177. }
  178. }).fail(function(data){
  179. dfd.reject();
  180. });
  181. return dfd.promise();
  182. },
  183. /**
  184. * make GET call to server in order to fetch host-component names
  185. * @param {object} request
  186. * @returns {$.ajax}
  187. */
  188. getHostComponentName: function (request) {
  189. return App.ajax.send({
  190. name: 'widgets.hostComponent.get.hostName',
  191. sender: this,
  192. data: {
  193. serviceName: request.service_name,
  194. componentName: request.component_name,
  195. metricPaths: request.metric_paths.join(','),
  196. hostComponentCriteria: request.host_component_criteria
  197. }
  198. });
  199. },
  200. /**
  201. * callback on getting aggregated metrics and host component metrics
  202. * @param data
  203. */
  204. getMetricsSuccessCallback: function (data) {
  205. var metrics = [];
  206. this.get('content.metrics').forEach(function (_metric) {
  207. if (!Em.isNone(Em.get(data, _metric.metric_path.replace(/\//g, '.')))) {
  208. _metric.data = Em.get(data, _metric.metric_path.replace(/\//g, '.'));
  209. this.get('metrics').pushObject(_metric);
  210. }
  211. }, this);
  212. },
  213. /**
  214. * make GET call to get host component metrics accross
  215. * @param {object} request
  216. * @return {$.ajax}
  217. */
  218. getHostComponentsMetrics: function(request) {
  219. request.metric_paths.forEach(function(_metric,index){
  220. request.metric_paths[index] = "host_components/" + _metric;
  221. });
  222. return App.ajax.send({
  223. name: 'widgets.serviceComponent.metrics.get',
  224. sender: this,
  225. data: {
  226. serviceName: request.service_name,
  227. componentName: request.component_name,
  228. metricPaths: request.metric_paths.join(',')
  229. },
  230. success: 'getHostComponentsMetricsSuccessCallback'
  231. });
  232. },
  233. getHostComponentsMetricsSuccessCallback: function(data) {
  234. var metrics = this.get('content.metrics');
  235. data.host_components.forEach(function(item){
  236. metrics.forEach(function (_metric) {
  237. if (!Em.isNone(Em.get(item, _metric.metric_path.replace(/\//g, '.')))) {
  238. var metric = $.extend({},_metric,true);
  239. metric.data = Em.get(item, _metric.metric_path.replace(/\//g, '.'));
  240. metric.hostName = item.HostRoles.host_name;
  241. this.get('metrics').pushObject(metric);
  242. }
  243. }, this);
  244. },this);
  245. },
  246. getHostsMetrics: function(request) {
  247. return App.ajax.send({
  248. name: 'widgets.hosts.metrics.get',
  249. sender: this,
  250. data: {
  251. metricPaths: request.metric_paths.join(',')
  252. },
  253. success: 'getHostsMetricsSuccessCallback'
  254. });
  255. },
  256. getHostsMetricsSuccessCallback: function(data) {
  257. var metrics = this.get('content.metrics');
  258. data.items.forEach(function(item){
  259. metrics.forEach(function (_metric,index) {
  260. if (!Em.isNone(Em.get(item, _metric.metric_path.replace(/\//g, '.')))) {
  261. var metric = $.extend({},_metric,true);
  262. metric.data = Em.get(item, _metric.metric_path.replace(/\//g, '.'));
  263. metric.hostName = item.Hosts.host_name;
  264. this.get('metrics').pushObject(metric);
  265. }
  266. }, this);
  267. },this);
  268. },
  269. /**
  270. * callback on metrics loaded
  271. */
  272. onMetricsLoaded: function () {
  273. var self = this;
  274. this.set('isLoaded', true);
  275. this.drawWidget();
  276. setTimeout(function () {
  277. self.loadMetrics();
  278. }, App.contentUpdateInterval);
  279. },
  280. /**
  281. * draw widget
  282. */
  283. drawWidget: function () {
  284. if (this.get('isLoaded')) {
  285. this.calculateValues();
  286. this.set('value', this.get('content.values')[0] && this.get('content.values')[0].computedValue);
  287. }
  288. },
  289. /**
  290. * calculate series datasets for graph widgets
  291. */
  292. calculateValues: function () {
  293. var metrics = this.get('metrics');
  294. var displayUnit = this.get('content.properties.display_unit');
  295. this.get('content.values').forEach(function (value) {
  296. var computeExpression = this.computeExpression(this.extractExpressions(value), metrics);
  297. value.computedValue = value.value.replace(this.get('EXPRESSION_REGEX'), function (match) {
  298. return (!Em.isNone(computeExpression[match])) ? computeExpression[match] + (displayUnit || "") : Em.I18n.t('common.na');
  299. });
  300. }, this);
  301. },
  302. /**
  303. * extract expressions
  304. * Example:
  305. * input: "${a/b} equal ${b+a}"
  306. * expressions: ['a/b', 'b+a']
  307. *
  308. * @param {object} input
  309. * @returns {Array}
  310. */
  311. extractExpressions: function (input) {
  312. var pattern = this.get('EXPRESSION_REGEX'),
  313. expressions = [],
  314. match;
  315. while (match = pattern.exec(input.value)) {
  316. expressions.push(match[1]);
  317. }
  318. return expressions;
  319. },
  320. /**
  321. * compute expression
  322. * @param expressions
  323. * @param metrics
  324. * @returns {object}
  325. */
  326. computeExpression: function (expressions, metrics) {
  327. var result = {};
  328. expressions.forEach(function (_expression) {
  329. var validExpression = true;
  330. var value = "";
  331. //replace values with metrics data
  332. var beforeCompute = _expression.replace(this.get('VALUE_NAME_REGEX'), function (match) {
  333. if (window.isNaN(match)) {
  334. if (metrics.someProperty('name', match)) {
  335. return metrics.findProperty('name', match).data;
  336. } else {
  337. validExpression = false;
  338. console.error('Metrics with name "' + match + '" not found to compute expression');
  339. }
  340. } else {
  341. return match;
  342. }
  343. });
  344. //check for correct math expression
  345. if (!(validExpression && this.get('MATH_EXPRESSION_REGEX').test(beforeCompute))) {
  346. validExpression = false;
  347. console.error('Value for metric is not correct mathematical expression: ' + beforeCompute);
  348. }
  349. result['${' + _expression + '}'] = (validExpression) ? Number(window.eval(beforeCompute)).toString() : value;
  350. }, this);
  351. return result;
  352. },
  353. /*
  354. * make call when clicking on "remove icon" on widget
  355. */
  356. hideWidget: function (event) {
  357. this.get('controller').hideWidget(
  358. {
  359. context: Em.Object.create({
  360. id: event.context
  361. })
  362. }
  363. );
  364. },
  365. /*
  366. * make call when clicking on "clone icon" on widget
  367. */
  368. cloneWidget: function (event) {
  369. var self = this;
  370. return App.showConfirmationPopup(
  371. function() {
  372. self.postWidgetDefinition();
  373. },
  374. Em.I18n.t('widget.clone.body').format(self.get('content.displayName')),
  375. null,
  376. null,
  377. Em.I18n.t('common.clone')
  378. );
  379. },
  380. /**
  381. * collect all needed data to create new widget
  382. * @returns {{WidgetInfo: {widget_name: *, display_name: *, widget_type: *, description: *, scope: *, metrics: *, values: *, properties: *}}}
  383. */
  384. collectWidgetData: function () {
  385. return {
  386. WidgetInfo: {
  387. widget_name: this.get('content.widgetName'),
  388. display_name: this.get('content.displayName'),
  389. widget_type: this.get('content.widgetType'),
  390. description: this.get('content.widgetDescription'),
  391. scope: this.get('content.scope'),
  392. "metrics": this.get('content.metrics').map(function (metric) {
  393. return {
  394. "name": metric.name,
  395. "service_name": metric.serviceName,
  396. "component_name": metric.componentName,
  397. "metric_path": metric.metric_path,
  398. "category": metric.category
  399. }
  400. }),
  401. values: this.get('content.values'),
  402. properties: this.get('content.properties')
  403. }
  404. };
  405. },
  406. /**
  407. * post widget definition to server
  408. * @returns {$.ajax}
  409. */
  410. postWidgetDefinition: function () {
  411. return App.ajax.send({
  412. name: 'widgets.wizard.add',
  413. sender: this,
  414. data: {
  415. data: this.collectWidgetData()
  416. },
  417. success: 'postWidgetDefinitionSuccessCallback'
  418. });
  419. },
  420. postWidgetDefinitionSuccessCallback: function() {
  421. },
  422. /*
  423. * make call when clicking on "edit icon" on widget
  424. */
  425. editWidget: function (event) {
  426. this.get('controller').editWidget(this.get('content'));
  427. }
  428. });
  429. App.WidgetPreviewMixin = Ember.Mixin.create({
  430. beforeRender: Em.K,
  431. isLoaded: true,
  432. metrics: [],
  433. content: Em.Object.create({
  434. widgetName: 'mock-widget',
  435. values: []
  436. }),
  437. drawWidget: function () {
  438. this.loadMetrics();
  439. this.get('content').setProperties({
  440. 'values': this.get('controller.widgetValues'),
  441. 'properties': this.get('controller.widgetProperties'),
  442. 'displayName': this.get('controller.widgetName')
  443. });
  444. this._super();
  445. }.observes('controller.widgetProperties', 'controller.widgetValues', 'controller.widgetMetrics', 'controller.widgetName'),
  446. loadMetrics: function () {
  447. var metrics = [];
  448. this.get('controller.widgetMetrics').forEach(function (metric) {
  449. metrics.push({
  450. name: metric.name,
  451. data: this.get('MOCK_VALUE')
  452. });
  453. }, this);
  454. this.set('metrics', metrics);
  455. }
  456. });