step2_controller.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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.WidgetWizardStep2Controller = Em.Controller.extend({
  20. name: "widgetWizardStep2Controller",
  21. EXPRESSION_PREFIX: 'Expression',
  22. /**
  23. * @type {RegExp}
  24. * @const
  25. */
  26. EXPRESSION_REGEX: /\$\{([\w\s\.\,\+\-\*\/\(\)\:\=\[\]]*)\}/g,
  27. /**
  28. * list of operators that can be used in expression
  29. * @type {Array}
  30. * @constant
  31. */
  32. OPERATORS: ["+", "-", "*", "/", "(", ")"],
  33. /**
  34. * actual values of properties in API format
  35. * @type {object}
  36. */
  37. widgetProperties: {},
  38. /**
  39. * @type {Array}
  40. */
  41. widgetValues: [],
  42. /**
  43. * @type {Array}
  44. */
  45. widgetMetrics: [],
  46. /**
  47. * @type {Array}
  48. */
  49. expressions: [],
  50. /**
  51. * used only for GRAPH widget
  52. * @type {Array}
  53. */
  54. dataSets: [],
  55. /**
  56. * content of template of Template widget
  57. * @type {string}
  58. */
  59. templateValue: '',
  60. /**
  61. * views of properties
  62. * @type {Array}
  63. */
  64. widgetPropertiesViews: [],
  65. /**
  66. * @type {boolean}
  67. */
  68. isEditWidget: Em.computed.equal('content.controllerName', 'widgetEditController'),
  69. /**
  70. * metrics filtered by type
  71. * @type {Array}
  72. */
  73. filteredMetrics: function () {
  74. var type = this.get('content.widgetType');
  75. return this.get('content.allMetrics').filter(function (metric) {
  76. if (type === 'GRAPH') {
  77. return metric.temporal;
  78. } else {
  79. return metric.point_in_time;
  80. }
  81. }, this);
  82. }.property('content.allMetrics'),
  83. /**
  84. * @type {boolean}
  85. */
  86. isSubmitDisabled: function() {
  87. if (this.get('widgetPropertiesViews').someProperty('isValid', false)) {
  88. return true;
  89. }
  90. switch (this.get('content.widgetType')) {
  91. case "NUMBER":
  92. case "GAUGE":
  93. return !this.isExpressionComplete(this.get('expressions')[0]);
  94. case "GRAPH":
  95. return !this.isGraphDataComplete(this.get('dataSets'));
  96. case "TEMPLATE":
  97. return this.get('isTemplateInvalid') || !this.isTemplateDataComplete(this.get('expressions'), this.get('templateValue'));
  98. }
  99. return false;
  100. }.property(
  101. 'widgetPropertiesViews.@each.isValid',
  102. 'dataSets.@each.label',
  103. 'templateValue', 'isTemplateInvalid'
  104. ),
  105. /**
  106. * check whether data of expression is complete
  107. * @param {Em.Object} expression
  108. * @returns {boolean}
  109. */
  110. isExpressionComplete: function (expression) {
  111. return Boolean(expression && !expression.get('isInvalid') && !expression.get('isEmpty'));
  112. },
  113. /**
  114. * check whether data of graph widget is complete
  115. * @param dataSets
  116. * @returns {boolean} isComplete
  117. */
  118. isGraphDataComplete: function (dataSets) {
  119. var isComplete = Boolean(dataSets.length);
  120. for (var i = 0; i < dataSets.length; i++) {
  121. if (dataSets[i].get('label').trim() === '' || !this.isExpressionComplete(dataSets[i].get('expression'))) {
  122. isComplete = false;
  123. break;
  124. }
  125. }
  126. return isComplete;
  127. },
  128. /**
  129. * check whether data of template widget is complete
  130. * @param {Array} expressions
  131. * @param {string} templateValue
  132. * @returns {boolean} isComplete
  133. */
  134. isTemplateDataComplete: function (expressions, templateValue) {
  135. var isComplete = Boolean(expressions.length > 0 && templateValue.trim() !== '');
  136. if (isComplete) {
  137. for (var i = 0; i < expressions.length; i++) {
  138. if (!this.isExpressionComplete(expressions[i])) {
  139. isComplete = false;
  140. break;
  141. }
  142. }
  143. }
  144. return isComplete;
  145. },
  146. /**
  147. * Add data set
  148. * @param {object|null} event
  149. * @param {boolean} isDefault
  150. * @returns {number} id
  151. */
  152. addDataSet: function(event, isDefault) {
  153. var id = (isDefault) ? 1 :(Math.max.apply(this, this.get('dataSets').mapProperty('id')) + 1);
  154. this.get('dataSets').pushObject(Em.Object.create({
  155. id: id,
  156. label: Em.I18n.t('dashboard.widgets.wizard.step2.dataSeries').format(id),
  157. isRemovable: !isDefault,
  158. expression: Em.Object.create({
  159. id: id,
  160. data: [],
  161. isInvalid: false,
  162. isEmpty: function () {
  163. return (this.get('data.length') === 0);
  164. }.property('data.length')
  165. })
  166. }));
  167. return id;
  168. },
  169. /**
  170. * Remove data set
  171. * @param {object} event
  172. */
  173. removeDataSet: function(event) {
  174. this.get('dataSets').removeObject(event.context);
  175. },
  176. /**
  177. * Add expression
  178. * @param {object|null} event
  179. * @param {boolean} isDefault
  180. * @returns {number} id
  181. */
  182. addExpression: function(event, isDefault) {
  183. var id = (isDefault) ? 1 :(Math.max.apply(this, this.get('expressions').mapProperty('id')) + 1);
  184. this.get('expressions').pushObject(Em.Object.create({
  185. id: id,
  186. isRemovable: !isDefault,
  187. data: [],
  188. alias: '{{' + this.get('EXPRESSION_PREFIX') + id + '}}',
  189. isInvalid: false,
  190. isEmpty: function () {
  191. return (this.get('data.length') === 0);
  192. }.property('data.length')
  193. }));
  194. return id;
  195. },
  196. /**
  197. * Remove expression
  198. * @param {object} event
  199. */
  200. removeExpression: function(event) {
  201. this.get('expressions').removeObject(event.context);
  202. },
  203. /**
  204. * initialize data
  205. * widget should have at least one expression or dataSet
  206. */
  207. initWidgetData: function() {
  208. this.set('widgetProperties', this.get('content.widgetProperties'));
  209. this.set('widgetValues', this.get('content.widgetValues'));
  210. this.set('widgetMetrics', this.get('content.widgetMetrics'));
  211. if (this.get('expressions.length') === 0) {
  212. this.addExpression(null, true);
  213. }
  214. if (this.get('dataSets.length') === 0) {
  215. this.addDataSet(null, true);
  216. }
  217. },
  218. /**
  219. * update preview widget with latest expression data
  220. * Note: in order to draw widget it should be converted to API format of widget
  221. */
  222. updateExpressions: function () {
  223. var widgetType = this.get('content.widgetType');
  224. var expressionData = {
  225. values: [],
  226. metrics: []
  227. };
  228. if (this.get('expressions').length > 0 && this.get('dataSets').length > 0) {
  229. switch (widgetType) {
  230. case 'GAUGE':
  231. case 'NUMBER':
  232. expressionData = this.parseExpression(this.get('expressions')[0]);
  233. expressionData.values = [
  234. {
  235. name: "",
  236. value: expressionData.value
  237. }
  238. ];
  239. break;
  240. case 'TEMPLATE':
  241. expressionData = this.parseTemplateExpression(this.get('templateValue'), this.get('expressions'));
  242. break;
  243. case 'GRAPH':
  244. expressionData = this.parseGraphDataset(this.get('dataSets'));
  245. break;
  246. }
  247. }
  248. this.set('widgetValues', expressionData.values);
  249. this.set('widgetMetrics', expressionData.metrics);
  250. }.observes('templateValue', 'dataSets.@each.label'),
  251. /**
  252. * parse Graph data set
  253. * @param {Array} dataSets
  254. * @returns {{metrics: Array, values: Array}}
  255. */
  256. parseGraphDataset: function (dataSets) {
  257. var metrics = [];
  258. var values = [];
  259. dataSets.forEach(function (dataSet) {
  260. var result = this.parseExpression(dataSet.get('expression'));
  261. metrics.pushObjects(result.metrics);
  262. values.push({
  263. name: dataSet.get('label'),
  264. value: result.value
  265. });
  266. }, this);
  267. return {
  268. metrics: metrics,
  269. values: values
  270. };
  271. },
  272. /**
  273. * parse expression from template
  274. * @param {string} templateValue
  275. * @param {Array} expressions
  276. * @returns {{metrics: Array, values: {value: *}[]}}
  277. */
  278. parseTemplateExpression: function (templateValue, expressions) {
  279. var metrics = [];
  280. var self = this;
  281. // check if there is invalid expression name eg. {{myExpre}}
  282. var isTemplateInvalid = false;
  283. var validExpressionName = /\{\{(Expression[\d])\}\}/g;
  284. var expressionName = /\{\{((?!}}).)*\}\}/g;
  285. if (templateValue) {
  286. var expressionNames = templateValue.match(expressionName);
  287. if (expressionNames) {
  288. expressionNames.forEach(function(name) {
  289. if (!name.match(validExpressionName)) {
  290. isTemplateInvalid = true;
  291. }
  292. });
  293. }
  294. }
  295. this.set('isTemplateInvalid', isTemplateInvalid);
  296. var expression = templateValue.replace(/\{\{Expression[\d]\}\}/g, function (exp) {
  297. var result;
  298. if (expressions.someProperty('alias', exp)) {
  299. result = self.parseExpression(expressions.findProperty('alias', exp));
  300. metrics.pushObjects(result.metrics);
  301. return result.value;
  302. }
  303. return exp;
  304. });
  305. return {
  306. metrics: metrics,
  307. values: [
  308. {
  309. value: expression
  310. }
  311. ]
  312. };
  313. },
  314. /**
  315. *
  316. * @param {object} expression
  317. * @returns {{metrics: Array, value: string}}
  318. */
  319. parseExpression: function (expression) {
  320. var value = '';
  321. var metrics = [];
  322. if (expression.data.length > 0) {
  323. value = '${';
  324. expression.data.forEach(function (element) {
  325. if (element.isMetric) {
  326. var metricObj = {
  327. "name": element.name,
  328. "service_name": element.serviceName,
  329. "component_name": element.componentName,
  330. "metric_path": element.metricPath
  331. };
  332. if (element.hostComponentCriteria) {
  333. metricObj.host_component_criteria = element.hostComponentCriteria;
  334. }
  335. metrics.push(metricObj);
  336. }
  337. value += element.name;
  338. }, this);
  339. value += '}';
  340. }
  341. return {
  342. metrics: metrics,
  343. value: value
  344. };
  345. },
  346. /**
  347. * update properties of preview widget
  348. */
  349. updateProperties: function () {
  350. var result = {};
  351. this.get('widgetPropertiesViews').forEach(function (property) {
  352. for (var key in property.valueMap) {
  353. result[property.valueMap[key]] = property.get(key);
  354. }
  355. });
  356. this.set('widgetProperties', result);
  357. }.observes('widgetPropertiesViews.@each.value', 'widgetPropertiesViews.@each.bigValue', 'widgetPropertiesViews.@each.smallValue'),
  358. /*
  359. * Generate the thresholds, unit, time range.etc object based on the widget type selected in previous step.
  360. */
  361. renderProperties: function () {
  362. var widgetProperties = App.WidgetType.find(this.get('content.widgetType')).get('properties');
  363. var propertiesData = this.get('widgetProperties');
  364. var result = [];
  365. widgetProperties.forEach(function (property) {
  366. var definition = App.WidgetPropertyTypes.findProperty('name', property.name);
  367. property = $.extend({}, property);
  368. //restore previous values
  369. for (var key in definition.valueMap) {
  370. if (propertiesData[definition.valueMap[key]]) {
  371. property[key] = propertiesData[definition.valueMap[key]];
  372. }
  373. }
  374. result.pushObject(App.WidgetProperty.create($.extend(definition, property)));
  375. });
  376. this.set('widgetPropertiesViews', result);
  377. },
  378. /**
  379. * convert data with model format to editable format
  380. * Note: in order to edit widget expression it should be converted to editable format
  381. */
  382. convertData: function() {
  383. var self = this;
  384. var expressionId = 0;
  385. var widgetValues = this.get('content.widgetValues');
  386. var widgetMetrics = this.get('content.widgetMetrics');
  387. this.get('expressions').clear();
  388. this.get('dataSets').clear();
  389. if (widgetValues.length > 0) {
  390. switch (this.get('content.widgetType')) {
  391. case 'NUMBER':
  392. case 'GAUGE':
  393. var id = this.addExpression(null, true);
  394. this.get('expressions').findProperty('id', id).set('data', this.parseValue(widgetValues[0].value, widgetMetrics)[0]);
  395. break;
  396. case 'TEMPLATE':
  397. this.parseValue(widgetValues[0].value, widgetMetrics).forEach(function (item, index) {
  398. var id = this.addExpression(null, (index === 0));
  399. this.get('expressions').findProperty('id', id).set('data', item);
  400. }, this);
  401. this.set('templateValue', widgetValues[0].value.replace(this.get('EXPRESSION_REGEX'), function () {
  402. return '{{' + self.get('EXPRESSION_PREFIX') + ++expressionId + '}}';
  403. }));
  404. break;
  405. case 'GRAPH':
  406. widgetValues.forEach(function (value, index) {
  407. var id = this.addDataSet(null, (index === 0));
  408. var dataSet = this.get('dataSets').findProperty('id', id);
  409. dataSet.set('label', value.name);
  410. dataSet.set('expression.data', this.parseValue(value.value, widgetMetrics)[0]);
  411. }, this);
  412. break;
  413. }
  414. }
  415. },
  416. /**
  417. * parse value
  418. * @param value
  419. * @param metrics
  420. * @returns {Array}
  421. */
  422. parseValue: function(value, metrics) {
  423. var pattern = this.get('EXPRESSION_REGEX'),
  424. expressions = [],
  425. match;
  426. while (match = pattern.exec(value)) {
  427. expressions.push(this.getExpressionData(match[1], metrics));
  428. }
  429. return expressions;
  430. },
  431. /**
  432. * format values into expression data objects
  433. * @param {string} expression
  434. * @param {Array} metrics
  435. * @returns {Array}
  436. */
  437. getExpressionData: function(expression, metrics) {
  438. var str = '';
  439. var data = [];
  440. var id = 0;
  441. for (var i = 0, l = expression.length; i < l; i++) {
  442. if (this.get('OPERATORS').contains(expression[i])) {
  443. if (str.trim().length > 0) {
  444. data.pushObject(this.getExpressionVariable(str.trim(), ++id, metrics));
  445. str = '';
  446. }
  447. data.pushObject(Em.Object.create({
  448. id: ++id,
  449. name: expression[i],
  450. isOperator: true
  451. }));
  452. } else {
  453. str += expression[i];
  454. }
  455. }
  456. if (str.trim().length > 0) {
  457. data.pushObject(this.getExpressionVariable(str.trim(), ++id, metrics));
  458. }
  459. return data;
  460. },
  461. /**
  462. * get variable of expression
  463. * could be name of metric "m1" or constant number "1"
  464. * @param {string} name
  465. * @param {Array} metrics
  466. * @param {number} id
  467. * @returns {Em.Object}
  468. */
  469. getExpressionVariable: function (name, id, metrics) {
  470. var metric;
  471. if (isNaN(Number(name))) {
  472. metric = metrics.findProperty('name', name);
  473. return Em.Object.create({
  474. id: id,
  475. name: metric.name,
  476. isMetric: true,
  477. componentName: metric.component_name,
  478. serviceName: metric.service_name,
  479. metricPath: metric.metric_path,
  480. hostComponentCriteria: metric.host_component_criteria
  481. });
  482. } else {
  483. return Em.Object.create({
  484. id: id,
  485. name: name,
  486. isNumber: true
  487. });
  488. }
  489. },
  490. next: function () {
  491. App.router.send('next');
  492. }
  493. });