step2_controller.js 14 KB

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