Updated Branches: refs/heads/trunk aa4b88fed -> 5c767210d
AMBARI-3525 UI optimization: install wizard. (atkach) Project: http://git-wip-us.apache.org/repos/asf/incubator-ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ambari/commit/5c767210 Tree: http://git-wip-us.apache.org/repos/asf/incubator-ambari/tree/5c767210 Diff: http://git-wip-us.apache.org/repos/asf/incubator-ambari/diff/5c767210 Branch: refs/heads/trunk Commit: 5c767210d83fd8bd07a984f59d43410c98ff73c6 Parents: aa4b88f Author: atkach <[email protected]> Authored: Fri Oct 18 15:05:26 2013 +0300 Committer: atkach <[email protected]> Committed: Fri Oct 18 15:05:26 2013 +0300 ---------------------------------------------------------------------- ambari-web/app/assets/test/tests.js | 1 + .../app/controllers/wizard/step3_controller.js | 78 +++++++------ .../app/controllers/wizard/step5_controller.js | 19 +++- .../app/controllers/wizard/step6_controller.js | 24 +++- ambari-web/app/routes/installer.js | 12 +- ambari-web/app/utils/ajax.js | 1 - ambari-web/app/utils/lazy_loading.js | 98 +++++++++++++++++ ambari-web/app/views/wizard/step3_view.js | 2 +- ambari-web/app/views/wizard/step5_view.js | 21 ++++ ambari-web/test/utils/lazy_loading_test.js | 110 +++++++++++++++++++ 10 files changed, 319 insertions(+), 47 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/5c767210/ambari-web/app/assets/test/tests.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js index 014bf9f..521a0cc 100644 --- a/ambari-web/app/assets/test/tests.js +++ b/ambari-web/app/assets/test/tests.js @@ -70,6 +70,7 @@ require('test/utils/misc_test'); require('test/utils/validator_test'); require('test/utils/config_test'); require('test/utils/string_utils_test'); +require('test/utils/lazy_loading_test'); require('test/views/common/chart/linear_time_test'); require('test/views/common/filter_view_test'); require('test/views/common/quick_link_view_test'); http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/5c767210/ambari-web/app/controllers/wizard/step3_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step3_controller.js b/ambari-web/app/controllers/wizard/step3_controller.js index ca55c9d..f123b9e 100644 --- a/ambari-web/app/controllers/wizard/step3_controller.js +++ b/ambari-web/app/controllers/wizard/step3_controller.js @@ -17,6 +17,7 @@ */ var App = require('app'); +var lazyloading = require('utils/lazy_loading'); App.WizardStep3Controller = Em.Controller.extend({ name: 'wizardStep3Controller', @@ -99,28 +100,30 @@ App.WizardStep3Controller = Em.Controller.extend({ }.property('[email protected]'), isRetryDisabled: true, + isLoaded: false, navigateStep: function () { - this.loadStep(); - if (this.get('content.installOptions.manualInstall') !== true) { - if (!App.db.getBootStatus()) { - this.startBootstrap(); - } - } else { - this.set('bootHosts', this.get('hosts')); - if (App.testMode) { - this.getHostInfo(); - this.get('bootHosts').setEach('bootStatus', 'REGISTERED'); - this.get('bootHosts').setEach('cpu', '2'); - this.get('bootHosts').setEach('memory', '2000000'); - this.set('isSubmitDisabled', false); + if(this.get('isLoaded')){ + if (this.get('content.installOptions.manualInstall') !== true) { + if (!App.db.getBootStatus()) { + this.startBootstrap(); + } } else { - this.set('registrationStartedAt', null); - this.get('bootHosts').setEach('bootStatus', 'DONE'); - this.startRegistration(); + this.set('bootHosts', this.get('hosts')); + if (App.testMode) { + this.getHostInfo(); + this.get('bootHosts').setEach('bootStatus', 'REGISTERED'); + this.get('bootHosts').setEach('cpu', '2'); + this.get('bootHosts').setEach('memory', '2000000'); + this.set('isSubmitDisabled', false); + } else { + this.set('registrationStartedAt', null); + this.get('bootHosts').setEach('bootStatus', 'DONE'); + this.startRegistration(); + } } } - }, + }.observes('isLoaded'), clearStep: function () { this.set('stopBootstrap', false); @@ -134,37 +137,42 @@ App.WizardStep3Controller = Em.Controller.extend({ loadStep: function () { console.log("TRACE: Loading step3: Confirm Hosts"); this.set('registrationStartedAt', null); + this.set('isLoaded', false); this.clearStep(); - var hosts = this.loadHosts(); + this.loadHosts(); // hosts.setEach('bootStatus', 'RUNNING'); - this.renderHosts(hosts); }, /* Loads the hostinfo from localStorage on the insertion of view. It's being called from view */ loadHosts: function () { - var hostInfo = this.get('content.hosts'); - var hosts = new Ember.Set(); - for (var index in hostInfo) { - hosts.add(hostInfo[index]); - console.log("TRACE: host name is: " + hostInfo[index].name); - } - return hosts; - }, + var hostsInfo = this.get('content.hosts'); + var hosts = []; - /* Renders the set of passed hosts */ - renderHosts: function (hostsInfo) { - var self = this; - hostsInfo.forEach(function (_hostInfo) { + for (var index in hostsInfo) { var hostInfo = App.HostInfo.create({ - name: _hostInfo.name, - bootStatus: _hostInfo.bootStatus, + name: hostsInfo[index].name, + bootStatus: hostsInfo[index].bootStatus, isChecked: false }); console.log('pushing ' + hostInfo.name); - self.hosts.pushObject(hostInfo); - }); + hosts.pushObject(hostInfo); + } + + if(hosts.length > 100) { + lazyloading.run({ + destination: this.get('hosts'), + source: hosts, + context: this, + initSize: 20, + chunkSize: 100, + delay: 300 + }); + } else { + this.set('hosts', hosts); + this.set('isLoaded', true); + } }, /** http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/5c767210/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 2ee3187..c1ab556 100644 --- a/ambari-web/app/controllers/wizard/step5_controller.js +++ b/ambari-web/app/controllers/wizard/step5_controller.js @@ -56,6 +56,7 @@ App.WizardStep5Controller = Em.Controller.extend({ }.property('[email protected]'), hosts:[], + isLazyLoading: false, servicesMasters:[], selectedServicesMasters:[], @@ -220,7 +221,10 @@ App.WizardStep5Controller = Em.Controller.extend({ renderComponents:function (masterComponents) { var services = this.get('content.services') .filterProperty('isInstalled', true).mapProperty('serviceName'); //list of shown services - + var hosts = this.get('hosts'); + //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 && hosts.length > 100; var showRemoveControlZk = !services.contains('ZOOKEEPER') && masterComponents.filterProperty('display_name', 'ZooKeeper').length > 1; var showRemoveControlHb = !services.contains('HBASE') && masterComponents.filterProperty('component_name', 'HBASE_MASTER').length > 1; var zid = 1; @@ -228,6 +232,8 @@ App.WizardStep5Controller = Em.Controller.extend({ var nid = 1; var result = []; + this.set('isLazyLoading', isLazyLoading); + masterComponents.forEach(function (item) { var componentObj = Ember.Object.create(item); @@ -242,7 +248,16 @@ App.WizardStep5Controller = Em.Controller.extend({ } else if (item.component_name === "NAMENODE") { componentObj.set('zId', nid++); } - componentObj.set("availableHosts", this.get("hosts")); + if(isLazyLoading){ + //select need at least 30 hosts to have scrollbar + var initialHosts = hosts.slice(0, 30); + if(!initialHosts.someProperty('host_name', item.selectedHost)){ + initialHosts.push(hosts.findProperty('host_name', item.selectedHost)); + } + componentObj.set("availableHosts", initialHosts); + } else { + componentObj.set("availableHosts", hosts); + } result.push(componentObj); }, this); http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/5c767210/ambari-web/app/controllers/wizard/step6_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step6_controller.js b/ambari-web/app/controllers/wizard/step6_controller.js index 5e0a411..4917cee 100644 --- a/ambari-web/app/controllers/wizard/step6_controller.js +++ b/ambari-web/app/controllers/wizard/step6_controller.js @@ -18,6 +18,7 @@ var App = require('app'); var db = require('utils/db'); +var lazyloading = require('utils/lazy_loading'); /** * By Step 6, we have the following information stored in App.db and set on this @@ -42,6 +43,7 @@ App.WizardStep6Controller = Em.Controller.extend({ * false - slaves and clients */ isMasters: false, + isLoaded: false, components: require('data/service_components'), @@ -111,6 +113,7 @@ App.WizardStep6Controller = Em.Controller.extend({ this.set('hosts', []); this.set('headers', []); this.clearError(); + this.set('isLoaded', false); }, /** @@ -273,7 +276,7 @@ App.WizardStep6Controller = Em.Controller.extend({ * Load all data needed for this module. Then it automatically renders in template */ render: function () { - var hostsObj = Em.Set.create(); + var hostsObj = []; var allHosts = this.getHostNames(); var self = this; @@ -303,9 +306,20 @@ App.WizardStep6Controller = Em.Controller.extend({ hostsObj = this.renderSlaves(hostsObj); } - hostsObj.forEach(function (host) { - this.get('hosts').pushObject(host); - }, this); + if(hostsObj.length > 100) { + lazyloading.run({ + destination: this.get('hosts'), + source: hostsObj, + context: this, + initSize: 20, + chunkSize: 50, + delay: 300 + }); + } else { + hostsObj.forEach(function (host) { + this.get('hosts').pushObject(host); + }, this); + } this.get('headers').forEach(function (header) { self.checkCallback(header.get('label')); }); @@ -474,4 +488,4 @@ App.WizardStep6Controller = Em.Controller.extend({ return !isError; } -}); \ No newline at end of file +}); http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/5c767210/ambari-web/app/routes/installer.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/routes/installer.js b/ambari-web/app/routes/installer.js index c90c82e..3cab49c 100644 --- a/ambari-web/app/routes/installer.js +++ b/ambari-web/app/routes/installer.js @@ -136,8 +136,13 @@ module.exports = Em.Route.extend({ next: function (router) { var wizardStep1Controller = router.get('wizardStep1Controller'); var installerController = router.get('installerController'); - installerController.checkRepoURL(wizardStep1Controller); - // make sure got all validations feedback and no invalid url, then proceed + if (App.testMode) { + installerController.set('validationCnt', 0); + installerController.set('invalidCnt', 0); + } else { + installerController.checkRepoURL(wizardStep1Controller); + } + // make sure got all validations feedback and no invalid url, then proceed var myVar = setInterval( function(){ var cnt = installerController.get('validationCnt'); @@ -240,7 +245,7 @@ module.exports = Em.Route.extend({ router.setNavigationFlow('step5'); var controller = router.get('installerController'); - var wizardStep5Controller = router.get('wizardStep5Controller'); + router.get('wizardStep5Controller').set('servicesMasters', []); controller.setCurrentStep('5'); controller.loadAllPriorSteps(); controller.connectOutlet('wizardStep5', controller.get('content')); @@ -261,6 +266,7 @@ module.exports = Em.Route.extend({ router.setNavigationFlow('step6'); var controller = router.get('installerController'); + router.get('wizardStep6Controller').set('hosts', []); controller.setCurrentStep('6'); controller.loadAllPriorSteps(); controller.connectOutlet('wizardStep6', controller.get('content')); http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/5c767210/ambari-web/app/utils/ajax.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/ajax.js b/ambari-web/app/utils/ajax.js index 99c425a..a0cad02 100644 --- a/ambari-web/app/utils/ajax.js +++ b/ambari-web/app/utils/ajax.js @@ -840,7 +840,6 @@ var urls = { 'type': 'PUT', 'format': function (data) { return { - type: 'PUT', async: true, data: JSON.stringify(data.data) } http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/5c767210/ambari-web/app/utils/lazy_loading.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/lazy_loading.js b/ambari-web/app/utils/lazy_loading.js new file mode 100644 index 0000000..3a439ba --- /dev/null +++ b/ambari-web/app/utils/lazy_loading.js @@ -0,0 +1,98 @@ +/** + * 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. + */ + +module.exports = { + /** + * Divide source array in chunks, then push each chunk into destination array + * with delay time one by one. So then destination array gets more and more items + * till all items are loaded. + * @param options + * options.initSize - number of items which will be pushed in array immediately + * options.chunkSize - number of items which will be pushed after defined delay + * options.delay - interval between each chunk push + * options.destination - array where items will be pushed + * options.source - source of items + * options.context - the object that should know when data is completely loaded, + * lazy loading will define "isLoaded" property for context object and update it + */ + run: function (options) { + var initSize = options.initSize || 25, + chunkSize = options.chunkSize || 50, + delay = options.delay || 300, + destination = options.destination, + source = options.source, + context = options.context, + chunks; + if (Array.isArray(destination) && Array.isArray(source)) { + destination.pushObjects(source.slice(0, initSize)); + if(source.length > initSize) { + chunks = this.divideIntoChunks(source.slice(initSize, source.length), chunkSize); + this.pushChunk(chunks, 0, delay, destination, context); + } else { + context.set('isLoaded', true); + } + } else { + console.error('Lazy loading: source or destination has incorrect value'); + } + }, + + /** + * push chunks into destination array in delay time + * @param chunks + * @param index + * @param delay + * @param destination + * @param context + */ + pushChunk: function (chunks, index, delay, destination, context) { + var self = this; + setTimeout(function () { + destination.pushObjects(chunks[index]); + if (chunks.length === (index + 1)) { + context.set('isLoaded', true); + } + index++; + self.pushChunk(chunks, index, delay, destination, context); + }, delay); + }, + + /** + * divide source array into chunks + * @param source + * @param chunkSize + * @return {Array} + */ + divideIntoChunks: function (source, chunkSize) { + var chunk = []; + var chunks = []; + var counter = 0; + source.forEach(function (item) { + counter++; + chunk.push(item); + if (counter === chunkSize) { + chunks.push(chunk); + chunk = []; + counter = 0; + } + }); + if (chunk.length > 0) { + chunks.push(chunk); + } + return chunks; + } +}; http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/5c767210/ambari-web/app/views/wizard/step3_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/wizard/step3_view.js b/ambari-web/app/views/wizard/step3_view.js index cfa6749..f603059 100644 --- a/ambari-web/app/views/wizard/step3_view.js +++ b/ambari-web/app/views/wizard/step3_view.js @@ -25,7 +25,7 @@ App.WizardStep3View = Em.View.extend({ category: '', didInsertElement: function () { - this.get('controller').navigateStep(); + this.get('controller').loadStep(); }, message:'', http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/5c767210/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 a9419b6..d735b8a 100644 --- a/ambari-web/app/views/wizard/step5_view.js +++ b/ambari-web/app/views/wizard/step5_view.js @@ -18,6 +18,7 @@ var App = require('app'); +var lazyloading = require('utils/lazy_loading'); App.WizardStep5View = Em.View.extend({ @@ -35,10 +36,30 @@ App.SelectHostView = Em.Select.extend({ selectedHost:null, componentName:null, attributeBindings:['disabled'], + isLoaded: false, change:function () { this.get('controller').assignHostToMaster(this.get("componentName"), this.get("value"), this.get("zId")); }, + click: function () { + var source = []; + var selectedHost = this.get('selectedHost'); + var content = this.get('content'); + if (!this.get('isLoaded') && this.get('controller.isLazyLoading')) { + //filter out hosts, which already pushed in select + source = this.get('controller.hosts').filter(function(_host){ + return !content.someProperty('host_name', _host.host_name); + }, this); + lazyloading.run({ + destination: this.get('content'), + source: source, + context: this, + initSize: 30, + chunkSize: 50, + delay: 200 + }); + } + }, didInsertElement:function () { this.set("value", this.get("selectedHost")); http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/5c767210/ambari-web/test/utils/lazy_loading_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/utils/lazy_loading_test.js b/ambari-web/test/utils/lazy_loading_test.js new file mode 100644 index 0000000..f5120ee --- /dev/null +++ b/ambari-web/test/utils/lazy_loading_test.js @@ -0,0 +1,110 @@ +/** + * 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 lazyLoading = require('utils/lazy_loading'); + +describe('lazy_loading', function () { + + describe('#run', function () { + var context = Em.Object.create({isLoaded: false}); + var options = { + destination: [], + source: [{'test':'test'}], + context: context + }; + it('load one item', function () { + lazyLoading.run(options); + expect(options.destination[0]).to.eql(options.source[0]); + expect(context.get('isLoaded')).to.equal(true); + }); + + var testsInfo = [ + { + title: 'load 11 item with initSize - 11', + result: true, + initSize: 11, + destinationLength: 11, + destination: [], + source: [{i:1}, {i:2}, {i:3}, {i:4}, {i:5}, {i:6}, {i:7}, {i:8}, {i:9}, {i:10},{i:11}], + context: Em.Object.create() + }, + { + title: 'load 11 item with initSize - 12', + result: true, + initSize: 12, + destinationLength: 11, + destination: [], + source: [{i:1}, {i:2}, {i:3}, {i:4}, {i:5}, {i:6}, {i:7}, {i:8}, {i:9}, {i:10},{i:11}], + context: Em.Object.create() + }, + {//items will be completely loaded on next iteration of pushing chunk + title: 'load 11 item with initSize - 10', + result: false, + initSize: 10, + destinationLength: 10, + destination: [], + source: [{i:1}, {i:2}, {i:3}, {i:4}, {i:5}, {i:6}, {i:7}, {i:8}, {i:9}, {i:10},{i:11}], + context: Em.Object.create({isLoaded: false}) + } + ]; + testsInfo.forEach(function(test){ + it(test.title, function () { + lazyLoading.run(test); + expect(test.destinationLength).to.equal(test.destination.length); + expect(test.context.get('isLoaded')).to.equal(test.result); + }); + }); + }); + + describe('#divideIntoChunks', function () { + var testsInfo = [ + { + title: 'load 11 item with chunkSize - 3', + chunkSize: 3, + source: [{i:1}, {i:2}, {i:3}, {i:4}, {i:5}, {i:6}, {i:7}, {i:8}, {i:9}, {i:10},{i:11}], + chunks: [[{i:1}, {i:2}, {i:3}], [{i:4}, {i:5}, {i:6}], [{i:7}, {i:8}, {i:9}], [{i:10},{i:11}]] + }, + { + title: 'load 11 item with chunkSize - 0', + chunkSize: 0, + source: [{i:1}, {i:2}, {i:3}, {i:4}, {i:5}, {i:6}, {i:7}, {i:8}, {i:9}, {i:10},{i:11}], + chunks: [[{i:1}, {i:2}, {i:3}, {i:4}, {i:5}, {i:6}, {i:7}, {i:8}, {i:9}, {i:10},{i:11}]] + }, + { + title: 'load 11 item with chunkSize - 1', + chunkSize: 1, + source: [{i:1}, {i:2}, {i:3}, {i:4}, {i:5}, {i:6}, {i:7}, {i:8}, {i:9}, {i:10},{i:11}], + chunks: [[{i:1}], [{i:2}], [{i:3}], [{i:4}], [{i:5}], [{i:6}], [{i:7}], [{i:8}], [{i:9}], [{i:10}], [{i:11}]] + }, + { + title: 'load 11 item with chunkSize - 11', + chunkSize: 0, + source: [{i:1}, {i:2}, {i:3}, {i:4}, {i:5}, {i:6}, {i:7}, {i:8}, {i:9}, {i:10},{i:11}], + chunks: [[{i:1}, {i:2}, {i:3}, {i:4}, {i:5}, {i:6}, {i:7}, {i:8}, {i:9}, {i:10},{i:11}]] + } + ]; + testsInfo.forEach(function(test){ + it(test.title, function () { + var chunks = lazyLoading.divideIntoChunks(test.source, test.chunkSize); + expect(chunks).to.eql(test.chunks); + }); + }); + }); + + +});
