expression_view.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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 misc = require('utils/misc');
  19. App.WidgetWizardExpressionView = Em.View.extend({
  20. templateName: require('templates/main/service/widgets/create/expression'),
  21. /**
  22. * @type {Array}
  23. */
  24. classNames: ['metric-container'],
  25. /**
  26. * @type {Array}
  27. */
  28. classNameBindings: ['isInvalid'],
  29. /**
  30. * list of operators that can be used in expression
  31. * @type {Array}
  32. * @constant
  33. */
  34. OPERATORS: ["+", "-", "*", "/", "(", ")"],
  35. /**
  36. * contains expression data before editing in order to restore previous state
  37. */
  38. dataBefore: [],
  39. /**
  40. * @type {Ember.Object}
  41. */
  42. expression: null,
  43. /**
  44. * @type {boolean}
  45. */
  46. isInvalid: false,
  47. /**
  48. * add operator to expression data
  49. * @param event
  50. */
  51. addOperator: function (event) {
  52. var data = this.get('expression.data');
  53. var lastId = (data.length > 0) ? Math.max.apply(this, data.mapProperty('id')) : 0;
  54. data.pushObject(Em.Object.create({
  55. id: ++lastId,
  56. name: event.context,
  57. isOperator: true
  58. }));
  59. },
  60. /**
  61. * redraw expression
  62. * NOTE: needed in order to avoid collision between scrollable lib and metric action event
  63. */
  64. redrawField: function () {
  65. this.set('expression.data', misc.sortByOrder($(this.get('element')).find('.metric-instance').map(function () {
  66. return this.id;
  67. }), this.get('expression.data')));
  68. },
  69. /**
  70. * enable metric edit area
  71. */
  72. startEdit: function () {
  73. var self = this;
  74. this.set('dataBefore', this.get('expression.data').slice(0));
  75. this.set('expression.editMode', true);
  76. this.propertyDidChange('expression');
  77. Em.run.next(function () {
  78. $(self.get('element')).find('.metric-field').sortable({
  79. items: "> div",
  80. tolerance: "pointer",
  81. scroll: false,
  82. update: function () {
  83. self.redrawField();
  84. }
  85. }).disableSelection();
  86. });
  87. },
  88. /**
  89. * discard changes and disable metric edit area
  90. */
  91. cancelEdit: function () {
  92. this.set('expression.data', this.get('dataBefore'));
  93. this.set('expression.editMode', false);
  94. this.propertyDidChange('expression');
  95. },
  96. /**
  97. * save changes and disable metric edit area
  98. */
  99. saveMetrics: function () {
  100. this.set('expression.editMode', false);
  101. this.propertyDidChange('expression');
  102. },
  103. /**
  104. * remove metric or operator from expression
  105. * @param {object} event
  106. */
  107. removeElement: function (event) {
  108. this.get('expression.data').removeObject(event.context);
  109. },
  110. validate: function () {
  111. //number 1 used as substitute to test expression to be mathematically correct
  112. var testNumber = 1;
  113. var isInvalid = true;
  114. var expression = this.get('expression.data').map(function (element) {
  115. if (element.isMetric) {
  116. return testNumber;
  117. } else {
  118. return element.name;
  119. }
  120. }, this).join(" ");
  121. if (expression.length > 0) {
  122. if (/^((\(\s)*[\d]+)[\(\)\+\-\*\/\d\s]*[\d\)]*$/.test(expression)) {
  123. try {
  124. isInvalid = !isFinite(window.eval(expression));
  125. } catch (e) {
  126. isInvalid = true;
  127. }
  128. }
  129. } else {
  130. isInvalid = false;
  131. }
  132. this.set('isInvalid', isInvalid);
  133. if (!isInvalid) this.get('controller').updateExpressions();
  134. }.observes('expression.data.length'),
  135. /**
  136. * show popup that provide ability to add metric
  137. */
  138. addMetric: function () {
  139. return App.ModalPopup.show({
  140. header: Em.I18n.t('dashboard.widgets.wizard.step2.addMetric'),
  141. classNames: ['modal-690px-width'],
  142. disablePrimary: function () {
  143. return Em.isNone(this.get('selectedMetric'));
  144. }.property('selectedMetric'),
  145. expression: this.get('expression'),
  146. /**
  147. * @type {object|null}
  148. */
  149. selectedMetric: null,
  150. /**
  151. * @type {Ember.View}
  152. * @class
  153. */
  154. bodyClass: Em.View.extend({
  155. templateName: require('templates/main/service/widgets/create/step2_add_metric'),
  156. controller: this.get('controller'),
  157. elementId: 'add-metric-popup',
  158. didInsertElement: function () {
  159. var self = this;
  160. //prevent dropdown closing on checkbox click
  161. $('html').on('click.dropdown', '.dropdown-menu li', function (e) {
  162. $(this).hasClass('keep-open') && e.stopPropagation();
  163. });
  164. $(".chosen-select").chosen({
  165. placeholder_text: Em.I18n.t('widget.create.wizard.step2.noMetricFound'),
  166. no_results_text: Em.I18n.t('widget.create.wizard.step2.noMetricFound')
  167. }).change(function (event, obj) {
  168. self.set('parentView.selectedMetric', {
  169. name: obj.selected,
  170. componentName: self.get('selectedComponent.componentName'),
  171. serviceName: self.get('selectedComponent.serviceName')
  172. });
  173. });
  174. },
  175. /**
  176. * @type {Ember.Object}
  177. * @default null
  178. */
  179. selectedComponent: null,
  180. showMore: Em.K,
  181. selectComponents: function (event) {
  182. var component = this.get('componentMap').findProperty('serviceName', event.context.get('serviceName'))
  183. .get('components').findProperty('id', event.context.get('id'));
  184. this.set('selectedComponent', component);
  185. this.set('parentView.selectedMetric', null);
  186. Em.run.next(function () {
  187. $('.chosen-select').trigger('chosen:updated');
  188. });
  189. },
  190. /**
  191. * map of components
  192. * has following hierarchy: service -> component -> metrics
  193. */
  194. componentMap: function () {
  195. var servicesMap = {};
  196. var result = [];
  197. var components = [];
  198. var masterNames = App.StackServiceComponent.find().filterProperty('isMaster').mapProperty('componentName');
  199. this.get('controller.filteredMetrics').forEach(function (metric) {
  200. var service = servicesMap[metric.service_name];
  201. var componentId = masterNames.contains(metric.component_name) ? metric.component_name + '_' + metric.level : metric.component_name;
  202. if (service) {
  203. service.count++;
  204. if (service.components[componentId]) {
  205. service.components[componentId].count++;
  206. service.components[componentId].metrics.push(metric.name);
  207. } else {
  208. service.components[componentId] = {
  209. component_name: metric.component_name,
  210. level: metric.level,
  211. count: 1,
  212. metrics: [metric.name]
  213. };
  214. }
  215. } else {
  216. servicesMap[metric.service_name] = {
  217. count: 1,
  218. components: {}
  219. };
  220. }
  221. }, this);
  222. for (var serviceName in servicesMap) {
  223. components = [];
  224. for (var componentId in servicesMap[serviceName].components) {
  225. components.push(Em.Object.create({
  226. componentName: servicesMap[serviceName].components[componentId].component_name,
  227. level: servicesMap[serviceName].components[componentId].level,
  228. displayName: function() {
  229. var stackComponent = App.StackServiceComponent.find(this.get('componentName'));
  230. if (stackComponent.get('isMaster')) {
  231. if (this.get('level') === 'COMPONENT') {
  232. return Em.I18n.t('widget.create.wizard.step2.allComponents').format(stackComponent.get('displayName'));
  233. } else {
  234. return Em.I18n.t('widget.create.wizard.step2.activeComponents').format(stackComponent.get('displayName'));
  235. }
  236. }
  237. return stackComponent.get('displayName');
  238. }.property('componentName', 'level'),
  239. count: servicesMap[serviceName].components[componentId].count,
  240. metrics: servicesMap[serviceName].components[componentId].metrics.uniq().sort(),
  241. selected: false,
  242. id: componentId,
  243. serviceName: serviceName
  244. }));
  245. }
  246. result.push(Em.Object.create({
  247. serviceName: serviceName,
  248. //in order to support accordion lists
  249. href: '#' + serviceName,
  250. displayName: App.StackService.find(serviceName).get('displayName'),
  251. count: servicesMap[serviceName].count,
  252. components: components
  253. }));
  254. }
  255. return result;
  256. }.property('controller.filteredMetrics')
  257. }),
  258. primary: Em.I18n.t('common.save'),
  259. onPrimary: function () {
  260. var data = this.get('expression.data');
  261. var lastId = (data.length > 0) ? Math.max.apply(this, data.mapProperty('id')) : 0;
  262. data.pushObject(Em.Object.create({
  263. id: ++lastId,
  264. name: this.get('selectedMetric.name'),
  265. componentName: this.get('selectedMetric.componentName'),
  266. serviceName: this.get('selectedMetric.serviceName'),
  267. isMetric: true
  268. }));
  269. this.hide();
  270. }
  271. })
  272. }
  273. });