Repository: ambari Updated Branches: refs/heads/trunk d5f2853df -> 5334953e1
AMBARI-13971. Implement Em.computed macros (2) (onechiporenko) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/5334953e Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/5334953e Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/5334953e Branch: refs/heads/trunk Commit: 5334953e1f36f9ab0d760c5c1319a82876b0b486 Parents: d5f2853 Author: Oleg Nechiporenko <onechipore...@apache.org> Authored: Thu Nov 19 17:26:21 2015 +0200 Committer: Oleg Nechiporenko <onechipore...@apache.org> Committed: Thu Nov 19 17:26:21 2015 +0200 ---------------------------------------------------------------------- ambari-web/app/utils/ember_computed.js | 182 +++++++++++++- .../test/controllers/wizard/step2_test.js | 10 +- .../test/controllers/wizard/step6_test.js | 6 +- ambari-web/test/utils/ember_computed_test.js | 247 ++++++++++++++++++- 4 files changed, 426 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/5334953e/ambari-web/app/utils/ember_computed.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/ember_computed.js b/ambari-web/app/utils/ember_computed.js index d79c997..4ae8c5b 100644 --- a/ambari-web/app/utils/ember_computed.js +++ b/ambari-web/app/utils/ember_computed.js @@ -20,23 +20,65 @@ var computed = Em.computed; var get = Em.get; var makeArray = Em.makeArray; +var slice = [].slice; + var dataUtils = require('utils/data_manipulation'); function getProperties(self, propertyNames) { var ret = {}; for (var i = 0; i < propertyNames.length; i++) { - ret[propertyNames[i]] = get(self, propertyNames[i]); + var propertyName = propertyNames[i]; + var value; + if (propertyName.startsWith('!')) { + propertyName = propertyName.substring(1); + value = !get(self, propertyName); + } + else { + value = get(self, propertyName); + } + ret[propertyName] = value; } return ret; } +function getValues(self, propertyNames) { + return propertyNames.map(function (propertyName) { + return get(self, propertyName); + }); +} + +function generateComputedWithKey(macro) { + return function () { + var properties = slice.call(arguments, 1); + var key = arguments[0]; + var computedFunc = computed(function () { + var values = getValues(this, properties); + return macro.call(this, key, values); + }); + + return computedFunc.property.apply(computedFunc, properties); + } +} + function generateComputedWithProperties(macro) { return function () { - var properties = [].slice.call(arguments); + var properties = slice.call(arguments); var computedFunc = computed(function () { return macro.apply(this, [getProperties(this, properties)]); }); + var realProperties = properties.slice().invoke('replace', '!', ''); + return computedFunc.property.apply(computedFunc, realProperties); + }; +} + +function generateComputedWithValues(macro) { + return function () { + var properties = slice.call(arguments); + var computedFunc = computed(function () { + return macro.apply(this, [getValues(this, properties)]); + }); + return computedFunc.property.apply(computedFunc, properties); }; } @@ -182,12 +224,13 @@ computed.ifThenElse = function (dependentKey, trueValue, falseValue) { * Returns true if all of them are truly, false - otherwise * * @method and + * @param {...string} dependentKeys * @returns {Ember.ComputedProperty} */ computed.and = generateComputedWithProperties(function (properties) { var value; for (var key in properties) { - value = properties[key]; + value = !!properties[key]; if (properties.hasOwnProperty(key) && !value) { return false; } @@ -201,12 +244,13 @@ computed.and = generateComputedWithProperties(function (properties) { * Returns true if at least one of them is truly, false - otherwise * * @method or + * @param {...string} dependentKeys * @returns {Ember.ComputedProperty} */ computed.or = generateComputedWithProperties(function (properties) { var value; for (var key in properties) { - value = properties[key]; + value = !!properties[key]; if (properties.hasOwnProperty(key) && value) { return value; } @@ -219,6 +263,7 @@ computed.or = generateComputedWithProperties(function (properties) { * Takes any number of arguments * * @method sumProperties + * @param {...string} dependentKeys * @returns {Ember.ComputedProperty} */ computed.sumProperties = generateComputedWithProperties(function (properties) { @@ -443,7 +488,7 @@ computed.findBy = function (collectionKey, propertyName, neededValue) { computed.alias = function (dependentKey) { return computed(dependentKey, function () { return get(this, dependentKey); - }) + }); }; /** @@ -458,5 +503,128 @@ computed.existsIn = function (dependentKey, neededValues) { return computed(dependentKey, function () { var value = get(this, dependentKey); return makeArray(neededValues).contains(value); - }) -}; \ No newline at end of file + }); +}; + +/** + * A computed property that returns true if dependent property doesn't exist in the needed values + * + * @method notExistsIn + * @param {string} dependentKey + * @param {array} neededValues + * @returns {Ember.ComputedProperty} + */ +computed.notExistsIn = function (dependentKey, neededValues) { + return computed(dependentKey, function () { + var value = get(this, dependentKey); + return !makeArray(neededValues).contains(value); + }); +}; + +/** + * A computed property that returns result of calculation <code>(dependentProperty1/dependentProperty2 * 100)</code> + * If accuracy is 0 (by default), result is rounded to integer + * Otherwise - result is float with provided accuracy + * + * @method percents + * @param {string} dependentKey1 + * @param {string} dependentKey2 + * @param {number} [accuracy=0] + * @returns {Ember.ComputedProperty} + */ +computed.percents = function (dependentKey1, dependentKey2, accuracy) { + if (arguments.length < 3) { + accuracy = 0; + } + return computed(dependentKey1, dependentKey2, function () { + var v1 = get(this, dependentKey1); + var v2 = get(this, dependentKey2); + var result = v1 / v2 * 100; + if (0 === accuracy) { + return Math.round(result); + } + return parseFloat(result.toFixed(accuracy)); + }); +}; + +/** + * A computed property that returns result of <code>App.format.role</code> for dependent value + * + * @method formatRole + * @param {string} dependentKey + * @returns {Ember.ComputedProperty} + */ +computed.formatRole = function (dependentKey) { + return computed(dependentKey, function () { + var value = get(this, dependentKey); + return App.format.role(value); + }); +}; + +/** + * A computed property that returns sum of the named property in the each collection's item + * + * @method sumBy + * @param {string} collectionKey + * @param {string} propertyName + * @returns {Ember.ComputedProperty} + */ +computed.sumBy = function (collectionKey, propertyName) { + return computed(collectionKey + '.@each.' + propertyName, function () { + var collection = get(this, collectionKey); + if (Em.isEmpty(collection)) { + return 0; + } + var sum = 0; + collection.forEach(function (item) { + sum += get(item, propertyName); + }); + return sum; + }); +}; + +/** + * A computed property that returns I18n-string formatted with dependent properties + * Takes at least one argument + * + * @param {string} key key in the I18n-messages + * @param {...string} dependentKeys + * @method i18nFormat + * @returns {Ember.ComputedProperty} + */ +computed.i18nFormat = generateComputedWithKey(function (key, dependentValues) { + var str = Em.I18n.t(key); + return str.format.apply(str, dependentValues); +}); + +/** + * A computed property that returns dependent values joined with separator + * Takes at least one argument + * + * @param {string} separator + * @param {...string} dependentKeys + * @method concat + * @return {Ember.ComputedProperty} + */ +computed.concat = generateComputedWithKey(function (separator, dependentValues) { + return dependentValues.join(separator); +}); + +/** + * A computed property that returns first not blank value from dependent values + * Based on <code>Ember.isBlank</code> + * Takes at least 1 argument + * Dependent values order affects the result + * + * @param {...string} dependentKeys + * @method {firstNotBlank} + * @return {Ember.ComputedProperty} + */ +computed.firstNotBlank = generateComputedWithValues(function (values) { + for (var i = 0; i < values.length; i++) { + if (!Em.isBlank(values[i])) { + return values[i]; + } + } + return null; +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/5334953e/ambari-web/test/controllers/wizard/step2_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/wizard/step2_test.js b/ambari-web/test/controllers/wizard/step2_test.js index 8f0fc66..b137669 100644 --- a/ambari-web/test/controllers/wizard/step2_test.js +++ b/ambari-web/test/controllers/wizard/step2_test.js @@ -531,31 +531,31 @@ describe('App.WizardStep2Controller', function () { it('should return value if hostsError is not empty', function () { controller.set('hostsError', 'error'); - expect(controller.get('isSubmitDisabled').length).to.above(0); + expect(controller.get('isSubmitDisabled')).to.be.true; }); it('should return value if sshKeyError is not empty', function () { controller.set('sshKeyError', 'error'); controller.set('hostsError', ''); - expect(controller.get('isSubmitDisabled').length).to.above(0); + expect(controller.get('isSubmitDisabled')).to.be.true; }); it('should return value if sshUserError is not empty', function () { controller.set('sshUserError', 'error'); controller.set('sshKeyError', ''); - expect(controller.get('isSubmitDisabled').length).to.above(0); + expect(controller.get('isSubmitDisabled')).to.be.true; }); it('should return value if agentUserError is not empty', function () { controller.set('agentUserError', 'error'); controller.set('sshUserError', ''); - expect(controller.get('isSubmitDisabled').length).to.above(0); + expect(controller.get('isSubmitDisabled')).to.be.true; }); it('should return value if sshPortError is not empty', function () { controller.set('sshPortError', 'error'); controller.set('agentUserError', ''); - expect(controller.get('isSubmitDisabled').length).to.above(0); + expect(controller.get('isSubmitDisabled')).to.be.true; }); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/5334953e/ambari-web/test/controllers/wizard/step6_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/wizard/step6_test.js b/ambari-web/test/controllers/wizard/step6_test.js index 303f986..2de57fd 100644 --- a/ambari-web/test/controllers/wizard/step6_test.js +++ b/ambari-web/test/controllers/wizard/step6_test.js @@ -314,15 +314,15 @@ describe('App.WizardStep6Controller', function () { describe('#anyGeneralIssues', function () { it('should return error message if errorMessage', function () { controller.set('errorMessage', "error 404"); - expect(controller.get('anyGeneralIssues')).to.equal("error 404"); + expect(controller.get('anyGeneralIssues')).to.be.true; }); it('should return true if we have several errors', function () { controller.set('generalErrorMessages', ["error 404", "error"]); - expect(controller.get('anyGeneralIssues')).to.equal(true); + expect(controller.get('anyGeneralIssues')).to.be.true; }); it('should return true if we have several warnings', function () { controller.set('generalWarningMessages', ["error 404", "error"]); - expect(controller.get('anyGeneralIssues')).to.equal(true); + expect(controller.get('anyGeneralIssues')).to.be.true; }); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/5334953e/ambari-web/test/utils/ember_computed_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/utils/ember_computed_test.js b/ambari-web/test/utils/ember_computed_test.js index 0d8bb8e..659139c 100644 --- a/ambari-web/test/utils/ember_computed_test.js +++ b/ambari-web/test/utils/ember_computed_test.js @@ -109,19 +109,39 @@ describe('Ember.computed macros', function () { prop1: true, prop2: true, prop3: true, - prop4: Em.computed.and('prop1', 'prop2', 'prop3') + prop4: Em.computed.and('prop1', 'prop2', 'prop3'), + prop5: Em.computed.and('prop1', '!prop2', '!prop3') }); }); - it('`true` if all dependent properties are true', function () { + it('prop4 `true` if all dependent properties are true', function () { expect(this.obj.get('prop4')).to.be.true; }); - it('`false` if at elast one dependent property is false', function () { + it('prop4 `false` if at elast one dependent property is false', function () { this.obj.set('prop2', false); expect(this.obj.get('prop4')).to.be.false; }); + it('prop5 dependent keys are valid', function () { + expect(Em.meta(this.obj).descs.prop5._dependentKeys).to.eql(['prop1', 'prop2', 'prop3']); + }); + + it('prop5 `false` if some inverted dependent properties is true', function () { + expect(this.obj.get('prop5')).to.be.false; + }); + + it('prop5 `false` if some inverted dependent properties is true (2)', function () { + this.obj.set('prop1', true); + expect(this.obj.get('prop5')).to.be.false; + }); + + it('prop5 `true` ', function () { + this.obj.set('prop2', false); + this.obj.set('prop3', false); + expect(this.obj.get('prop5')).to.be.true; + }); + }); describe('#or', function () { @@ -131,7 +151,8 @@ describe('Ember.computed macros', function () { prop1: false, prop2: false, prop3: false, - prop4: Em.computed.or('prop1', 'prop2', 'prop3') + prop4: Em.computed.or('prop1', 'prop2', 'prop3'), + prop5: Em.computed.or('!prop1', '!prop2', '!prop3') }); }); @@ -144,6 +165,26 @@ describe('Ember.computed macros', function () { expect(this.obj.get('prop4')).to.be.true; }); + it('prop5 dependent keys are valid', function () { + expect(Em.meta(this.obj).descs.prop5._dependentKeys).to.eql(['prop1', 'prop2', 'prop3']); + }); + + it('prop5 `true` if some inverted dependent properties is true', function () { + expect(this.obj.get('prop5')).to.be.true; + }); + + it('prop5 `true` if some inverted dependent properties is true (2)', function () { + this.obj.set('prop1', true); + expect(this.obj.get('prop5')).to.be.true; + }); + + it('prop5 `false` ', function () { + this.obj.set('prop1', true); + this.obj.set('prop2', true); + this.obj.set('prop3', true); + expect(this.obj.get('prop5')).to.be.false; + }); + }); describe('#sumProperties', function () { @@ -548,4 +589,202 @@ describe('Ember.computed macros', function () { }); + describe('#percents', function () { + + beforeEach(function () { + this.obj = Em.Object.create({ + prop1: 10, + prop2: 25, + prop3: Em.computed.percents('prop1', 'prop2'), + prop4: Em.computed.percents('prop1', 'prop2', 2) + }); + }); + + it('should calculate percents', function () { + expect(this.obj.get('prop3')).to.equal(40); + expect(this.obj.get('prop4')).to.equal(40.00); + }); + + it('should calculate percents (2)', function () { + this.obj.set('prop2', 35); + expect(this.obj.get('prop3')).to.equal(29); + expect(this.obj.get('prop4')).to.equal(28.57); + }); + + }); + + describe('#formatRole', function () { + + beforeEach(function () { + this.obj = Em.Object.create({ + prop1: 'NAMENODE', + prop2: Em.computed.formatRole('prop1') + }); + sinon.stub(App.StackServiceComponent, 'find', function () { + return [ + Em.Object.create({id: 'NAMENODE', displayName: 'NameNode'}), + Em.Object.create({id: 'SECONDARY_NAMENODE', displayName: 'Secondary NameNode'}) + ]; + }); + sinon.stub(App.StackService, 'find', function () { + return [ + Em.Object.create({id: 'MAPREDUCE2', displayName: 'MapReduce2'}), + Em.Object.create({id: 'HIVE', displayName: 'Hive'}) + ]; + }); + }); + + afterEach(function () { + App.StackService.find.restore(); + App.StackServiceComponent.find.restore(); + }); + + it('should format as role', function () { + expect(this.obj.get('prop2')).to.equal('NameNode'); + }); + + it('should format as role (2)', function () { + this.obj.set('prop1', 'HIVE'); + expect(this.obj.get('prop2')).to.equal('Hive'); + }); + + }); + + describe('#sumBy', function () { + + beforeEach(function () { + this.obj = Em.Object.create({ + prop1: [ + {a: 1}, {a: 2}, {a: 3} + ], + prop2: Em.computed.sumBy('prop1', 'a') + }); + }); + + it('should calculate sum', function () { + expect(this.obj.get('prop2')).to.equal(6); + }); + + it('should calculate sum (2)', function () { + this.obj.get('prop1').pushObject({a: 4}); + expect(this.obj.get('prop2')).to.equal(10); + }); + + }); + + describe('#i18nFormat', function () { + + beforeEach(function () { + sinon.stub(Em.I18n, 't', function (key) { + var msgs = { + key1: '{0} {1} {2}' + }; + return msgs[key]; + }); + this.obj = Em.Object.create({ + prop1: 'abc', + prop2: 'cba', + prop3: 'aaa', + prop4: Em.computed.i18nFormat('key1', 'prop1', 'prop2', 'prop3') + }); + }); + + afterEach(function () { + Em.I18n.t.restore(); + }); + + it('`prop4` check dependent keys', function () { + expect(Em.meta(this.obj).descs.prop4._dependentKeys).to.eql(['prop1', 'prop2', 'prop3']); + }); + + it('should format message', function () { + expect(this.obj.get('prop4')).to.equal('abc cba aaa'); + }); + + it('should format message (2)', function () { + this.obj.set('prop1', 'aaa'); + expect(this.obj.get('prop4')).to.equal('aaa cba aaa'); + }); + + }); + + describe('#concat', function () { + + beforeEach(function () { + this.obj = Em.Object.create({ + prop1: 'abc', + prop2: 'cba', + prop3: 'aaa', + prop4: Em.computed.concat(' ', 'prop1', 'prop2', 'prop3') + }); + }); + + it('should concat dependent values', function () { + expect(this.obj.get('prop4')).to.equal('abc cba aaa'); + }); + + it('should concat dependent values (2)', function () { + this.obj.set('prop1', 'aaa'); + expect(this.obj.get('prop4')).to.equal('aaa cba aaa'); + }); + + }); + + describe('#notExistsIn', function () { + + beforeEach(function () { + this.obj = Em.Object.create({ + prop1: 'v1', + prop2: Em.computed.notExistsIn('prop1', ['v1', 'v2']) + }); + }); + + it('`false` if dependent value is in the array', function () { + expect(this.obj.get('prop2')).to.be.false; + }); + + it('`false` if dependent value is in the array (2)', function () { + this.obj.set('prop1', 'v2'); + expect(this.obj.get('prop2')).to.be.false; + }); + + it('`true` if dependent value is not in the array', function () { + this.obj.set('prop1', 'v3'); + expect(this.obj.get('prop2')).to.be.true; + }); + + }); + + describe('#firstNotBlank', function () { + + beforeEach(function () { + this.obj = Em.Object.create({ + prop1: '', + prop2: null, + prop3: '1234', + prop4: Em.computed.firstNotBlank('prop1', 'prop2', 'prop3') + }) + }); + + it('`prop4` check dependent keys', function () { + expect(Em.meta(this.obj).descs.prop4._dependentKeys).to.eql(['prop1', 'prop2', 'prop3']); + }); + + it('should returns prop3', function () { + expect(this.obj.get('prop4')).to.equal('1234'); + }); + + it('should returns prop2', function () { + this.obj.set('prop2', 'not empty string'); + expect(this.obj.get('prop4')).to.equal('not empty string'); + }); + + it('should returns prop1', function () { + this.obj.set('prop2', 'not empty string'); + this.obj.set('prop1', 'prop1 is used'); + expect(this.obj.get('prop4')).to.equal('prop1 is used'); + }); + + }); + }); \ No newline at end of file