step2_controller.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  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: Em.computed.equal('data.length', 0)
  163. })
  164. }));
  165. return id;
  166. },
  167. /**
  168. * Remove data set
  169. * @param {object} event
  170. */
  171. removeDataSet: function(event) {
  172. this.get('dataSets').removeObject(event.context);
  173. },
  174. /**
  175. * Add expression
  176. * @param {object|null} event
  177. * @param {boolean} isDefault
  178. * @returns {number} id
  179. */
  180. addExpression: function(event, isDefault) {
  181. var id = (isDefault) ? 1 :(Math.max.apply(this, this.get('expressions').mapProperty('id')) + 1);
  182. this.get('expressions').pushObject(Em.Object.create({
  183. id: id,
  184. isRemovable: !isDefault,
  185. data: [],
  186. alias: '{{' + this.get('EXPRESSION_PREFIX') + id + '}}',
  187. isInvalid: false,
  188. isEmpty: Em.computed.equal('data.length', 0)
  189. }));
  190. return id;
  191. },
  192. /**
  193. * Remove expression
  194. * @param {object} event
  195. */
  196. removeExpression: function(event) {
  197. this.get('expressions').removeObject(event.context);
  198. },
  199. /**
  200. * initialize data
  201. * widget should have at least one expression or dataSet
  202. */
  203. initWidgetData: function() {
  204. this.set('widgetProperties', this.get('content.widgetProperties'));
  205. this.set('widgetValues', this.get('content.widgetValues'));
  206. this.set('widgetMetrics', this.get('content.widgetMetrics'));
  207. if (this.get('expressions.length') === 0) {
  208. this.addExpression(null, true);
  209. }
  210. if (this.get('dataSets.length') === 0) {
  211. this.addDataSet(null, true);
  212. }
  213. },
  214. /**
  215. * update preview widget with latest expression data
  216. * Note: in order to draw widget it should be converted to API format of widget
  217. */
  218. updateExpressions: function () {
  219. var widgetType = this.get('content.widgetType');
  220. var expressionData = {
  221. values: [],
  222. metrics: []
  223. };
  224. if (this.get('expressions').length > 0 && this.get('dataSets').length > 0) {
  225. switch (widgetType) {
  226. case 'GAUGE':
  227. case 'NUMBER':
  228. expressionData = this.parseExpression(this.get('expressions')[0]);
  229. expressionData.values = [
  230. {
  231. name: "",
  232. value: expressionData.value
  233. }
  234. ];
  235. break;
  236. case 'TEMPLATE':
  237. expressionData = this.parseTemplateExpression(this.get('templateValue'), this.get('expressions'));
  238. break;
  239. case 'GRAPH':
  240. expressionData = this.parseGraphDataset(this.get('dataSets'));
  241. break;
  242. }
  243. }
  244. this.set('widgetValues', expressionData.values);
  245. this.set('widgetMetrics', expressionData.metrics);
  246. }.observes('templateValue', 'dataSets.@each.label'),
  247. /**
  248. * parse Graph data set
  249. * @param {Array} dataSets
  250. * @returns {{metrics: Array, values: Array}}
  251. */
  252. parseGraphDataset: function (dataSets) {
  253. var metrics = [];
  254. var values = [];
  255. dataSets.forEach(function (dataSet) {
  256. var result = this.parseExpression(dataSet.get('expression'));
  257. metrics.pushObjects(result.metrics);
  258. values.push({
  259. name: dataSet.get('label'),
  260. value: result.value
  261. });
  262. }, this);
  263. return {
  264. metrics: metrics,
  265. values: values
  266. };
  267. },
  268. /**
  269. * parse expression from template
  270. * @param {string} templateValue
  271. * @param {Array} expressions
  272. * @returns {{metrics: Array, values: {value: *}[]}}
  273. */
  274. parseTemplateExpression: function (templateValue, expressions) {
  275. var metrics = [];
  276. var self = this;
  277. // check if there is invalid expression name eg. {{myExpre}}
  278. var isTemplateInvalid = false;
  279. var validExpressionName = /\{\{(Expression[\d])\}\}/g;
  280. var expressionName = /\{\{((?!}}).)*\}\}/g;
  281. if (templateValue) {
  282. var expressionNames = templateValue.match(expressionName);
  283. if (expressionNames) {
  284. expressionNames.forEach(function(name) {
  285. if (!name.match(validExpressionName)) {
  286. isTemplateInvalid = true;
  287. }
  288. });
  289. }
  290. }
  291. this.set('isTemplateInvalid', isTemplateInvalid);
  292. var expression = templateValue.replace(/\{\{Expression[\d]\}\}/g, function (exp) {
  293. var result;
  294. if (expressions.someProperty('alias', exp)) {
  295. result = self.parseExpression(expressions.findProperty('alias', exp));
  296. metrics.pushObjects(result.metrics);
  297. return result.value;
  298. }
  299. return exp;
  300. });
  301. return {
  302. metrics: metrics,
  303. values: [
  304. {
  305. value: expression
  306. }
  307. ]
  308. };
  309. },
  310. /**
  311. *
  312. * @param {object} expression
  313. * @returns {{metrics: Array, value: string}}
  314. */
  315. parseExpression: function (expression) {
  316. var value = '';
  317. var metrics = [];
  318. if (expression.data.length > 0) {
  319. value = '${';
  320. expression.data.forEach(function (element) {
  321. if (element.isMetric) {
  322. var metricObj = {
  323. "name": element.name,
  324. "service_name": element.serviceName,
  325. "component_name": element.componentName,
  326. "metric_path": element.metricPath
  327. };
  328. if (element.hostComponentCriteria) {
  329. metricObj.host_component_criteria = element.hostComponentCriteria;
  330. }
  331. metrics.push(metricObj);
  332. }
  333. value += element.name;
  334. }, this);
  335. value += '}';
  336. }
  337. return {
  338. metrics: metrics,
  339. value: value
  340. };
  341. },
  342. /**
  343. * update properties of preview widget
  344. */
  345. updateProperties: function () {
  346. var result = {};
  347. this.get('widgetPropertiesViews').forEach(function (property) {
  348. for (var key in property.valueMap) {
  349. result[property.valueMap[key]] = property.get(key);
  350. }
  351. });
  352. this.set('widgetProperties', result);
  353. }.observes('widgetPropertiesViews.@each.value', 'widgetPropertiesViews.@each.bigValue', 'widgetPropertiesViews.@each.smallValue'),
  354. /*
  355. * Generate the thresholds, unit, time range.etc object based on the widget type selected in previous step.
  356. */
  357. renderProperties: function () {
  358. var widgetProperties = App.WidgetType.find(this.get('content.widgetType')).get('properties');
  359. var propertiesData = this.get('widgetProperties');
  360. var result = [];
  361. widgetProperties.forEach(function (property) {
  362. var definition = App.WidgetPropertyTypes.findProperty('name', property.name);
  363. property = $.extend({}, property);
  364. //restore previous values
  365. for (var key in definition.valueMap) {
  366. if (propertiesData[definition.valueMap[key]]) {
  367. property[key] = propertiesData[definition.valueMap[key]];
  368. }
  369. }
  370. result.pushObject(App.WidgetProperty.create($.extend(definition, property)));
  371. });
  372. this.set('widgetPropertiesViews', result);
  373. },
  374. /**
  375. * convert data with model format to editable format
  376. * Note: in order to edit widget expression it should be converted to editable format
  377. */
  378. convertData: function() {
  379. var self = this;
  380. var expressionId = 0;
  381. var widgetValues = this.get('content.widgetValues');
  382. var widgetMetrics = this.get('content.widgetMetrics');
  383. this.get('expressions').clear();
  384. this.get('dataSets').clear();
  385. if (widgetValues.length > 0) {
  386. switch (this.get('content.widgetType')) {
  387. case 'NUMBER':
  388. case 'GAUGE':
  389. var id = this.addExpression(null, true);
  390. this.get('expressions').findProperty('id', id).set('data', this.parseValue(widgetValues[0].value, widgetMetrics)[0]);
  391. break;
  392. case 'TEMPLATE':
  393. this.parseValue(widgetValues[0].value, widgetMetrics).forEach(function (item, index) {
  394. var id = this.addExpression(null, (index === 0));
  395. this.get('expressions').findProperty('id', id).set('data', item);
  396. }, this);
  397. this.set('templateValue', widgetValues[0].value.replace(this.get('EXPRESSION_REGEX'), function () {
  398. return '{{' + self.get('EXPRESSION_PREFIX') + ++expressionId + '}}';
  399. }));
  400. break;
  401. case 'GRAPH':
  402. widgetValues.forEach(function (value, index) {
  403. var id = this.addDataSet(null, (index === 0));
  404. var dataSet = this.get('dataSets').findProperty('id', id);
  405. dataSet.set('label', value.name);
  406. dataSet.set('expression.data', this.parseValue(value.value, widgetMetrics)[0]);
  407. }, this);
  408. break;
  409. }
  410. }
  411. },
  412. /**
  413. * parse value
  414. * @param value
  415. * @param metrics
  416. * @returns {Array}
  417. */
  418. parseValue: function(value, metrics) {
  419. var pattern = this.get('EXPRESSION_REGEX'),
  420. expressions = [],
  421. match;
  422. while (match = pattern.exec(value)) {
  423. expressions.push(this.getExpressionData(match[1], metrics));
  424. }
  425. return expressions;
  426. },
  427. /**
  428. * format values into expression data objects
  429. * @param {string} expression
  430. * @param {Array} metrics
  431. * @returns {Array}
  432. */
  433. getExpressionData: function(expression, metrics) {
  434. var str = '';
  435. var data = [];
  436. var id = 0;
  437. for (var i = 0, l = expression.length; i < l; i++) {
  438. if (this.get('OPERATORS').contains(expression[i])) {
  439. if (str.trim().length > 0) {
  440. data.pushObject(this.getExpressionVariable(str.trim(), ++id, metrics));
  441. str = '';
  442. }
  443. data.pushObject(Em.Object.create({
  444. id: ++id,
  445. name: expression[i],
  446. isOperator: true
  447. }));
  448. } else {
  449. str += expression[i];
  450. }
  451. }
  452. if (str.trim().length > 0) {
  453. data.pushObject(this.getExpressionVariable(str.trim(), ++id, metrics));
  454. }
  455. return data;
  456. },
  457. /**
  458. * get variable of expression
  459. * could be name of metric "m1" or constant number "1"
  460. * @param {string} name
  461. * @param {Array} metrics
  462. * @param {number} id
  463. * @returns {Em.Object}
  464. */
  465. getExpressionVariable: function (name, id, metrics) {
  466. var metric;
  467. if (isNaN(Number(name))) {
  468. metric = metrics.findProperty('name', name);
  469. return Em.Object.create({
  470. id: id,
  471. name: metric.name,
  472. isMetric: true,
  473. componentName: metric.component_name,
  474. serviceName: metric.service_name,
  475. metricPath: metric.metric_path,
  476. hostComponentCriteria: metric.host_component_criteria
  477. });
  478. } else {
  479. return Em.Object.create({
  480. id: id,
  481. name: name,
  482. isNumber: true
  483. });
  484. }
  485. },
  486. next: function () {
  487. App.router.send('next');
  488. }
  489. });