Repository: ambari Updated Branches: refs/heads/trunk 6961561be -> 99bab6aba
AMBARI-6714 Step5 skipped during installaton, if only "Tez","Hive+Hcatalog+WebHCat" services choosen. (Buzhor Denys via ababiichuk) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/99bab6ab Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/99bab6ab Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/99bab6ab Branch: refs/heads/trunk Commit: 99bab6aba6fac63f3a0182d255d34dc9e1c62e29 Parents: 6961561 Author: aBabiichuk <ababiic...@cybervisiontech.com> Authored: Fri Aug 1 19:54:52 2014 +0300 Committer: aBabiichuk <ababiic...@cybervisiontech.com> Committed: Fri Aug 1 19:59:41 2014 +0300 ---------------------------------------------------------------------- .../app/controllers/wizard/step4_controller.js | 206 +++++++++--- .../service/reassign/step2_controller_test.js | 7 +- .../test/controllers/wizard/step4_test.js | 311 +++++++++++++++---- ambari-web/test/views/main/host/summary_test.js | 2 +- 4 files changed, 408 insertions(+), 118 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/99bab6ab/ambari-web/app/controllers/wizard/step4_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step4_controller.js b/ambari-web/app/controllers/wizard/step4_controller.js index 872962d..b4742c2 100644 --- a/ambari-web/app/controllers/wizard/step4_controller.js +++ b/ambari-web/app/controllers/wizard/step4_controller.js @@ -38,6 +38,14 @@ App.WizardStep4Controller = Em.ArrayController.extend({ }.property("@each.isSelected"), /** + * List of validation errors. Look to #createError method for information + * regarding object structure. + * + * @type {Object[]} + */ + errorStack: [], + + /** * Check whether all properties are selected * @type {bool} */ @@ -58,6 +66,12 @@ App.WizardStep4Controller = Em.ArrayController.extend({ }.property('@each.isSelected'), /** + * Drop errorStack content on selected state changes. + **/ + clearErrors: function() { + this.set('errorStack', []); + }.observes('@each.isSelected'), + /** * Onclick handler for <code>select all</code> link * @method selectAll */ @@ -93,86 +107,169 @@ App.WizardStep4Controller = Em.ArrayController.extend({ }, /** - * Check do we have any monitoring service turned on - * @return {bool} - * @method gangliaOrNagiosNotSelected - */ - isMonitoringServiceNotSelected: function () { - var stackMonitoringServices = this.filterProperty('isMonitoringService',true); - return stackMonitoringServices.someProperty('isSelected',false); - }, - - /** * Check whether user turned on monitoring service and go to next step * @method validateMonitoring */ - validateMonitoring: function () { - var monitoringServices = this.filterProperty('isMonitoringService',true); - var notSelectedService = monitoringServices.filterProperty('isSelected',false); + serviceMonitoringValidation: function () { + var monitoringServices = this.filterProperty('isMonitoringService', true); + var notSelectedService = monitoringServices.filterProperty('isSelected', false); if (!!notSelectedService.length) { notSelectedService = stringUtils.getFormattedStringFromArray(notSelectedService.mapProperty('displayNameOnSelectServicePage')); monitoringServices = stringUtils.getFormattedStringFromArray(monitoringServices.mapProperty('displayNameOnSelectServicePage')); - this.monitoringCheckPopup(notSelectedService,monitoringServices); - } else { - App.router.send('next'); + this.addValidationError({ + id: 'monitoringCheck', + type: 'WARNING', + callback: this.monitoringCheckPopup, + callbackParams: [notSelectedService, monitoringServices] + }); } }, /** - * Onclick handler for <code>Next</code> button + * Onclick handler for <code>Next</code> button. * @method submit */ submit: function () { - this.setGroupedServices(); - if (!this.get("isSubmitDisabled") && !this.isSubmitChecksFailed()) { - this.validateMonitoring(); + if (!this.get('isSubmitDisabled')) { + if (this.validate()) { + this.set('errorStack', []); + this.setGroupedServices(); + App.router.send('next'); + } } }, /** - * @method {isSubmitChecksFailed} Do the required checks on Next button click event - * @returns {boolean} - */ - isSubmitChecksFailed: function() { - return this.isFileSystemCheckFailed() || this.isServiceDependencyCheckFailed(); + * Check if validation passed: + * - required file system services selected + * - dependencies between services + * - monitoring services selected (not required) + * + * @return {Boolean} + * @method validate + **/ + validate: function() { + this.serviceDependencyValidation(); + this.fileSystemServiceValidation(); + this.serviceMonitoringValidation(); + if (!!this.get('errorStack').filterProperty('isShown', false).length) { + this.showError(this.get('errorStack').findProperty('isShown', false)); + return false; + } + return true; }, /** - * @method: isFileSystemCheckFailed - Checks if a filesystem is selected and only one filesystem is selected - * @return: {boolean} + * Create error and push it to stack. + * + * @param {Object} errorObject - look to #createError + * @return {Boolean} + * @method addValidationError + **/ + addValidationError: function(errorObject) { + if (!this.get('errorStack').mapProperty('id').contains(errorObject.id)) { + this.get('errorStack').push(this.createError(errorObject)); + return true; + } else { + return false; + } + }, + + /** + * Show current error by passed error object. + * + * @param {Object} errorObject + * @method showError + **/ + showError: function(errorObject) { + return errorObject.callback.apply(errorObject.callbackContext, errorObject.callbackParams); + }, + + /** + * Default primary button("Ok") callback for warning popups. + * Change isShown state for last shown error. + * Call #submit() method. + * + * @method onPrimaryPopupCallback + **/ + onPrimaryPopupCallback: function() { + if (this.get('errorStack').someProperty('isShown', false)) { + this.get('errorStack').findProperty('isShown', false).isShown = true; + } + this.submit(); + }, + + /** + * Create error object with passed options. + * Available options: + * id - {String} + * type - {String} + * isShowed - {Boolean} + * callback - {Function} + * callbackContext + * callbackParams - {Array} + * + * @param {Object} opt + * @return {Object} + * @method createError + **/ + createError: function(opt) { + var options = { + // {String} error identifier + id: '', + // {String} type of error CRITICAL|WARNING + type: 'CRITICAL', + // {Boolean} error was shown + isShown: false, + // {Function} callback to execute + callback: null, + // context which execute from + callbackContext: this, + // {Array} params applied to callback + callbackParams: [] + }; + $.extend(options, opt); + return options; + }, + + /** + * Checks if a filesystem is selected and only one filesystem is selected + * + * @method isFileSystemCheckFailed */ - isFileSystemCheckFailed: function() { - var isCheckFailed = false; + fileSystemServiceValidation: function() { var primaryDFS = this.findProperty('isPrimaryDFS',true); var primaryDfsDisplayName = primaryDFS.get('displayNameOnSelectServicePage'); var primaryDfsServiceName = primaryDFS.get('serviceName'); if (this.noDFSs()) { - isCheckFailed = true; - this.needToAddServicePopup.apply(this, [{serviceName: primaryDfsServiceName, selected: true},'fsCheck',primaryDfsDisplayName]); - } else if (this.multipleDFSs()) { + this.addValidationError({ + id: 'fsCheck', + callback: this.needToAddServicePopup, + callbackParams: [{serviceName: primaryDfsServiceName, selected: true},'fsCheck', primaryDfsDisplayName] + }); + } + else if (this.multipleDFSs()) { var dfsServices = this.filterProperty('isDFS',true).filterProperty('isSelected',true).mapProperty('serviceName'); var services = dfsServices.map(function (item){ - var mappedObj = { + return { serviceName: item, - selected: false + selected: item === primaryDfsServiceName }; - if (item === primaryDfsServiceName) { - mappedObj.selected = true; - } - return mappedObj; }); - isCheckFailed = true; - this.needToAddServicePopup.apply(this, [services,'multipleDFS',primaryDfsDisplayName]); + this.addValidationError({ + id: 'multipleDFS', + callback: this.needToAddServicePopup, + callbackParams: [services, 'multipleDFS', primaryDfsDisplayName] + }); } - return isCheckFailed; }, /** - * @method: isServiceDependencyCheckFailed - Checks if a dependent service is selected without selecting the main service - * @return {boolean} + * Checks if a dependent service is selected without selecting the main service. + * + * @method serviceDependencyValidation */ - isServiceDependencyCheckFailed: function() { - var isCheckFailed = false; + serviceDependencyValidation: function() { var notSelectedServices = this.filterProperty('isSelected',false); notSelectedServices.forEach(function(service){ var showWarningPopup; @@ -183,17 +280,24 @@ App.WizardStep4Controller = Em.ArrayController.extend({ var dependentService = this.findProperty('serviceName', _dependentService); if (dependentService && dependentService.get('isSelected') === true) { showWarningPopup = true; - isCheckFailed = true; } },this); if (showWarningPopup) { - this.needToAddServicePopup.apply(this, [{serviceName: service.get('serviceName'), selected: true},'serviceCheck',service.get('displayNameOnSelectServicePage')]); + this.addValidationError({ + id: 'serviceCheck_' + service.get('serviceName'), + callback: this.needToAddServicePopup, + callbackParams: [{serviceName: service.get('serviceName'), selected: true}, 'serviceCheck', service.get('displayNameOnSelectServicePage')] + }); } } },this); - return isCheckFailed; }, + /** + * Select co hosted services which not showed on UI. + * + * @method setGroupedServices + **/ setGroupedServices: function() { this.forEach(function(service){ var coSelectedServices = service.get('coSelectedServices'); @@ -204,7 +308,6 @@ App.WizardStep4Controller = Em.ArrayController.extend({ },this); }, - /** * Select/deselect services * @param services array of objects @@ -235,8 +338,8 @@ App.WizardStep4Controller = Em.ArrayController.extend({ services.forEach(function (service) { self.findProperty('serviceName', service.serviceName).set('isSelected', service.selected); }); + self.onPrimaryPopupCallback(); this.hide(); - self.submit(); } }); }, @@ -247,12 +350,13 @@ App.WizardStep4Controller = Em.ArrayController.extend({ * @method monitoringCheckPopup */ monitoringCheckPopup: function (notSelectedServiceNames,monitoringServicesNames) { + var self = this; return App.ModalPopup.show({ header: Em.I18n.t('installer.step4.monitoringCheck.popup.header'), body: Em.I18n.t('installer.step4.monitoringCheck.popup.body').format(notSelectedServiceNames,monitoringServicesNames), onPrimary: function () { + self.onPrimaryPopupCallback(); this.hide(); - App.router.send('next'); } }); } http://git-wip-us.apache.org/repos/asf/ambari/blob/99bab6ab/ambari-web/test/controllers/main/service/reassign/step2_controller_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/main/service/reassign/step2_controller_test.js b/ambari-web/test/controllers/main/service/reassign/step2_controller_test.js index 67c1d35..5e89c7e 100644 --- a/ambari-web/test/controllers/main/service/reassign/step2_controller_test.js +++ b/ambari-web/test/controllers/main/service/reassign/step2_controller_test.js @@ -16,7 +16,7 @@ * limitations under the License. */ -App = require('app'); +var App = require('app'); require('controllers/main/service/reassign/step2_controller'); require('models/host_component'); @@ -37,10 +37,15 @@ describe('App.ReassignMasterWizardStep2Controller', function () { describe('#loadStep', function () { beforeEach(function () { + sinon.stub(App.router, 'send', Em.K); + sinon.stub(controller, 'loadStepCallback', Em.K); sinon.stub(controller, 'rebalanceSingleComponentHosts', Em.K); }); + afterEach(function () { controller.rebalanceSingleComponentHosts.restore(); + App.router.send.restore(); + controller.loadStepCallback.restore(); }); it('SECONDARY_NAMENODE is absent, reassign component is NAMENODE', function () { http://git-wip-us.apache.org/repos/asf/ambari/blob/99bab6ab/ambari-web/test/controllers/wizard/step4_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/wizard/step4_test.js b/ambari-web/test/controllers/wizard/step4_test.js index 811c0f7..507a809 100644 --- a/ambari-web/test/controllers/wizard/step4_test.js +++ b/ambari-web/test/controllers/wizard/step4_test.js @@ -18,8 +18,9 @@ var Ember = require('ember'); var App = require('app'); -require('controllers/wizard/step4_controller'); +var modelSetup = require('test/init_model_test'); +require('controllers/wizard/step4_controller'); describe('App.WizardStep4Controller', function () { var services = [ @@ -28,6 +29,34 @@ describe('App.WizardStep4Controller', function () { ]; var controller = App.WizardStep4Controller.create(); + + var generateSelectedServicesContent = function(selectedServiceNames) { + var allServices = services.slice(0); + if (selectedServiceNames.contains('GLUSTERFS')) allServices.push('GLUSTERFS'); + allServices = allServices.map(function(serviceName) { + return [Ember.Object.create({ + 'serviceName': serviceName, + 'isSelected': false, + 'canBeSelected': true, + 'isInstalled': false, + isPrimaryDFS: serviceName == 'HDFS', + isDFS: ['HDFS','GLUSTERFS'].contains(serviceName), + isMonitoringService: ['NAGIOS','GANGLIA'].contains(serviceName), + dependentServices: App.StackService.dependency['HDP-2'][serviceName], + displayNameOnSelectServicePage: App.format.role(serviceName), + coSelectedServices: function() { + return App.StackService.coSelected[this.get('serviceName')] || []; + }.property('serviceName') + })]; + }).reduce(function(current, prev) { return current.concat(prev); }); + + selectedServiceNames.forEach(function(serviceName) { + allServices.findProperty('serviceName', serviceName).set('isSelected', true); + }); + + return allServices; + }; + services.forEach(function(serviceName, index){ controller.pushObject(Ember.Object.create({ 'serviceName':serviceName, 'isSelected': true, 'isHiddenOnSelectServicePage': false, 'isInstalled': false, 'isDisabled': 'HDFS' === serviceName, isDFS: 'HDFS' === serviceName @@ -211,19 +240,181 @@ describe('App.WizardStep4Controller', function () { }, this); }); - describe('#monitoringCheckPopup', function() { - it('should show App.ModalPopup', function() { - sinon.spy(App.ModalPopup, 'show'); - controller.monitoringCheckPopup(); - expect(App.ModalPopup.show.calledOnce).to.equal(true); - App.ModalPopup.show.restore(); + describe('#addValidationError()', function() { + var tests = [ + { + errorObjects: [ + { + id: 'serviceCheck_ZOOKEEPER', + shouldBeAdded: true + }, + { + id: 'serviceCheck_YARN', + shouldBeAdded: true + } + ], + expectedIds: ['serviceCheck_ZOOKEEPER', 'serviceCheck_YARN'] + }, + { + errorObjects: [ + { + id: 'fsCheck', + shouldBeAdded: true + }, + { + id: 'fsCheck', + shouldBeAdded: false + } + ], + expectedIds: ['fsCheck'] + } + ]; + + beforeEach(function() { + controller.clear(); + controller.set('errorStack', []); }); - it('onPrimary should proceed to next step', function() { + + tests.forEach(function(test) { + var message = 'Erorrs {0} thrown. errorStack property should contains ids: {1}' + .format(test.errorObjects.mapProperty('id').join(', '), test.expectedIds.join(', ')); + it(message, function() { + test.errorObjects.forEach(function(errorObject) { + expect(controller.addValidationError(errorObject)).to.equal(errorObject.shouldBeAdded); + }); + expect(controller.get('errorStack').mapProperty('id')).to.eql(test.expectedIds); + }); + }) + }); + + describe('#validate()', function() { + var tests = [ + { + services: ['HDFS','ZOOKEEPER'], + errorsExpected: ['monitoringCheck'] + }, + { + services: ['ZOOKEEPER'], + errorsExpected: ['fsCheck', 'monitoringCheck'] + }, + { + services: ['HDFS'], + errorsExpected: ['serviceCheck_ZOOKEEPER', 'monitoringCheck'] + }, + { + services: ['HDFS', 'TEZ', 'ZOOKEEPER'], + errorsExpected: ['serviceCheck_YARN', 'monitoringCheck'] + }, + { + services: ['HDFS', 'ZOOKEEPER', 'FALCON', 'NAGIOS'], + errorsExpected: ['serviceCheck_OOZIE', 'monitoringCheck'] + }, + { + services: ['HDFS', 'ZOOKEEPER', 'GANGLIA', 'NAGIOS', 'HIVE'], + errorsExpected: ['serviceCheck_YARN'] + }, + { + services: ['HDFS', 'GLUSTERFS', 'ZOOKEEPER', 'HIVE'], + errorsExpected: ['serviceCheck_YARN', 'multipleDFS', 'monitoringCheck'] + }, + { + services: ['HDFS','ZOOKEEPER', 'NAGIOS', 'GANGLIA'], + errorsExpected: [] + } + ]; + + tests.forEach(function(test) { + var message = '{0} selected validation should be {1}, errors with ids: {2} present' + .format(test.services.join(','), !!test.validationPassed ? 'passed' : 'failed', test.errorsExpected.join(',')); + it(message, function() { + controller.clear(); + controller.set('content', generateSelectedServicesContent(test.services)); + controller.validate(); + expect(controller.get('errorStack').mapProperty('id')).to.be.eql(test.errorsExpected); + }); + }) + }); + + describe('#onPrimaryPopupCallback()', function() { + var c; + var tests = [ + { + services: ['HDFS','ZOOKEEPER'], + confirmPopupCount: 1, + errorsExpected: ['monitoringCheck'] + }, + { + services: ['ZOOKEEPER'], + confirmPopupCount: 2, + errorsExpected: ['fsCheck', 'monitoringCheck'] + }, + { + services: ['HDFS', 'GLUSTERFS', 'ZOOKEEPER', 'HIVE'], + confirmPopupCount: 3, + errorsExpected: ['serviceCheck_YARN', 'serviceCheck_TEZ', 'multipleDFS', 'monitoringCheck'] + }, + { + services: ['HDFS','ZOOKEEPER', 'NAGIOS', 'GANGLIA'], + confirmPopupCount: 0, + errorsExpected: [] + } + ]; + + beforeEach(function() { + c = App.WizardStep4Controller.create({}); sinon.stub(App.router, 'send', Em.K); - controller.monitoringCheckPopup().onPrimary(); - expect(App.router.send.calledWith('next')).to.equal(true); + sinon.stub(c, 'submit', Em.K); + sinon.spy(c, 'onPrimaryPopupCallback'); + }); + + afterEach(function() { App.router.send.restore(); + c.submit.restore(); + c.onPrimaryPopupCallback.restore(); }); + + + tests.forEach(function(test) { + var message = 'Selected services: {0}. {1} errors should be confirmed' + .format(test.services.join(', '), test.confirmPopupCount); + + it(message, function() { + var runValidations = function() { + c.serviceDependencyValidation(); + c.fileSystemServiceValidation(); + c.serviceMonitoringValidation(); + } + + c.set('content', generateSelectedServicesContent(test.services)); + runValidations(); + // errors count validation + expect(c.get('errorStack.length')).to.equal(test.confirmPopupCount); + // if errors detected than it should be shown + if (test.errorsExpected) { + test.errorsExpected.forEach(function(error, index, errors) { + // validate current error + var currentErrorObject = c.get('errorStack').findProperty('isShown', false); + if (currentErrorObject) { + expect(error).to.be.equal(currentErrorObject.id); + // show current error + var popup = c.showError(currentErrorObject); + // submit popup + popup.onPrimary(); + // onPrimaryPopupCallback should be called + expect(c.onPrimaryPopupCallback.called).to.equal(true); + // submit called + expect(c.submit.called).to.equal(true); + if (c.get('errorStack').length) { + // current error isShown flag changed to true + expect(currentErrorObject.isShown).to.equal(true); + } + runValidations(); + } + }); + } + }); + }); + }); describe('#needToAddServicePopup', function() { @@ -253,65 +444,55 @@ describe('App.WizardStep4Controller', function () { }); }); - describe('#submit', function() { + describe('#submit', function() { + var c; + var tests = [ + { + isSubmitDisabled: true, + validate: false, + userCanProceed: false + }, + { + isSubmitDisabled: false, + validate: false, + userCanProceed: false + }, + { + isSubmitDisabled: false, + validate: true, + userCanProceed: true + } + ]; + beforeEach(function() { - sinon.stub(controller, 'validateMonitoring', Em.K); - sinon.stub(controller, 'setGroupedServices', Em.K); + c = App.WizardStep4Controller.create(); + sinon.stub(App.router, 'send', Em.K); }); + afterEach(function() { - controller.validateMonitoring.restore(); - controller.setGroupedServices.restore(); - }); - it('if not isSubmitDisabled shound\'t do nothing', function() { - controller.reopen({isSubmitDisabled: true}); - controller.submit(); - expect(controller.validateMonitoring.called).to.equal(false); - }); - it('if isSubmitDisabled and not submitChecks should call validateMonitoring', function() { - sinon.stub(controller, 'isSubmitChecksFailed', function() { return false; }); - controller.reopen({ - isSubmitDisabled: false, - submitChecks: [] - }); - controller.submit(); - expect(controller.validateMonitoring.calledOnce).to.equal(true); - controller.isSubmitChecksFailed.restore(); - }); - it('if isSubmitDisabled and some submitChecks true shouldn\'t call validateMonitoring', function() { - controller.reopen({ - isSubmitDisabled: false, - submitChecks: [ - { - popupParams: [ - {serviceName: 'MAPREDUCE', selected: true}, - 'mapreduceCheck' - ] - } - ] - }); - sinon.stub(controller, 'isSubmitChecksFailed', function() { return true; }); - controller.submit(); - controller.isSubmitChecksFailed.restore(); - expect(controller.validateMonitoring.called).to.equal(false); + App.router.send.restore(); }); - it('if isSubmitDisabled and some submitChecks false should call validateMonitoring', function() { - controller.reopen({ - isSubmitDisabled: false, - submitChecks: [ - { - checkCallback: 'needToAddMapReduce', - popupParams: [ - {serviceName: 'MAPREDUCE', selected: true}, - 'mapreduceCheck' - ] - } - ] + + tests.forEach(function(test) { + var messageFormat = [ + test.isSubmitDisabled ? 'disabled' : 'enabled', + test.validate ? 'success' : 'failed', + test.userCanProceed ? '' : 'not' + ]; + var message = String.prototype.format.apply('Submit btn: {0}. Validation: {1}. Can{2} move to the next step.', messageFormat); + + it(message, function() { + c.reopen({ + isSubmitDisabled: test.isSubmitDisabled, + validate: function() { return test.validate; } + }); + c.clear(); + c.submit(); + + expect(App.router.send.calledOnce).to.equal(test.userCanProceed); }); - sinon.stub(controller, 'isSubmitChecksFailed', function() { return false; }); - controller.submit(); - controller.isSubmitChecksFailed.restore(); - expect(controller.validateMonitoring.calledOnce).to.equal(true); - }); + + }) }); -}); \ No newline at end of file +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/99bab6ab/ambari-web/test/views/main/host/summary_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/views/main/host/summary_test.js b/ambari-web/test/views/main/host/summary_test.js index b5637db..95ea90b 100644 --- a/ambari-web/test/views/main/host/summary_test.js +++ b/ambari-web/test/views/main/host/summary_test.js @@ -288,7 +288,7 @@ describe('App.MainHostSummaryView', function() { ]) }), services: ['HDFS', 'YARN', 'MAPREDUCE2'], - e: ['NODEMANAGER', 'CLIENTS'], + e: ['MAPREDUCE2_CLIENT', 'NODEMANAGER', 'YARN_CLIENT', 'CLIENTS'], m: 'some components are already installed' }, {