config_widget_view.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  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. require('views/common/controls_view');
  20. /**
  21. * Common view for config widgets
  22. * @type {Em.View}
  23. */
  24. App.ConfigWidgetView = Em.View.extend(App.SupportsDependentConfigs, App.WidgetPopoverSupport, App.ConvertUnitWidgetViewMixin, App.ServiceConfigCalculateId, {
  25. /**
  26. * @type {App.ConfigProperty}
  27. */
  28. config: null,
  29. /**
  30. * Determines if user hover on widget-view
  31. * @type {boolean}
  32. */
  33. isHover: false,
  34. /**
  35. * Determines if widget controls should be disabled
  36. * @type {boolean}
  37. */
  38. disabled: false,
  39. /**
  40. * Determines if widget is editable
  41. * It true - show all control-elements (undo, override, finalize etc) for widget
  42. * If false - no widget control-elements will be shown
  43. * Bound from template
  44. * @type {boolean}
  45. */
  46. canEdit: true,
  47. canNotEdit: Em.computed.not('canEdit'),
  48. /**
  49. * Config label class attribute. Displays validation status of config.
  50. * @type {string}
  51. */
  52. configLabelClass: '',
  53. /**
  54. * defines if widget should be shown
  55. * if not, text-field with config value or label "Undefined" should be shown
  56. * @type {boolean}
  57. */
  58. doNotShowWidget: Em.computed.or('isPropertyUndefined', 'config.showAsTextBox'),
  59. /**
  60. * defines if property in not defined in selected version
  61. * in this case "Undefined" should be shown instead of widget
  62. * @type {boolean}
  63. */
  64. isPropertyUndefined: Em.computed.equal('config.value', 'Undefined'),
  65. /**
  66. * Tab where current widget placed
  67. * Bound in the template
  68. * @type {App.Tab}
  69. */
  70. tab: null,
  71. /**
  72. * Section where current widget placed
  73. * Bound in the template
  74. * @type {App.Section}
  75. */
  76. section: null,
  77. /**
  78. * Subsection where current widget placed
  79. * Bound in the template
  80. * @type {App.SubSection}
  81. */
  82. subSection: null,
  83. /**
  84. * Determines if user can switch custom widget-view to the input-field
  85. * @type {boolean}
  86. */
  87. supportSwitchToTextBox: false,
  88. /**
  89. * @type {boolean}
  90. */
  91. showPencil: Em.computed.and('supportSwitchToTextBox', '!disabled'),
  92. /**
  93. * Alias to <code>config.isOriginalSCP</code>
  94. * Should be used in the templates
  95. * Don't use original <code>config.isOriginalSCP</code> in the widget-templates!!!
  96. * @type {boolean}
  97. */
  98. isOriginalSCPBinding: 'config.isOriginalSCP',
  99. /**
  100. * Check if property validation failed for overridden property in case when its value is equal to parent
  101. * config property.
  102. * @type {boolean}
  103. */
  104. isOverrideEqualityError: function() {
  105. return this.get('config.parentSCP') && this.get('config.parentSCP.value') == this.get('config.value');
  106. }.property('config.isValid'),
  107. /**
  108. * Alias to <code>config.isComparison</code>
  109. * Should be used in the templates
  110. * Don't use original <code>config.isComparison</code> in the widget-templates!!!
  111. * @type {boolean}
  112. */
  113. isComparisonBinding: 'config.isComparison',
  114. classNameBindings:['isComparison:compare-mode', 'config.overrides.length:overridden-property'],
  115. issueMessage: '',
  116. issueView: Em.View.extend({
  117. tagName: 'i',
  118. classNames: ['icon-warning-sign'],
  119. classNameBindings: ['issueIconClass'],
  120. attributeBindings:['issueMessage:data-original-title'],
  121. /**
  122. * @type {App.ServiceConfigProperty}
  123. */
  124. config: null,
  125. /**
  126. * @type {string}
  127. */
  128. issueIconClass: '',
  129. /**
  130. * @type {string}
  131. */
  132. issueMessage: '',
  133. didInsertElement: function() {
  134. App.tooltip($(this.get('element')));
  135. this.errorLevelObserver();
  136. this.addObserver('issuedConfig.warnMessage', this, this.errorLevelObserver);
  137. this.addObserver('issuedConfig.errorMessage', this, this.errorLevelObserver);
  138. this.addObserver('parentView.isPropertyUndefined', this, this.errorLevelObserver);
  139. },
  140. willDestroyElement: function() {
  141. this.removeObserver('issuedConfig.warnMessage', this, this.errorLevelObserver);
  142. this.removeObserver('issuedConfig.errorMessage', this, this.errorLevelObserver);
  143. this.removeObserver('parentView.isPropertyUndefined', this, this.errorLevelObserver);
  144. },
  145. /**
  146. *
  147. * @method errorLevelObserver
  148. */
  149. errorLevelObserver: function() {
  150. var messageLevel = this.get('issuedConfig.errorMessage') ? 'ERROR': this.get('issuedConfig.warnMessage') ? 'WARN' : 'NONE';
  151. if (this.get('parentView.isPropertyUndefined')) {
  152. messageLevel = 'NONE';
  153. }
  154. var issue = {
  155. ERROR: {
  156. iconClass: '',
  157. message: this.get('issuedConfig.errorMessage'),
  158. configLabelClass: 'text-error'
  159. },
  160. WARN: {
  161. iconClass: 'warning',
  162. message: this.get('issuedConfig.warnMessage'),
  163. configLabelClass: 'text-warning'
  164. },
  165. NONE: {
  166. iconClass: 'hide',
  167. message: false,
  168. configLabelClass: ''
  169. }
  170. }[messageLevel];
  171. this.set('parentView.configLabelClass', issue.configLabelClass);
  172. this.set('issueIconClass', issue.iconClass);
  173. this.set('issueMessage', issue.message);
  174. this.set('parentView.issueMessage', issue.message);
  175. },
  176. /**
  177. * @type {App.ServiceConfigProperty}
  178. */
  179. issuedConfig: function() {
  180. var config = this.get('config');
  181. // check editable override
  182. if (!config.get('isEditable') && config.get('isOriginalSCP') && config.get('overrides.length') && config.get('overrides').someProperty('isEditable', true)) {
  183. config = config.get('overrides').findProperty('isEditable', true);
  184. } else if (config.get('isOriginalSCP') && config.get('isEditable')) {
  185. // use original config if it is not valid
  186. if (!config.get('isValid')) {
  187. return config;
  188. // scan overrides for non valid values and use it
  189. } else if (config.get('overrides.length') && config.get('overrides').someProperty('isValid', false)) {
  190. return config.get('overrides').findProperty('isValid', false);
  191. }
  192. }
  193. return config;
  194. }.property('config.isEditable', 'config.overrides.length')
  195. }),
  196. /**
  197. * Config name to display.
  198. * @type {String}
  199. */
  200. configLabel: Em.computed.firstNotBlank('config.stackConfigProperty.displayName', 'config.displayName', 'config.name'),
  201. /**
  202. * Error message computed in config property model
  203. * @type {String}
  204. */
  205. configErrorMessageBinding: 'config.errorMessage',
  206. /**
  207. * Determines if config-value was changed
  208. * @type {boolean}
  209. */
  210. valueIsChanged: function () {
  211. return !Em.isNone(this.get('config.savedValue')) && this.get('config.value') != this.get('config.savedValue');
  212. }.property('config.value', 'config.savedValue'),
  213. /**
  214. * Enable/disable widget state
  215. * @method toggleWidgetState
  216. */
  217. toggleWidgetState: function () {
  218. this.set('disabled', !this.get('config.isEditable'));
  219. }.observes('config.isEditable'),
  220. /**
  221. * Reset config-value to its default
  222. * @method restoreValue
  223. */
  224. restoreValue: function () {
  225. var self = this;
  226. this.set('config.value', this.get('config.savedValue'));
  227. this.sendRequestRorDependentConfigs(this.get('config')).done(function() {
  228. self.restoreDependentConfigs(self.get('config'));
  229. });
  230. if (this.get('config.supportsFinal')) {
  231. this.get('config').set('isFinal', this.get('config.savedIsFinal'));
  232. }
  233. Em.$('body > .tooltip').remove();
  234. },
  235. /**
  236. * set <code>recommendedValue<code> to config
  237. * and send request to change dependent configs
  238. * @method setRecommendedValue
  239. */
  240. setRecommendedValue: function() {
  241. var self = this;
  242. this.set('config.value', this.get('config.recommendedValue'));
  243. this.sendRequestRorDependentConfigs(this.get('config')).done(function() {
  244. if (self.get('config.value') === self.get('config.savedValue')) {
  245. self.restoreDependentConfigs(self.get('config'));
  246. }
  247. });
  248. if (this.get('config.supportsFinal')) {
  249. this.get('config').set('isFinal', this.get('config.recommendedIsFinal'));
  250. }
  251. Em.$('body > .tooltip').remove();
  252. },
  253. /**
  254. * Determines if override is allowed for <code>config</code>
  255. * @type {boolean}
  256. */
  257. overrideAllowed: function () {
  258. var config = this.get('config');
  259. if (!config) return false;
  260. return config.get('isOriginalSCP') && config.get('isPropertyOverridable') && !this.get('config.isComparison');
  261. }.property('config.isOriginalSCP', 'config.isPropertyOverridable', 'config.isComparison'),
  262. /**
  263. * Determines if undo is allowed for <code>config</code>
  264. * @type {boolean}
  265. */
  266. undoAllowed: function () {
  267. var config = this.get('config');
  268. if (!config) return false;
  269. if (!this.get('isOriginalSCP') || this.get('disabled')) return false;
  270. return !config.get('cantBeUndone') && config.get('isNotDefaultValue');
  271. }.property('config.cantBeUndone', 'config.isNotDefaultValue', 'isOriginalSCP', 'disabled'),
  272. /**
  273. * Determines if "final"-button should be shown
  274. * @type {boolean}
  275. */
  276. showFinalConfig: function () {
  277. var config = this.get('config');
  278. return config.get('isFinal') || (!config.get('isNotEditable') && this.get('isHover'));
  279. }.property('config.isFinal', 'config.isNotEditable', 'isHover'),
  280. /**
  281. *
  282. * @param {{context: App.ServiceConfigProperty}} event
  283. * @method toggleFinalFlag
  284. */
  285. toggleFinalFlag: function (event) {
  286. var configProperty = event.context;
  287. if (configProperty.get('isNotEditable')) {
  288. return;
  289. }
  290. configProperty.toggleProperty('isFinal');
  291. },
  292. /**
  293. * sync widget value with config value when dependent properties
  294. * have been loaded or changed
  295. * @method syncValueWithConfig
  296. */
  297. syncValueWithConfig: function() {
  298. this.setValue(this.get('config.value'));
  299. }.observes('controller.recommendationTimeStamp'),
  300. /**
  301. * defines if config has same config group as selected
  302. * @type {boolean}
  303. */
  304. referToSelectedGroup: function() {
  305. return this.get('controller.selectedConfigGroup.isDefault') && this.get('config.group') === null
  306. || this.get('controller.selectedConfigGroup.name') === this.get('config.group.name');
  307. }.property('controller.selectedConfigGroup.name', 'controller.selectedConfigGroup.isDefault'),
  308. didInsertElement: function () {
  309. App.tooltip($(this.get('element')).find('span'));
  310. var self = this;
  311. var element = this.$();
  312. if (element) {
  313. element.hover(function() {
  314. self.set('isHover', true);
  315. }, function() {
  316. self.set('isHover', false);
  317. });
  318. }
  319. this.initIncompatibleWidgetAsTextBox();
  320. },
  321. willInsertElement: function() {
  322. var configConditions = this.get('config.configConditions');
  323. if (configConditions && configConditions.length) {
  324. this.configValueObserver();
  325. //Add Observer to configCondition that depends on another config value
  326. var isConditionConfigDependent = configConditions.filterProperty('resource', 'config').length;
  327. if (isConditionConfigDependent) {
  328. this.addObserver('config.value', this, this.configValueObserver);
  329. }
  330. }
  331. },
  332. willDestroyElement: function() {
  333. if (this.get('config.configConditions')) {
  334. this.removeObserver('config.value', this, this.configValueObserver);
  335. }
  336. },
  337. configValueObserver: function() {
  338. var configConditions = this.get('config.configConditions');
  339. var serviceName = this.get('config.serviceName');
  340. var serviceConfigs = this.get('controller.stepConfigs').findProperty('serviceName',serviceName).get('configs');
  341. var isConditionTrue;
  342. configConditions.forEach(function(configCondition){
  343. var ifStatement = configCondition.get("if");
  344. if (configCondition.get("resource") === 'config') {
  345. isConditionTrue = App.configTheme.calculateConfigCondition(ifStatement, serviceConfigs);
  346. if (configCondition.get("type") === 'subsection' || configCondition.get("type") === 'subsectionTab') {
  347. this.changeSubsectionAttribute(configCondition, isConditionTrue);
  348. } else {
  349. this.changeConfigAttribute(configCondition, isConditionTrue);
  350. }
  351. } else if (configCondition.get("resource") === 'service') {
  352. var service = App.Service.find().findProperty('serviceName', ifStatement);
  353. var serviceName;
  354. if (service) {
  355. isConditionTrue = true;
  356. } else if (!service && this.get('controller.allSelectedServiceNames') && this.get('controller.allSelectedServiceNames').length) {
  357. isConditionTrue = this.get('controller.allSelectedServiceNames').contains(ifStatement);
  358. } else {
  359. isConditionTrue = false;
  360. }
  361. this.changeConfigAttribute(configCondition, isConditionTrue);
  362. }
  363. }, this);
  364. },
  365. /**
  366. *
  367. * @param configCondition {App.ThemeCondition}
  368. * @param isConditionTrue {boolean}
  369. */
  370. changeConfigAttribute: function(configCondition, isConditionTrue) {
  371. var conditionalConfigName = configCondition.get("configName");
  372. var conditionalConfigFileName = configCondition.get("fileName");
  373. var serviceName = this.get('config.serviceName');
  374. var serviceConfigs = this.get('controller.stepConfigs').findProperty('serviceName',serviceName).get('configs');
  375. var action = isConditionTrue ? configCondition.get("then") : configCondition.get("else");
  376. var valueAttributes = action.property_value_attributes;
  377. for (var key in valueAttributes) {
  378. if (valueAttributes.hasOwnProperty(key)) {
  379. var valueAttribute = App.StackConfigValAttributesMap[key] || key;
  380. var conditionalConfig = serviceConfigs.filterProperty('filename',conditionalConfigFileName).findProperty('name', conditionalConfigName);
  381. if (conditionalConfig) {
  382. conditionalConfig.set(valueAttribute, valueAttributes[key]);
  383. }
  384. }
  385. }
  386. },
  387. /**
  388. *
  389. * @param subsectionCondition {App.ThemeCondition}
  390. * @param isConditionTrue {boolean}
  391. */
  392. changeSubsectionAttribute: function(subsectionCondition, isConditionTrue) {
  393. var subsectionConditionName = subsectionCondition.get('name');
  394. var action = isConditionTrue ? subsectionCondition.get("then") : subsectionCondition.get("else");
  395. if (subsectionCondition.get('id')) {
  396. var valueAttributes = action.property_value_attributes;
  397. if (valueAttributes && !Em.none(valueAttributes['visible'])) {
  398. var themeResource;
  399. if (subsectionCondition.get('type') === 'subsection') {
  400. themeResource = App.SubSection.find().findProperty('name', subsectionConditionName);
  401. } else if (subsectionCondition.get('type') === 'subsectionTab') {
  402. themeResource = App.SubSectionTab.find().findProperty('name', subsectionConditionName);
  403. }
  404. themeResource.set('isHiddenByConfig', !valueAttributes['visible']);
  405. themeResource.get('configs').setEach('hiddenBySection', !valueAttributes['visible']);
  406. }
  407. }
  408. },
  409. /**
  410. * set widget value same as config value
  411. * useful for widgets that work with intermediate config value, not original
  412. * for now used in slider widget
  413. * @abstract
  414. */
  415. setValue: Em.K,
  416. /**
  417. * Config group bound property. Needed for correct render process in template.
  418. *
  419. * @returns {App.ConfigGroup|Boolean}
  420. */
  421. configGroup: function() {
  422. return !this.get('config.group') || this.get('config.group.isDefault') ? false : this.get('config.group');
  423. }.property('config.group.name'),
  424. /**
  425. * switcher to display config as widget or text field
  426. * @method toggleWidgetView
  427. */
  428. toggleWidgetView: function() {
  429. if (!this.get('isWidgetViewAllowed')) {
  430. return false;
  431. }
  432. if (this.get('config.showAsTextBox')) {
  433. this.textBoxToWidget();
  434. } else {
  435. this.widgetToTextBox();
  436. }
  437. },
  438. /**
  439. * switch display of config to text field
  440. * @method widgetToTextBox
  441. */
  442. widgetToTextBox: function() {
  443. this.set("config.showAsTextBox", true);
  444. },
  445. /**
  446. * switch display of config to widget
  447. * @method textBoxToWidget
  448. */
  449. textBoxToWidget: function() {
  450. if (this.isValueCompatibleWithWidget()) {
  451. this.setValue(this.get('config.value'));
  452. this.set("config.showAsTextBox", false);
  453. }
  454. },
  455. /**
  456. * check if config value can be converted to config widget value
  457. * IMPORTANT! Each config-widget that override this method should use <code>updateWarningsForCompatibilityWithWidget</code>
  458. * @returns {boolean}
  459. */
  460. isValueCompatibleWithWidget: function() {
  461. return (this.get('isOverrideEqualityError') && !this.get('config.isValid')) || this.get('config.isValid') || !this.get('supportSwitchToTextBox');
  462. },
  463. /**
  464. * Initialize widget with incompatible value as textbox
  465. */
  466. initIncompatibleWidgetAsTextBox : function() {
  467. this.get('config').set('showAsTextBox', !this.isValueCompatibleWithWidget());
  468. },
  469. /**
  470. * Returns <code>true</code> if raw value can be used by widget or widget view is activated.
  471. * @returns {Boolean}
  472. */
  473. isWidgetViewAllowed: function() {
  474. if (!this.get('config.showAsTextBox')) {
  475. return true;
  476. }
  477. return this.isValueCompatibleWithWidget();
  478. }.property('config.value', 'config.isFinal', 'config.showAsTextBox'),
  479. /**
  480. * Used in <code>isValueCompatibleWithWidget</code>
  481. * Updates issue-parameters if config is in the raw-mode
  482. * @param {string} message empty string if value compatible with widget, error-message if value isn't compatible with widget
  483. * @method updateWarningsForCompatibilityWithWidget
  484. */
  485. updateWarningsForCompatibilityWithWidget: function (message) {
  486. this.setProperties({
  487. warnMessage: message,
  488. 'config.warnMessage': message,
  489. issueMessage: message,
  490. configLabelClass: message ? 'text-warning' : ''
  491. });
  492. }
  493. });