Repository: ambari Updated Branches: refs/heads/trunk d2cc6b31c -> 5d3fad2c1
AMBARI-6046. Lags on Assign Masters step on big cluster. (onechiporenko) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/5d3fad2c Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/5d3fad2c Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/5d3fad2c Branch: refs/heads/trunk Commit: 5d3fad2c1370097b66cb738c6349f156f1aed118 Parents: d2cc6b3 Author: Oleg Nechiporenko <[email protected]> Authored: Fri Jun 13 18:38:03 2014 +0300 Committer: Oleg Nechiporenko <[email protected]> Committed: Fri Jun 13 18:38:03 2014 +0300 ---------------------------------------------------------------------- .../app/controllers/wizard/step5_controller.js | 227 +++++++++------- ambari-web/app/mixins.js | 3 +- ambari-web/app/mixins/wizard/selectHost.js | 179 +++++++++++++ ambari-web/app/styles/application.less | 4 + ambari-web/app/templates/wizard/step5.hbs | 26 +- ambari-web/app/views/wizard/step5_view.js | 192 ++++++-------- ambari-web/test/views/wizard/step5_view_test.js | 262 +++++++++++-------- 7 files changed, 563 insertions(+), 330 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/5d3fad2c/ambari-web/app/controllers/wizard/step5_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step5_controller.js b/ambari-web/app/controllers/wizard/step5_controller.js index 6bcf6f0..0f6a79a 100644 --- a/ambari-web/app/controllers/wizard/step5_controller.js +++ b/ambari-web/app/controllers/wizard/step5_controller.js @@ -18,7 +18,6 @@ var App = require('app'); var numberUtils = require('utils/number_utils'); -var lazyLoading = require('utils/lazy_loading'); App.WizardStep5Controller = Em.Controller.extend({ @@ -66,38 +65,18 @@ App.WizardStep5Controller = Em.Controller.extend({ */ multipleComponents: ['ZOOKEEPER_SERVER', 'HBASE_MASTER'], + /** + * Define state for submit button + * @type {bool} + */ submitDisabled: false, /** - * Define state for submit button. Return true only for Reassign Master Wizard and if more than one master component was reassigned. + * Trigger for executing host names check for components + * Should de "triggered" when host changed for some component and when new multiple component is added/removed * @type {bool} */ - isSubmitDisabled: function () { - if (!this.get('isReassignWizard')) { - this.set('submitDisabled', false); - } else { - App.ajax.send({ - name: 'host_components.all', - sender: this, - data: { - clusterName: App.get('clusterName') - }, - success: 'isSubmitDisabledSuccessCallBack' - }); - } - }.observes('[email protected]'), - - isSubmitDisabledSuccessCallBack: function (response) { - var reassigned = 0; - var arr1 = response.items.mapProperty('HostRoles').filterProperty('component_name', this.get('content.reassign.component_name')).mapProperty('host_name'); - var arr2 = this.get('servicesMasters').mapProperty('selectedHost'); - arr1.forEach(function (host) { - if (!arr2.contains(host)) { - reassigned++; - } - }, this); - this.set('submitDisabled', reassigned !== 1); - }, + hostNameCheckTrigger: false, /** * List of hosts @@ -106,11 +85,19 @@ App.WizardStep5Controller = Em.Controller.extend({ hosts: [], /** + * Name of multiple component which host name was changed last * @type {Object|null} */ componentToRebalance: null, /** + * Name of component which host was changed last + * @type {string} + */ + lastChangedComponent: null, + + /** + * Flag for rebalance multiple components * @type {number} */ rebalanceComponentHostsCounter: 0, @@ -126,6 +113,12 @@ App.WizardStep5Controller = Em.Controller.extend({ selectedServicesMasters: [], /** + * Is data for current step loaded + * @type {bool} + */ + isLoaded: false, + + /** * Check if HIVE_SERVER component exist (also checks if this is not reassign) * @type {bool} */ @@ -141,69 +134,40 @@ App.WizardStep5Controller = Em.Controller.extend({ * { * host_name: '', * hostInfo: {}, - * masterServices: [] + * masterServices: [], + * masterServicesToDisplay: [] // used only in template * }, * .... * ] * </code> * @type {Ember.Enumerable} */ - masterHostMapping: [], - - isLoaded: false, - - /** - * Check if HIVE_SERVER component exist (also checks if this is not reassign) - * @type {bool} - */ - hasHiveServer: function () { - return this.get('selectedServicesMasters').someProperty('component_name', 'HIVE_SERVER') && !this.get('isReassignWizard'); - }.property('selectedServicesMasters'), - - masterHostMappingObserver: function () { - var requestName = this.get('content.controllerName') == 'installerController' ? 'hosts.confirmed.install' : 'hosts.confirmed' - App.ajax.send({ - name: requestName, - sender: this, - data: { - clusterName: App.get('clusterName') - }, - success: 'masterHostMappingSuccessCallback' - }); - }.observes('selectedServicesMasters', '[email protected]'), - - masterHostMappingSuccessCallback: function (response) { + masterHostMapping: function () { var mapping = [], mappingObject, mappedHosts, hostObj; //get the unique assigned hosts and find the master services assigned to them - mappedHosts = this.get("selectedServicesMasters").mapProperty("selectedHost").uniq().without(undefined); + mappedHosts = this.get("selectedServicesMasters").mapProperty("selectedHost").uniq(); mappedHosts.forEach(function (item) { - var host = response.items.mapProperty('Hosts').findProperty('host_name', item); - hostObj = { - name: host.host_name, - memory: host.total_mem, - cpu: host.cpu_count - }; - + hostObj = this.get("hosts").findProperty("host_name", item); + // User may input invalid host name (this is handled in hostname checker). Here we just skip it + if (!hostObj) return; + var masterServices = this.get("selectedServicesMasters").filterProperty("selectedHost", item), + masterServicesToDisplay = []; + masterServices.mapProperty('display_name').uniq().forEach(function(n) { + masterServicesToDisplay.pushObject(masterServices.findProperty('display_name', n)); + }); mappingObject = Em.Object.create({ host_name: item, - hostInfo: Em.I18n.t('installer.step5.hostInfo').fmt(hostObj.name, numberUtils.bytesToSize(hostObj.memory, 1, 'parseFloat', 1024), hostObj.cpu), - masterServices: this.get("selectedServicesMasters").filterProperty("selectedHost", item) + hostInfo: hostObj.host_info, + masterServices: masterServices, + masterServicesToDisplay: masterServicesToDisplay }); mapping.pushObject(mappingObject); }, this); - this.set('masterHostMapping', []); - lazyLoading.run({ - initSize: 20, - chunkSize: 50, - delay: 50, - destination: this.get('masterHostMapping'), - source: mapping.sortProperty('host_name'), - context: Em.Object.create() - }); - }, - + return mapping.sortProperty('host_name'); + }.property("[email protected]", '[email protected]'), + /** * Count of hosts without masters * @type {number} @@ -213,10 +177,49 @@ App.WizardStep5Controller = Em.Controller.extend({ return 0; } else { return (this.get("hosts.length") - this.get("masterHostMapping.length")); - }; + } }.property('masterHostMapping.length', '[email protected]'), /** + * Update submit button status + * @metohd getIsSubmitDisabled + */ + getIsSubmitDisabled: function () { + if (!this.get('isReassignWizard')) { + this.set('submitDisabled', this.get('servicesMasters').someProperty('isHostNameValid', false)); + } + else { + App.ajax.send({ + name: 'host_components.all', + sender: this, + data: { + clusterName: App.get('clusterName') + }, + success: 'getIsSubmitDisabledSuccessCallBack' + }); + } + }.observes('[email protected]', '[email protected]'), + + /** + * Success callback for getIsSubmitDisabled method + * Set true for Reassign Master Wizard and if more than one master component was reassigned. + * For installer, addHost and addService verify that provided host names for components are valid + * @param {object} response + * @method getIsSubmitDisabledSuccessCallBack + */ + getIsSubmitDisabledSuccessCallBack: function (response) { + var reassigned = 0; + var arr1 = response.items.mapProperty('HostRoles').filterProperty('component_name', this.get('content.reassign.component_name')).mapProperty('host_name'); + var arr2 = this.get('servicesMasters').mapProperty('selectedHost'); + arr1.forEach(function (host) { + if (!arr2.contains(host)) { + reassigned++; + } + }, this); + this.set('submitDisabled', reassigned !== 1); + }, + + /** * Clear controller data (hosts, masters etc) * @method clearStep */ @@ -291,15 +294,7 @@ App.WizardStep5Controller = Em.Controller.extend({ })); } } - this.set("hosts", []); - lazyLoading.run({ - initSize: 20, - chunkSize: 50, - delay: 50, - destination: this.get('hosts'), - source: result, - context: Em.Object.create() - }); + this.set("hosts", result); this.sortHosts(this.get('hosts')); this.set('isLoaded', true); }, @@ -421,7 +416,7 @@ App.WizardStep5Controller = Em.Controller.extend({ componentObj.set("showRemoveControl", showRemoveControlZk); } else { - if (App.supports.multipleHBaseMasters && item.component_name === "HBASE_MASTER") { + if (App.get('supports.multipleHBaseMasters') && item.component_name === "HBASE_MASTER") { componentObj.set('zId', hid++); componentObj.set("showRemoveControl", showRemoveControlHb); } @@ -431,6 +426,7 @@ App.WizardStep5Controller = Em.Controller.extend({ } } } + componentObj.set('isHostNameValid', true); result.push(componentObj); }, this); @@ -559,18 +555,61 @@ App.WizardStep5Controller = Em.Controller.extend({ }, /** - * On change callback for selects + * On change callback for inputs * @param {string} componentName * @param {string} selectedHost * @param {number} zId * @method assignHostToMaster */ assignHostToMaster: function (componentName, selectedHost, zId) { - if (selectedHost && componentName) { + var flag = this.isHostNameValid(componentName, selectedHost); + this.updateIsHostNameValidFlag(componentName, zId, flag); + if (zId) { + this.get('selectedServicesMasters').filterProperty('component_name', componentName).findProperty("zId", zId).set("selectedHost", selectedHost); + } + else { + this.get('selectedServicesMasters').findProperty("component_name", componentName).set("selectedHost", selectedHost); + } + }, + + /** + * Determines if hostName is valid for component: + * <ul> + * <li>host name shouldn't be empty</li> + * <li>host should exist</li> + * <li>host should have only one component with <code>componentName</code></li> + * </ul> + * @param {string} componentName + * @param {string} selectedHost + * @returns {boolean} true - valid, false - invalid + * @method isHostNameValid + */ + isHostNameValid: function(componentName, selectedHost) { + return (selectedHost.trim() !== '') && + this.get('hosts').mapProperty('host_name').contains(selectedHost) && + (this.get('selectedServicesMasters'). + filterProperty('component_name', componentName). + mapProperty('selectedHost'). + filter(function(h) { + return h === selectedHost; + }).length <= 1); + }, + + /** + * Update <code>isHostNameValid</code> property with <code>flag</code> value + * for component with name <code>componentName</code> and + * <code>zId</code>-property equal to <code>zId</code>-parameter value + * @param {string} componentName + * @param {number} zId + * @param {bool} flag + * @method updateIsHostNameValidFlag + */ + updateIsHostNameValidFlag: function (componentName, zId, flag) { + if (componentName) { if (zId) { - this.get('selectedServicesMasters').filterProperty('component_name', componentName).findProperty("zId", zId).set("selectedHost", selectedHost); + this.get('selectedServicesMasters').filterProperty('component_name', componentName).findProperty("zId", zId).set("isHostNameValid", flag); } else { - this.get('selectedServicesMasters').findProperty("component_name", componentName).set("selectedHost", selectedHost); + this.get('selectedServicesMasters').findProperty("component_name", componentName).set("isHostNameValid", flag); } } }, @@ -648,7 +687,7 @@ App.WizardStep5Controller = Em.Controller.extend({ this.set('componentToRebalance', componentName); this.incrementProperty('rebalanceComponentHostsCounter'); - + this.toggleProperty('hostNameCheckTrigger'); return true; } return false;//if no more zookeepers can be added @@ -682,7 +721,7 @@ App.WizardStep5Controller = Em.Controller.extend({ this.set('componentToRebalance', componentName); this.incrementProperty('rebalanceComponentHostsCounter'); - + this.toggleProperty('hostNameCheckTrigger'); return true; }, @@ -691,10 +730,10 @@ App.WizardStep5Controller = Em.Controller.extend({ * @metohd submit */ submit: function () { - this.isSubmitDisabled(); + this.getIsSubmitDisabled(); if (!this.get('submitDisabled')) { App.router.send('next'); } } -}); \ No newline at end of file +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/5d3fad2c/ambari-web/app/mixins.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins.js b/ambari-web/app/mixins.js index c94b459..e8a89a8 100644 --- a/ambari-web/app/mixins.js +++ b/ambari-web/app/mixins.js @@ -22,4 +22,5 @@ require('mixins/common/localStorage'); require('mixins/common/userPref'); require('mixins/common/tableServerProvider'); -require('mixins/main/host/details/host_components/decommissionable'); \ No newline at end of file +require('mixins/main/host/details/host_components/decommissionable'); +require('mixins/wizard/selectHost'); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/5d3fad2c/ambari-web/app/mixins/wizard/selectHost.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/wizard/selectHost.js b/ambari-web/app/mixins/wizard/selectHost.js new file mode 100644 index 0000000..38d99f0 --- /dev/null +++ b/ambari-web/app/mixins/wizard/selectHost.js @@ -0,0 +1,179 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var App = require('app'); + +/** + * Mixin for host select view on step 5 install wizard + * Implements base login for select, input types + * Each view which use this mixin should redefine method <code>changeHandler</code> (with + * observing <code>controller.hostNameCheckTrigger</code>) + * + * @type {Ember.Mixin} + */ +App.SelectHost = Em.Mixin.create({ + + /** + * Element of <code>controller.servicesMasters</code> + * Binded from template + * @type {object} + */ + component: null, + + /** + * List of avaiable host names + * @type {string[]} + */ + content: [], + + /** + * Host component name + * @type {string} + */ + componentName: null, + + /** + * Handler for selected value change + * Triggers <code>changeHandler</code> execution + * @method change + */ + change: function () { + if ('destroyed' === this.get('state')) return; + this.set('controller.lastChangedComponent', this.get('component.component_name')); + this.get('controller').toggleProperty('hostNameCheckTrigger'); + }, + + /** + * Add or remove <code>error</code> class from parent div-element + * @param {bool} flag true - add class, false - remove + * @method updateErrorStatus + */ + updateErrorStatus: function(flag) { + var parentBlock = this.$().parent('div'); + /* istanbul ignore next */ + if (flag) { + parentBlock.removeClass('error'); + } + else { + parentBlock.addClass('error'); + } + }, + + /** + * If component is multiple (defined in <code>controller.multipleComponents</code>), + * should update <code>controller.componentToRebalance</code> and trigger rebalancing + * @method tryTriggerRebalanceForMultipleComponents + */ + tryTriggerRebalanceForMultipleComponents: function() { + var componentIsMultiple = this.get('controller.multipleComponents').contains(this.get("component.component_name")); + if(componentIsMultiple) { + this.get('controller').set('componentToRebalance', this.get("component.component_name")); + this.get('controller').incrementProperty('rebalanceComponentHostsCounter'); + } + }, + + /** + * Handler for value changing + * Should be redeclared in child views + * Each redeclarion should contains code: + * <code> + * if (!this.shouldChangeHandlerBeCalled()) return; + * </code> + * @method changeHandler + */ + changeHandler: function() {}.observes('controller.hostNameCheckTrigger'), + + /** + * If view is destroyed or not current component's host name was changed we should do nothing + * This method should be called from <code>changeHandler</code>: + * <code> + * changeHandler: function() { + * if (!this.shouldChangeHandlerBeCalled()) return; + * // your code + * }.observes(...) + * </code> + * @returns {boolean} + * @method shouldChangeHandlerBeCalled + */ + shouldChangeHandlerBeCalled: function() { + return !(('destroyed' === this.get('state')) || (this.get('controller.lastChangedComponent') !== this.get("component.component_name"))); + }, + + /** + * If <code>component.isHostNameValid</code> was changed, + * error status should be updated according to new value + * @method isHostNameValidObs + */ + isHostNameValidObs: function() { + this.updateErrorStatus(this.get('component.isHostNameValid')); + }.observes('component.isHostNameValid'), + + /** + * Recalculate available hosts + * This should be done only once per Ember loop + * @method rebalanceComponentHosts + */ + rebalanceComponentHosts: function () { + Em.run.once(this, 'rebalanceComponentHostsOnce'); + }.observes('controller.rebalanceComponentHostsCounter'), + + /** + * Recalculate available hosts + * @method rebalanceComponentHostsOnce + */ + rebalanceComponentHostsOnce: function() { + if (this.get('component.component_name') === this.get('controller.componentToRebalance')) { + this.initContent(); + } + }, + + /** + * Get available hosts + * multipleComponents component can be assigned to multiple hosts, + * shared hosts among the same component should be filtered out + * @return {string[]} + * @method getAvailableHosts + */ + getAvailableHosts: function () { + var hosts = this.get('controller.hosts').slice(), + componentName = this.get('component.component_name'), + multipleComponents = this.get('controller.multipleComponents'), + occupiedHosts = this.get('controller.selectedServicesMasters') + .filterProperty('component_name', componentName) + .mapProperty('selectedHost') + .without(this.get('component.selectedHost')); + + if (multipleComponents.contains(componentName)) { + return hosts.filter(function (host) { + return !occupiedHosts.contains(host.get('host_name')); + }, this); + } + return hosts; + }, + + /** + * Extract hosts from controller, + * filter out available to selection and + * push them into form element content + * @method initContent + */ + initContent: function () { + this.set("content", this.getAvailableHosts()); + } + +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/5d3fad2c/ambari-web/app/styles/application.less ---------------------------------------------------------------------- diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less index ca86af4..00bacb5 100644 --- a/ambari-web/app/styles/application.less +++ b/ambari-web/app/styles/application.less @@ -79,6 +79,10 @@ wbr { display: inline-block; } +ul.typeahead.dropdown-menu { + z-index: 3000000 !important; +} + #wrapper { min-height: 100%; } http://git-wip-us.apache.org/repos/asf/ambari/blob/5d3fad2c/ambari-web/app/templates/wizard/step5.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/wizard/step5.hbs b/ambari-web/app/templates/wizard/step5.hbs index 2718a81..4994557 100644 --- a/ambari-web/app/templates/wizard/step5.hbs +++ b/ambari-web/app/templates/wizard/step5.hbs @@ -61,21 +61,25 @@ {{selectedHost}}<i class="icon-asterisks">✵</i> </div> {{else}} - {{view App.SelectHostView - optionValuePath="content.host_name" - optionLabelPath="content.host_info" - selectedHostBinding="selectedHost" - componentNameBinding="component_name" - class="host-select" - zIdBinding="zId" - disabledBinding="isInstalled" - }} + <div class="control-group"> + {{#if view.shouldUseInputs}} + {{view App.InputHostView + componentBinding="this" + disabledBinding="isInstalled" }} + {{else}} + {{view App.SelectHostView + componentBinding="this" + disabledBinding="isInstalled" + optionValuePath="content.host_name" + optionLabelPath="content.host_info" }} + {{/if}} {{#if showAddControl}} {{view App.AddControlView componentNameBinding="component_name"}} {{/if}} {{#if showRemoveControl}} {{view App.RemoveControlView componentNameBinding="component_name" zIdBinding="zId"}} {{/if}} + </div> {{/if}} </div> </div> @@ -90,7 +94,7 @@ {{#each masterHostMapping}} <div class="mapping-box round-corners well"> <div class="hostString"><span>{{hostInfo}}</span></div> - {{#each masterServices}} + {{#each masterServicesToDisplay}} <span {{bindAttr class="isInstalled:assignedService:newService :round-corners"}}>{{display_name}}</span> {{/each}} </div> @@ -109,4 +113,4 @@ <div class="btn-area"> <a class="btn pull-left" {{action back href="true"}}>← {{t common.back}}</a> <a class="btn btn-success pull-right" {{bindAttr disabled="submitDisabled"}} {{action submit target="controller"}}>{{t common.next}} →</a> -</div> \ No newline at end of file +</div> http://git-wip-us.apache.org/repos/asf/ambari/blob/5d3fad2c/ambari-web/app/views/wizard/step5_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/wizard/step5_view.js b/ambari-web/app/views/wizard/step5_view.js index 5a55e0f..3166d4a 100644 --- a/ambari-web/app/views/wizard/step5_view.js +++ b/ambari-web/app/views/wizard/step5_view.js @@ -16,160 +16,120 @@ * limitations under the License. */ - var App = require('app'); -var lazyloading = require('utils/lazy_loading'); App.WizardStep5View = Em.View.extend({ templateName: require('templates/wizard/step5'), + /** + * If install more than 25 hosts, should use App.InputHostView for hosts selection + * Othervise - App.SelectHostView + * @type {bool} + */ + shouldUseInputs: function() { + return this.get('controller.hosts.length') > 25; + }.property('controller.hosts.length'), + didInsertElement: function () { this.get('controller').loadStep(); } }); -App.SelectHostView = Em.Select.extend({ +App.InputHostView = Em.TextField.extend(App.SelectHost, { + + attributeBindings: ['disabled'], /** - * List of avaiable host names - * @type {string[]} + * Saved typeahead component + * @type {$} */ - content: [], + typeahead: null, /** - * Index for multiple component (like ZOOKEEPER_SERVER) - * @type {number|null} + * When <code>value</code> (host_info) is changed this method is triggered + * If new hostname is valid, this host is assigned to master component + * @method changeHandler */ - zId: null, + changeHandler: function() { + if (!this.shouldChangeHandlerBeCalled()) return; + var host = this.get('controller.hosts').findProperty('host_info', this.get('value')); + if (Em.isNone(host)) { + this.get('controller').updateIsHostNameValidFlag(this.get("component.component_name"), this.get("component.zId"), false); + return; + } + this.get('controller').assignHostToMaster(this.get("component.component_name"), host.get('host_name'), this.get("component.zId")); + this.tryTriggerRebalanceForMultipleComponents(); + }.observes('controller.hostNameCheckTrigger'), + + didInsertElement: function () { + this.initContent(); + var value = this.get('content').findProperty('host_name', this.get('component.selectedHost')).get('host_info'); + this.set("value", value); + var content = this.get('content').mapProperty('host_info'), + self = this, + typeahead = this.$().typeahead({items: 10, source: content, minLength: 0}); + typeahead.on('blur', function() { + self.change(); + }).on('keyup', function(e) { + self.set('value', $(e.currentTarget).val()); + self.change(); + }); + this.set('typeahead', typeahead); + }, /** - * Selected host name for host component - * @type {string} + * Extract hosts from controller, + * filter out available to selection and + * push them into Em.Select content + * @method initContent */ - selectedHost: null, + initContent: function () { + this._super(); + this.updateTypeaheadData(this.get('content').mapProperty('host_info')); + }, /** - * Host component name - * @type {string} + * Update <code>source</code> property of <code>typeahead</code> with a new list of hosts + * @param {string[]} hosts + * @method updateTypeaheadData */ - componentName: null, + updateTypeaheadData: function(hosts) { + if (this.get('typeahead')) { + this.get('typeahead').data('typeahead').source = hosts; + } + } - attributeBindings: ['disabled'], +}); - /** - * Is data loaded - * @type {bool} - */ - isLoaded: false, +App.SelectHostView = Em.Select.extend(App.SelectHost, { - /** - * Is lazy loading used - * @type {bool} - */ - isLazyLoading: false, + attributeBindings: ['disabled'], - /** - * Handler for selected value change - * @method change - */ - change: function () { - this.get('controller').assignHostToMaster(this.get("componentName"), this.get("value"), this.get("zId")); - this.set('selectedHost', this.get('value')); - this.get('controller').set('componentToRebalance', this.get("componentName")); - this.get('controller').incrementProperty('rebalanceComponentHostsCounter'); + didInsertElement: function () { + this.initContent(); + this.set("value", this.get("component.selectedHost")); }, /** - * Recalculate available hosts - * @method rebalanceComponentHosts - */ - rebalanceComponentHosts: function () { - if (this.get('componentName') === this.get('controller.componentToRebalance')) { - this.get('content').clear(); - this.set('isLoaded', false); - this.initContent(); - } - }.observes('controller.rebalanceComponentHostsCounter'), - - /** - * Get available hosts - * multipleComponents component can be assigned to multiple hosts, - * shared hosts among the same component should be filtered out - * @return {string[]} - * @method getAvailableHosts + * Handler for selected value change + * @method change */ - getAvailableHosts: function () { - var hosts = this.get('controller.hosts').slice(), - componentName = this.get('componentName'), - multipleComponents = this.get('controller.multipleComponents'), - occupiedHosts = this.get('controller.selectedServicesMasters') - .filterProperty('component_name', componentName) - .mapProperty('selectedHost') - .without(this.get('selectedHost')); - - if (multipleComponents.contains(componentName)) { - return hosts.filter(function (host) { - return !occupiedHosts.contains(host.get('host_name')); - }, this); - } - return hosts; - }, + changeHandler: function () { + if (!this.shouldChangeHandlerBeCalled()) return; + this.get('controller').assignHostToMaster(this.get("component.component_name"), this.get("value"), this.get("component.zId")); + this.tryTriggerRebalanceForMultipleComponents(); + }.observes('controller.hostNameCheckTrigger'), /** - * On click start lazy loading + * On click handler * @method click */ click: function () { - var source = []; - var availableHosts = this.getAvailableHosts(); - - if (!this.get('isLoaded') && this.get('isLazyLoading')) { - //filter out hosts, which already pushed in select - source = availableHosts.filter(function (_host) { - return !this.get('content').someProperty('host_name', _host.host_name); - }, this).slice(); - lazyloading.run({ - destination: this.get('content'), - source: source, - context: this, - initSize: 30, - chunkSize: 200, - delay: 50 - }); - } - }, - - didInsertElement: function () { - //The lazy loading for select elements supported only by Firefox and Chrome - var isBrowserSupported = $.browser.mozilla || ($.browser.safari && navigator.userAgent.indexOf('Chrome') !== -1); - var isLazyLoading = isBrowserSupported && this.get('controller.hosts').length > 100; - this.set('isLazyLoading', isLazyLoading); this.initContent(); - this.set("value", this.get("selectedHost")); - }, - - /** - * Extract hosts from controller, - * filter out available to selection and - * push them into Em.Select content - * @method initContent - */ - initContent: function () { - var hosts = this.getAvailableHosts(); - if (this.get('isLazyLoading')) { - //select need at least 30 hosts to have scrollbar - var initialHosts = hosts.slice(0, 30); - if (!initialHosts.someProperty('host_name', this.get('selectedHost'))) { - initialHosts.unshift(hosts.findProperty('host_name', this.get('selectedHost'))); - } - this.set("content", initialHosts); - } - else { - this.set("content", hosts); - } } + }); App.AddControlView = Em.View.extend({ @@ -224,4 +184,4 @@ App.RemoveControlView = Em.View.extend({ click: function () { this.get('controller').removeComponent(this.get('componentName'), this.get("zId")); } -}); +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/5d3fad2c/ambari-web/test/views/wizard/step5_view_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/views/wizard/step5_view_test.js b/ambari-web/test/views/wizard/step5_view_test.js index d2e94ce..7107461 100644 --- a/ambari-web/test/views/wizard/step5_view_test.js +++ b/ambari-web/test/views/wizard/step5_view_test.js @@ -18,7 +18,6 @@ var App = require('app'); -var lazyloading = require('utils/lazy_loading'); require('views/wizard/step5_view'); var view; @@ -39,59 +38,176 @@ describe('App.WizardStep5View', function() { }); }); + describe('#shouldUseInputs', function() { + it('should based on hosts count', function() { + view.set('controller.hosts', d3.range(0, 25).map(function() {return {};})); + expect(view.get('shouldUseInputs')).to.be.false; + view.set('controller.hosts', d3.range(0, 26).map(function() {return {};})); + expect(view.get('shouldUseInputs')).to.be.true; + view.set('controller.hosts', d3.range(0, 24).map(function() {return {};})); + expect(view.get('shouldUseInputs')).to.be.false; + }); + }); + }); describe('App.SelectHostView', function() { beforeEach(function() { view = App.SelectHostView.create({ - controller: App.WizardStep5Controller.create({}) + controller: App.WizardStep5Controller.create({}), + $: function() {return {typeahead: function(){return {on: Em.K}}}}, + updateErrorStatus: Em.K + }); + }); + + describe('#click', function() { + + beforeEach(function() { + sinon.stub(view, 'initContent', Em.K); + }); + + afterEach(function() { + view.initContent.restore(); }); + + it('should call initContent', function() { + view.click(); + expect(view.initContent.calledOnce).to.be.true; + }); + }); describe('#didInsertElement', function() { + + it('should set value', function() { + view.set('value', ''); + view.set('component', {selectedHost: 'h1'}); + view.didInsertElement(); + expect(view.get('value')).to.equal('h1'); + }); + + }); + + describe('#changeHandler', function() { + + beforeEach(function() { + view.set('component', {component_name: 'ZOOKEEPER_SERVER', zId: 1}); + view.set('controller.hosts', [Em.Object.create({host_info: 'h1 info', host_name: 'h1'})]); + view.set('value', 'h1 info'); + view.set('controller.rebalanceComponentHostsCounter', 0); + view.set('controller.componentToRebalance', ''); + sinon.stub(view.get('controller'), 'assignHostToMaster', Em.K); + sinon.stub(view.get('controller'), 'updateIsHostNameValidFlag', Em.K); + sinon.stub(view, 'shouldChangeHandlerBeCalled', function() {return true;}); + }); + + afterEach(function() { + view.get('controller').assignHostToMaster.restore(); + view.get('controller').updateIsHostNameValidFlag.restore(); + view.shouldChangeHandlerBeCalled.restore(); + }); + + it('shouldn\'t do nothing if view is destroyed', function() { + view.set('state', 'destroyed'); + expect(view.get('controller').assignHostToMaster.called).to.be.false; + }); + + it('should call assignHostToMaster', function() { + view.changeHandler(); + expect(view.get('controller').assignHostToMaster.calledWith('ZOOKEEPER_SERVER', 'h1', 1)); + }); + + it('should increment rebalanceComponentHostsCounter if component it is multiple', function() { + view.set('component', {component_name: 'ZOOKEEPER_SERVER'}); + view.changeHandler(); + expect(view.get('controller.rebalanceComponentHostsCounter')).to.equal(1); + }); + + it('should set componentToRebalance', function() { + view.changeHandler(); + expect(view.get('controller.componentToRebalance')).to.equal('ZOOKEEPER_SERVER'); + }); + + }); + +}); + +describe('App.InputHostView', function() { + + beforeEach(function() { + view = App.InputHostView.create({ + controller: App.WizardStep5Controller.create({}), + $: function() {return {typeahead: function(){return {on: Em.K}}}}, + updateErrorStatus: Em.K + }); + }); + + describe('#didInsertElement', function() { + beforeEach(function() { sinon.stub(view, 'initContent', Em.K); + view.set('content', [Em.Object.create({host_name: 'h1', host_info: 'h1 info'})]); + view.set('component', {selectedHost: 'h1'}); }); + afterEach(function() { view.initContent.restore(); }); + it('should call initContent', function() { view.didInsertElement(); expect(view.initContent.calledOnce).to.equal(true); }); - it('should set selectedHost to value', function() { - view.set('selectedHost', 'h1'); + + it('should set selectedHost host_info to value', function() { view.set('value', ''); view.didInsertElement(); - expect(view.get('value')).to.equal('h1'); + expect(view.get('value')).to.equal('h1 info'); }); + }); - describe('#change', function() { + describe('#changeHandler', function() { + beforeEach(function() { - view.set('componentName', 'ZOOKEEPER_SERVER'); - view.set('value', 'h1'); - view.set('zId', 1); + view.set('component', {component_name: 'ZOOKEEPER_SERVER', zId: 1}); + view.set('controller.hosts', [Em.Object.create({host_info: 'h1 info', host_name: 'h1'})]); + view.set('value', 'h1 info'); view.set('controller.rebalanceComponentHostsCounter', 0); view.set('controller.componentToRebalance', ''); sinon.stub(view.get('controller'), 'assignHostToMaster', Em.K); + sinon.stub(view.get('controller'), 'updateIsHostNameValidFlag', Em.K); + sinon.stub(view, 'shouldChangeHandlerBeCalled', function() {return true;}); }); + afterEach(function() { view.get('controller').assignHostToMaster.restore(); + view.get('controller').updateIsHostNameValidFlag.restore(); + view.shouldChangeHandlerBeCalled.restore(); + }); + + it('shouldn\'t do nothing if view is destroyed', function() { + view.set('state', 'destroyed'); + expect(view.get('controller').assignHostToMaster.called).to.be.false; }); + it('should call assignHostToMaster', function() { - view.change(); + view.changeHandler(); expect(view.get('controller').assignHostToMaster.calledWith('ZOOKEEPER_SERVER', 'h1', 1)); }); - it('should increment rebalanceComponentHostsCounter', function() { - view.change(); + + it('should increment rebalanceComponentHostsCounter if component it is multiple', function() { + view.set('component', {component_name: 'ZOOKEEPER_SERVER'}); + view.changeHandler(); expect(view.get('controller.rebalanceComponentHostsCounter')).to.equal(1); }); + it('should set componentToRebalance', function() { - view.change(); + view.changeHandler(); expect(view.get('controller.componentToRebalance')).to.equal('ZOOKEEPER_SERVER'); }); + }); describe('#getAvailableHosts', function() { @@ -149,7 +265,7 @@ describe('App.SelectHostView', function() { tests.forEach(function(test) { it(test.m, function() { view.set('controller.hosts', test.hosts); - view.set('componentName', test.componentName); + view.set('component', {component_name: test.componentName}); view.set('controller.selectedServicesMasters', test.selectedServicesMasters); var r = view.getAvailableHosts(); expect(r.mapProperty('host_name')).to.eql(test.e); @@ -157,45 +273,36 @@ describe('App.SelectHostView', function() { }); }); - describe('#rebalanceComponentHosts', function() { + describe('#rebalanceComponentHostsOnce', function() { var tests = Em.A([ { componentName: 'c1', componentToRebalance: 'c2', - isLoaded: true, content: [{}], m: 'componentName not equal to componentToRebalance', e: { - initContent: false, - isLoaded: true, - content: 1 + initContent: false } }, { componentName: 'c2', componentToRebalance: 'c2', - isLoaded: true, content: [{}], m: 'componentName equal to componentToRebalance', e: { - initContent: true, - isLoaded: false, - content: 0 + initContent: true } } ]); tests.forEach(function(test) { it(test.m, function() { - view.set('isLoaded', test.isLoaded); view.set('content', test.content); - view.set('componentName', test.componentName); + view.set('component', {component_name: test.componentName}); view.set('controller.componentToRebalance', test.componentToRebalance); sinon.stub(view, 'initContent', Em.K); - view.rebalanceComponentHosts(); + view.rebalanceComponentHostsOnce(); expect(view.initContent.calledOnce).to.equal(test.e.initContent); - expect(view.get('isLoaded')).to.equal(test.e.isLoaded); - expect(view.get('content.length')).to.equal(test.e.content); view.initContent.restore(); }); }); @@ -204,43 +311,15 @@ describe('App.SelectHostView', function() { describe('#initContent', function() { var tests = Em.A([ { - isLazyLoading: false, hosts: 25, m: 'not lazy loading, 25 hosts, no selected host', e: 25 }, { - isLazyLoading: false, hosts: 25, h: 4, m: 'not lazy loading, 25 hosts, one selected host', e: 25 - }, - { - isLazyLoading: true, - hosts: 25, - h: 4, - m: 'lazy loading, 25 hosts, one selected host', - e: 25 - }, - { - isLazyLoading: true, - hosts: 25, - m: 'lazy loading, 25 hosts, no selected host', - e: 26 - }, - { - isLazyLoading: true, - hosts: 100, - h: 4, - m: 'lazy loading, 100 hosts, one selected host', - e: 30 - }, - { - isLazyLoading: true, - hosts: 100, - m: 'lazy loading, 100 hosts, no selected host', - e: 31 } ]); tests.forEach(function(test) { @@ -249,68 +328,35 @@ describe('App.SelectHostView', function() { if (test.h) { view.set('selectedHost', test.h); } - view.set('isLazyLoading', test.isLazyLoading); view.initContent(); expect(view.get('content.length')).to.equal(test.e); }); }); }); - describe('#click', function() { + describe('#change', function() { + beforeEach(function() { - sinon.stub(lazyloading, 'run', Em.K); + sinon.stub(view, 'changeHandler', Em.K); }); + afterEach(function() { - lazyloading.run.restore(); - }); - Em.A([ - { - isLoaded: true, - isLazyLoading: true, - e: false - }, - { - isLoaded: true, - isLazyLoading: false, - e: false - }, - { - isLoaded: false, - isLazyLoading: true, - e: true - }, - { - isLoaded: false, - isLazyLoading: false, - e: false - } - ]).forEach(function(test) { - it('isLoaded = ' + test.isLoaded.toString() + ', isLazyLoading = ' + test.isLazyLoading.toString(), function() { - view.reopen({ - isLazyLoading: test.isLazyLoading, - isLoaded: test.isLoaded - }); - view.click(); - if(test.e) { - expect(lazyloading.run.calledOnce).to.equal(true); - } - else { - expect(lazyloading.run.called).to.equal(false); - } - }); + view.changeHandler.restore(); }); - it('check lazyLoading parameters', function() { - view.reopen({ - isLoaded: false, - isLazyLoading: true, - content: [{host_name: 'host1'}, {host_name: 'host2'}] - }); - var availableHosts = d3.range(1, 100).map(function(i) {return {host_name: 'host' + i.toString()};}); - sinon.stub(view, 'getAvailableHosts', function() {return availableHosts;}); - view.click(); - expect(lazyloading.run.args[0][0].source.length).to.equal(97); // 99-2 - view.getAvailableHosts.restore(); + + it('shouldn\'t do nothing if view is destroyed', function() { + view.set('controller.hostNameCheckTrigger', false); + view.set('state', 'destroyed'); + view.change(); + expect(view.get('controller.hostNameCheckTrigger')).to.equal(false); + }); + + it('should toggle hostNameCheckTrigger', function() { + view.set('controller.hostNameCheckTrigger', false); + view.change(); + expect(view.get('controller.hostNameCheckTrigger')).to.equal(true); }); + }); }); @@ -366,4 +412,4 @@ describe('App.AddControlView', function() { }); -}); \ No newline at end of file +});
