Repository: ambari Updated Branches: refs/heads/trunk 0055b86bf -> c06fe29f3
AMBARI-10345. Refactor config groups managing (onechiporenko) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/c06fe29f Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/c06fe29f Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/c06fe29f Branch: refs/heads/trunk Commit: c06fe29f325ac90df0abc4cd655f9ec76e94b831 Parents: 0055b86 Author: Oleg Nechiporenko <[email protected]> Authored: Fri Apr 3 17:03:40 2015 +0300 Committer: Oleg Nechiporenko <[email protected]> Committed: Fri Apr 3 17:03:40 2015 +0300 ---------------------------------------------------------------------- .../controllers/main/service/info/configs.js | 140 +---- .../service/manage_config_groups_controller.js | 529 +++++++++++++++---- .../app/controllers/wizard/step7_controller.js | 2 +- .../app/controllers/wizard/step8_controller.js | 2 +- .../main/service/configs/config_overridable.js | 24 +- ambari-web/app/utils/hosts.js | 3 - .../configs/service_config_layout_tab_view.js | 1 + .../main/service/manage_config_groups_view.js | 137 +++-- .../main/service/info/config_test.js | 13 - .../manage_config_groups_controller_test.js | 60 +++ .../test/controllers/wizard/step8_test.js | 6 +- 11 files changed, 585 insertions(+), 332 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/app/controllers/main/service/info/configs.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service/info/configs.js b/ambari-web/app/controllers/main/service/info/configs.js index 9a40545..eae6a1b 100644 --- a/ambari-web/app/controllers/main/service/info/configs.js +++ b/ambari-web/app/controllers/main/service/info/configs.js @@ -22,7 +22,7 @@ var batchUtils = require('utils/batch_scheduled_requests'); var dataManipulationUtils = require('utils/data_manipulation'); var lazyLoading = require('utils/lazy_loading'); -App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorMixin, App.EnhancedConfigsMixin, App.ConfigOverridable, App.PreloadRequestsChainMixin, App.ThemesMappingMixin, App.VersionsMappingMixin, { +App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorMixin, App.EnhancedConfigsMixin, App.PreloadRequestsChainMixin, App.ThemesMappingMixin, App.VersionsMappingMixin, { name: 'mainServiceInfoConfigsController', @@ -50,6 +50,7 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorM /** * config groups for current service + * @type {App.ConfigGroup[]} */ configGroups: [], @@ -57,6 +58,10 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorM uiConfigs: [], + /** + * Determines if save configs is in progress + * @type {boolean} + */ saveInProgress: false, saveConfigsFlag: true, @@ -2483,138 +2488,7 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorM * @method manageConfigurationGroup */ manageConfigurationGroup: function () { - this.manageConfigurationGroups(); - }, - - /** - * Show popup with config groups - * User may edit/create/delete them - * @param controller - * @returns {App.ModalPopup} - * @method manageConfigurationGroups - */ - manageConfigurationGroups: function (controller) { - var configsController = this; - var serviceData = (controller && controller.get('selectedService')) || this.get('content'); - var serviceName = serviceData.get('serviceName'); - var displayName = serviceData.get('displayName'); - App.router.get('manageConfigGroupsController').set('isInstaller', !!controller); - App.router.get('manageConfigGroupsController').set('serviceName', serviceName); - if (controller) { - App.router.get('manageConfigGroupsController').set('isAddService', controller.get('content.controllerName') == 'addServiceController'); - } - return App.ModalPopup.show({ - header: Em.I18n.t('services.service.config_groups_popup.header').format(displayName), - bodyClass: App.MainServiceManageConfigGroupView.extend({ - serviceName: serviceName, - displayName: displayName, - controllerBinding: 'App.router.manageConfigGroupsController' - }), - classNames: ['sixty-percent-width-modal', 'manage-configuration-group-popup'], - primary: Em.I18n.t('common.save'), - onPrimary: function () { - var modifiedConfigGroups = this.get('subViewController.hostsModifiedConfigGroups'); - // Save modified config-groups - if (!!controller) { - controller.set('selectedService.configGroups', App.router.get('manageConfigGroupsController.configGroups')); - controller.selectedServiceObserver(); - if (controller.get('name') == "wizardStep7Controller") { - if (controller.get('selectedService.selected') === false && modifiedConfigGroups.toDelete.length > 0) { - controller.setGroupsToDelete(modifiedConfigGroups.toDelete); - } - configsController.persistConfigGroups(); - this.updateConfigGroupOnServicePage(); - } - this.hide(); - return; - } - console.log("manageConfigurationGroups(): Saving modified config-groups: ", modifiedConfigGroups); - var self = this; - var errors = []; - var deleteQueriesCounter = modifiedConfigGroups.toClearHosts.length + modifiedConfigGroups.toDelete.length; - var createQueriesCounter = modifiedConfigGroups.toSetHosts.length + modifiedConfigGroups.toCreate.length; - var deleteQueriesRun = false; - var createQueriesRun = false; - var runNextQuery = function () { - if (!deleteQueriesRun && deleteQueriesCounter > 0) { - deleteQueriesRun = true; - modifiedConfigGroups.toClearHosts.forEach(function (cg) { - configsController.clearConfigurationGroupHosts(cg, finishFunction, finishFunction); - }, this); - modifiedConfigGroups.toDelete.forEach(function (cg) { - configsController.deleteConfigGroup(cg, finishFunction, finishFunction); - }, this); - } else if (!createQueriesRun && deleteQueriesCounter < 1) { - createQueriesRun = true; - modifiedConfigGroups.toSetHosts.forEach(function (cg) { - configsController.updateConfigurationGroup(cg, finishFunction, finishFunction); - }, this); - modifiedConfigGroups.toCreate.forEach(function (cg) { - configsController.postNewConfigurationGroup(cg, finishFunction); - }, this); - } - }; - var finishFunction = function (xhr, text, errorThrown) { - if (xhr && errorThrown) { - var error = xhr.status + "(" + errorThrown + ") "; - try { - var json = $.parseJSON(xhr.responseText); - error += json.message; - } catch (err) { - } - console.error('Error updating Config Group:', error); - errors.push(error); - } - if (createQueriesRun) { - createQueriesCounter--; - } else { - deleteQueriesCounter--; - } - if (deleteQueriesCounter + createQueriesCounter < 1) { - if (errors.length > 0) { - console.log(errors); - self.get('subViewController').set('errorMessage', errors.join(". ")); - } else { - self.updateConfigGroupOnServicePage(); - self.hide(); - } - } else { - runNextQuery(); - } - }; - runNextQuery(); - }, - subViewController: function () { - return App.router.get('manageConfigGroupsController'); - }.property('App.router.manageConfigGroupsController'), - - updateConfigGroupOnServicePage: function () { - var subViewController = this.get('subViewController'); - var selectedConfigGroup = subViewController.get('selectedConfigGroup'); - var managedConfigGroups = subViewController.get('configGroups'); - if (!controller) { - controller = App.router.get('mainServiceInfoConfigsController'); - controller.set('configGroups', managedConfigGroups); - } else { - controller.set('selectedService.configGroups', managedConfigGroups); - } - - var selectEventObject = {}; - //check whether selectedConfigGroup exists - if (selectedConfigGroup && controller.get('configGroups').someProperty('name', selectedConfigGroup.get('name'))) { - selectEventObject.context = selectedConfigGroup; - } else { - selectEventObject.context = managedConfigGroups.findProperty('isDefault', true); - } - controller.selectConfigGroup(selectEventObject); - }, - - updateButtons: function () { - var modified = this.get('subViewController.isHostsModified'); - this.set('disablePrimary', !modified); - }.observes('subViewController.isHostsModified'), - didInsertElement: Em.K - }); + App.router.get('manageConfigGroupsController').manageConfigurationGroups(null, this.get('content')); }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/app/controllers/main/service/manage_config_groups_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service/manage_config_groups_controller.js b/ambari-web/app/controllers/main/service/manage_config_groups_controller.js index 6842323..826a05e 100644 --- a/ambari-web/app/controllers/main/service/manage_config_groups_controller.js +++ b/ambari-web/app/controllers/main/service/manage_config_groups_controller.js @@ -22,32 +22,179 @@ var validator = require('utils/validator'); var hostsManagement = require('utils/hosts'); var numberUtils = require('utils/number_utils'); -App.ManageConfigGroupsController = Em.Controller.extend({ +App.ManageConfigGroupsController = Em.Controller.extend(App.ConfigOverridable, { + name: 'manageConfigGroupsController', + /** + * Determines if needed data is already loaded + * Loading chain starts at <code>loadHosts</code> and is complete on the <code>loadConfigGroups</code> (if user on + * the Installer) or on the <code>_onLoadConfigGroupsSuccess</code> (otherwise) + * @type {boolean} + */ isLoaded: false, + /** + * Determines if user currently is on the Cluster Installer + * @type {boolean} + */ isInstaller: false, + /** + * Determines if user currently is on the Add Service Wizard + * @type {boolean} + */ isAddService: false, + /** + * Current service name + * @type {string} + */ serviceName: null, + /** + * @type {App.ConfigGroup[]} + */ configGroups: [], + /** + * @type {App.ConfigGroup[]} + */ originalConfigGroups: [], + /** + * @type {App.ConfigGroup} + */ selectedConfigGroup: null, + /** + * @type {string[]} + */ selectedHosts: [], + /** + * List of all hosts in the cluster + * @type {{ + * id: string, + * ip: string, + * osType: string, + * osArch: string, + * hostName: string, + * publicHostName: string, + * cpu: number, + * memory: number, + * diskTotal: string, + * diskFree: string, + * disksMounted: number, + * hostComponents: { + * componentName: string, + * displayName: string + * }[] + * }[]} + */ clusterHosts: [], - resortConfigGroup: function() { - var configGroups = Ember.copy(this.get('configGroups')); - if(configGroups.length < 2){ - return; + /** + * List of available service components for <code>serviceName</code> + * @type {{componentName: string, displayName: string, selected: boolean}[]} + */ + componentsForFilter: function () { + return App.StackServiceComponent.find().filterProperty('serviceName', this.get('serviceName')).map(function (component) { + return Em.Object.create({ + componentName: component.get('componentName'), + displayName: App.format.role(component.get('componentName')), + selected: false + }); + }); + }.property('serviceName'), + + /** + * Determines when host may be deleted from config group + * @type {boolean} + */ + isDeleteHostsDisabled: function () { + var selectedConfigGroup = this.get('selectedConfigGroup'); + if (selectedConfigGroup) { + return selectedConfigGroup.isDefault || this.get('selectedHosts').length === 0; } + return true; + }.property('selectedConfigGroup', 'selectedConfigGroup.hosts.length', 'selectedHosts.length'), + + /** + * Map with modified/deleted/created config groups + * @type {{ + * toClearHosts: App.ConfigGroup[], + * toDelete: App.ConfigGroup[], + * toSetHosts: App.ConfigGroup[], + * toCreate: App.ConfigGroup[] + * }} + */ + hostsModifiedConfigGroups: function () { + if (!this.get('isLoaded')) { + return false; + } + var groupsToClearHosts = []; + var groupsToDelete = []; + var groupsToSetHosts = []; + var groupsToCreate = []; + var groups = this.get('configGroups'); + var originalGroups = this.get('originalConfigGroups'); + // remove default group + var originalGroupsCopy = originalGroups.without(originalGroups.findProperty('isDefault')); + var originalGroupsIds = originalGroupsCopy.mapProperty('id'); + groups.forEach(function (group) { + if (!group.get('isDefault')) { + var originalGroup = originalGroupsCopy.findProperty('id', group.get('id')); + if (originalGroup) { + if (!(JSON.stringify(group.get('hosts').slice().sort()) === JSON.stringify(originalGroup.get('hosts').sort()))) { + groupsToClearHosts.push(group.set('id', originalGroup.get('id'))); + if (group.get('hosts').length) { + groupsToSetHosts.push(group.set('id', originalGroup.get('id'))); + } + // should update name or description + } else if (group.get('description') !== originalGroup.get('description') || group.get('name') !== originalGroup.get('name') ) { + groupsToSetHosts.push(group.set('id', originalGroup.get('id'))); + } + originalGroupsIds = originalGroupsIds.without(group.get('id')); + } else { + groupsToCreate.push(group); + } + } + }); + originalGroupsIds.forEach(function (id) { + groupsToDelete.push(originalGroupsCopy.findProperty('id', id)); + }, this); + return { + toClearHosts: groupsToClearHosts, + toDelete: groupsToDelete, + toSetHosts: groupsToSetHosts, + toCreate: groupsToCreate + }; + }.property('selectedConfigGroup.hosts.@each', 'selectedConfigGroup.hosts.length', 'selectedConfigGroup.description', 'configGroups', 'isLoaded'), + + /** + * Determines if some changes were done with config groups + * @use hostsModifiedConfigGroups + * @type {boolean} + */ + isHostsModified: function () { + if (!this.get('isLoaded')) { + return false; + } + var modifiedGroups = this.get('hostsModifiedConfigGroups'); + return Em.keys(modifiedGroups).map(function (key) { + return Em.get(modifiedGroups[key], 'length'); + }).reduce(Em.sum) > 0; + }.property('hostsModifiedConfigGroups'), + + /** + * Resort config groups according to order: + * default group first, other - last + * @method resortConfigGroup + */ + resortConfigGroup: function() { + var configGroups = Em.copy(this.get('configGroups')); + if(configGroups.length < 2) return; var defaultConfigGroup = configGroups.findProperty('isDefault'); configGroups.removeObject(defaultConfigGroup); var sorted = [defaultConfigGroup].concat(configGroups.sortProperty('name')); @@ -57,35 +204,47 @@ App.ManageConfigGroupsController = Em.Controller.extend({ this.addObserver('[email protected]', this, 'resortConfigGroup'); }.observes('[email protected]'), + /** + * Load hosts from server or + * get them from installerController if user on the install wizard + * get them from isAddServiceController if user on the add service wizard + * @method loadHosts + */ loadHosts: function() { this.set('isLoaded', false); if (this.get('isInstaller')) { var allHosts = this.get('isAddService') ? App.router.get('addServiceController').get('allHosts') : App.router.get('installerController').get('allHosts'); this.set('clusterHosts', allHosts); this.loadConfigGroups(this.get('serviceName')); - } else { + } + else { this.loadHostsFromServer(); } }, /** - * request all hosts directly from server + * Request all hosts directly from server + * @method loadHostsFromServer + * @return {$.ajax} */ loadHostsFromServer: function() { - App.ajax.send({ + return App.ajax.send({ name: 'hosts.config_groups', sender: this, data: {}, - success: 'loadHostsFromServerSuccessCallback', - error: 'loadHostsFromServerErrorCallback' + success: '_loadHostsFromServerSuccessCallback', + error: '_loadHostsFromServerErrorCallback' }); }, /** - * parse hosts response and wrap them into Ember.Object - * @param data + * Success-callback for <code>loadHostsFromServer</code> + * Parse hosts response and wrap them into Ember.Object + * @param {object} data + * @method _loadHostsFromServerSuccessCallback + * @private */ - loadHostsFromServerSuccessCallback: function (data) { + _loadHostsFromServerSuccessCallback: function (data) { var wrappedHosts = []; data.items.forEach(function (host) { @@ -128,21 +287,35 @@ App.ManageConfigGroupsController = Em.Controller.extend({ this.loadConfigGroups(this.get('serviceName')); }, - loadHostsFromServerErrorCallback: function () { + /** + * Error-callback for <code>loadHostsFromServer</code> + * @method _loadHostsFromServerErrorCallback + * @private + */ + _loadHostsFromServerErrorCallback: function () { console.warn('ERROR: request to fetch all hosts failed'); this.set('clusterHosts', []); this.loadConfigGroups(this.get('serviceName')); }, + /** + * Load config groups from server if user is on the already installed cluster + * If not - use loaded data form wizardStep7Controller + * @param {string} serviceName + * @method loadConfigGroups + */ loadConfigGroups: function (serviceName) { if (this.get('isInstaller')) { this.set('serviceName', serviceName); var configGroups = this.copyConfigGroups(App.router.get('wizardStep7Controller.selectedService.configGroups')); var originalConfigGroups = this.copyConfigGroups(configGroups); - this.set('configGroups', configGroups); - this.set('originalConfigGroups', originalConfigGroups); - this.set('isLoaded', true); - } else { + this.setProperties({ + configGroups: configGroups, + originalConfigGroups: originalConfigGroups, + isLoaded: true + }); + } + else { this.set('serviceName', serviceName); App.ajax.send({ name: 'service.load_config_groups', @@ -150,13 +323,18 @@ App.ManageConfigGroupsController = Em.Controller.extend({ serviceName: serviceName }, sender: this, - success: 'onLoadConfigGroupsSuccess', - error: 'onLoadConfigGroupsError' + success: '_onLoadConfigGroupsSuccess' }); } }, - onLoadConfigGroupsSuccess: function (data) { + /** + * Success-callback for <code>loadConfigGroups</code> + * @param {object} data + * @private + * @method _onLoadConfigGroupsSuccess + */ + _onLoadConfigGroupsSuccess: function (data) { var usedHosts = []; var unusedHosts = []; var serviceName = this.get('serviceName'); @@ -218,10 +396,11 @@ App.ManageConfigGroupsController = Em.Controller.extend({ } }, - onLoadConfigGroupsError: function () { - console.error('Unable to load config groups for service.'); - }, - + /** + * + * @param {object} groupToTypeToTagMap + * @method loadProperties + */ loadProperties: function (groupToTypeToTagMap) { var typeTagToGroupMap = {}; var urlParams = []; @@ -242,12 +421,20 @@ App.ManageConfigGroupsController = Em.Controller.extend({ params: params, typeTagToGroupMap: typeTagToGroupMap }, - success: 'onLoadPropertiesSuccess' + success: '_onLoadPropertiesSuccess' }); } }, - onLoadPropertiesSuccess: function (data, opt, params) { + /** + * Success-callback for <code>loadProperties</code> + * @param {object} data + * @param {object} opt + * @param {object} params + * @private + * @method _onLoadPropertiesSuccess + */ + _onLoadPropertiesSuccess: function (data, opt, params) { data.items.forEach(function (configs) { var typeTagConfigs = []; var group = params.typeTagToGroupMap[configs.type + "///" + configs.tag]; @@ -261,14 +448,24 @@ App.ManageConfigGroupsController = Em.Controller.extend({ }, this); }, + /** + * Show popup with properties overridden in the selected config group + * @method showProperties + */ showProperties: function () { var properies = this.get('selectedConfigGroup.propertiesList').htmlSafe(); if (properies) { App.showAlertPopup(Em.I18n.t('services.service.config_groups_popup.properties'), properies); } }, + + /** + * Show popup with hosts to add to the selected config group + * @returns {boolean} + * @method addHosts + */ addHosts: function () { - if (this.get('selectedConfigGroup.isAddHostsDisabled')){ + if (this.get('selectedConfigGroup.isAddHostsDisabled')) { return false; } var availableHosts = this.get('selectedConfigGroup.availableHosts'); @@ -285,12 +482,12 @@ App.ManageConfigGroupsController = Em.Controller.extend({ * @method addHostsCallback */ addHostsCallback: function (selectedHosts) { - var group = this.get('selectedConfigGroup'); if (selectedHosts) { + var group = this.get('selectedConfigGroup'); selectedHosts.forEach(function (hostName) { group.get('hosts').pushObject(hostName); group.get('parentConfigGroup.hosts').removeObject(hostName); - }, this); + }); } }, @@ -309,39 +506,21 @@ App.ManageConfigGroupsController = Em.Controller.extend({ this.set('selectedHosts', []); }, - isDeleteHostsDisabled: function () { - var selectedConfigGroup = this.get('selectedConfigGroup'); - if (selectedConfigGroup) { - return selectedConfigGroup.isDefault || this.get('selectedHosts').length === 0; - } - return true; - }.property('selectedConfigGroup', 'selectedConfigGroup.hosts.length', 'selectedHosts.length'), - /** - * confirm delete config group + * show popup for confirmation delete config group + * @method confirmDelete */ - confirmDelete : function () { + confirmDelete: function () { var self = this; App.showConfirmationPopup(function() { self.deleteConfigGroup(); }); }, - /** - * add hosts to group - * @return {Array} - */ - componentsForFilter: function () { - return App.StackServiceComponent.find().filterProperty('serviceName', this.get('serviceName')).map(function (component) { - return Em.Object.create({ - componentName: component.get('componentName'), - displayName: App.format.role(component.get('componentName')), - selected: false - }); - }); - }.property('serviceName'), /** - * delete selected config group + * delete selected config group (stored in the <code>selectedConfigGroup</code>) + * then select default config group + * @method deleteConfigGroup */ deleteConfigGroup: function () { var selectedConfigGroup = this.get('selectedConfigGroup'); @@ -354,25 +533,31 @@ App.ManageConfigGroupsController = Em.Controller.extend({ this.get('configGroups').removeObject(selectedConfigGroup); this.set('selectedConfigGroup', this.get('configGroups').findProperty('isDefault')); }, + /** - * rename new config group + * rename new config group (not allowed for default group) + * @method renameConfigGroup */ renameConfigGroup: function () { if(this.get('selectedConfigGroup.isDefault')) { return; } var self = this; - this.renameGroupPopup = App.ModalPopup.show({ - primary: Em.I18n.t('ok'), - secondary: Em.I18n.t('common.cancel'), + var renameGroupPopup = App.ModalPopup.show({ header: Em.I18n.t('services.service.config_groups.rename_config_group_popup.header'), - bodyClass: Ember.View.extend({ + + bodyClass: Em.View.extend({ templateName: require('templates/main/service/new_config_group') }), + configGroupName: self.get('selectedConfigGroup.name'), + configGroupDesc: self.get('selectedConfigGroup.description'), + warningMessage: null, + isDescriptionDirty: false, + validate: function () { var warningMessage = ''; var originalGroup = self.get('selectedConfigGroup'); @@ -396,9 +581,11 @@ App.ManageConfigGroupsController = Em.Controller.extend({ } this.set('warningMessage', warningMessage); }.observes('configGroupName', 'configGroupDesc'), + disablePrimary: function () { return !(this.get('configGroupName').trim().length > 0 && (this.get('warningMessage') !== null && !this.get('warningMessage'))); }.property('warningMessage', 'configGroupName', 'configGroupDesc'), + onPrimary: function () { self.set('selectedConfigGroup.name', this.get('configGroupName')); self.set('selectedConfigGroup.description', this.get('configGroupDesc')); @@ -408,28 +595,37 @@ App.ManageConfigGroupsController = Em.Controller.extend({ this.hide(); } }); + this.set('renameGroupPopup', renameGroupPopup); }, /** - * add new config group + * add new config group (or copy existing) + * @param {boolean} duplicated true - copy <code>selectedConfigGroup</code>, false - create a new one + * @method addConfigGroup */ addConfigGroup: function (duplicated) { duplicated = (duplicated === true); + var self = this; - this.addGroupPopup = App.ModalPopup.show({ - primary: Em.I18n.t('ok'), - secondary: Em.I18n.t('common.cancel'), + + var addGroupPopup = App.ModalPopup.show({ header: Em.I18n.t('services.service.config_groups.add_config_group_popup.header'), - bodyClass: Ember.View.extend({ + + bodyClass: Em.View.extend({ templateName: require('templates/main/service/new_config_group') }), + configGroupName: duplicated ? self.get('selectedConfigGroup.name') + ' Copy' : "", + configGroupDesc: duplicated ? self.get('selectedConfigGroup.description') + ' (Copy)' : "", + warningMessage: '', + didInsertElement: function(){ this.validate(); this.$('input').focus(); }, + validate: function () { var warningMessage = ''; var groupName = this.get('configGroupName').trim(); @@ -441,9 +637,11 @@ App.ManageConfigGroupsController = Em.Controller.extend({ } this.set('warningMessage', warningMessage); }.observes('configGroupName'), + disablePrimary: function () { return !(this.get('configGroupName').trim().length > 0 && !this.get('warningMessage')); }.property('warningMessage', 'configGroupName'), + onPrimary: function () { var defaultConfigGroup = self.get('configGroups').findProperty('isDefault'); var properties = []; @@ -473,67 +671,22 @@ App.ManageConfigGroupsController = Em.Controller.extend({ this.hide(); } }); + this.set('addGroupPopup', addGroupPopup); }, + /** + * Duplicate existing config group + * @method duplicateConfigGroup + */ duplicateConfigGroup: function() { this.addConfigGroup(true); }, - hostsModifiedConfigGroups: function () { - if (!this.get('isLoaded')) { - return false; - } - var groupsToClearHosts = []; - var groupsToDelete = []; - var groupsToSetHosts = []; - var groupsToCreate = []; - var groups = this.get('configGroups'); - var originalGroups = this.get('originalConfigGroups'); - // remove default group - originalGroups = originalGroups.without(originalGroups.findProperty('isDefault')); - var originalGroupsIds = originalGroups.mapProperty('id'); - groups.forEach(function (group) { - if (!group.get('isDefault')) { - var originalGroup = originalGroups.findProperty('id', group.get('id')); - if (originalGroup) { - if (!(JSON.stringify(group.get('hosts').slice().sort()) === JSON.stringify(originalGroup.get('hosts').sort()))) { - groupsToClearHosts.push(group.set('id', originalGroup.get('id'))); - if (group.get('hosts').length) { - groupsToSetHosts.push(group.set('id', originalGroup.get('id'))); - } - // should update name or description - } else if (group.get('description') !== originalGroup.get('description') || group.get('name') !== originalGroup.get('name') ) { - groupsToSetHosts.push(group.set('id', originalGroup.get('id'))); - } - originalGroupsIds = originalGroupsIds.without(group.get('id')); - } else { - groupsToCreate.push(group); - } - } - }); - originalGroupsIds.forEach(function (id) { - groupsToDelete.push(originalGroups.findProperty('id', id)); - }, this); - return { - toClearHosts: groupsToClearHosts, - toDelete: groupsToDelete, - toSetHosts: groupsToSetHosts, - toCreate: groupsToCreate - }; - }.property('selectedConfigGroup.hosts.@each', 'selectedConfigGroup.hosts.length', 'selectedConfigGroup.description', 'configGroups', 'isLoaded'), - - isHostsModified: function () { - var modifiedGroups = this.get('hostsModifiedConfigGroups'); - if (!this.get('isLoaded')) { - return false; - } - return !!(modifiedGroups.toClearHosts.length || modifiedGroups.toSetHosts.length || modifiedGroups.toCreate.length || modifiedGroups.toDelete.length); - }.property('hostsModifiedConfigGroups'), - /** * copy config groups to manage popup to give user choice whether or not save changes * @param originGroups * @return {Array} + * @method copyConfigGroups */ copyConfigGroups: function (originGroups) { var configGroups = []; @@ -561,5 +714,151 @@ App.ManageConfigGroupsController = Em.Controller.extend({ result.push(App.ConfigGroup.create(groupCopy)); }, this); return result; + }, + + /** + * Show popup with config groups + * User may edit/create/delete them + * @param {Em.Controller} controller + * @param {App.Service} service + * @returns {App.ModalPopup} + * @method manageConfigurationGroups + */ + manageConfigurationGroups: function (controller, service) { + var configsController = this; + var serviceData = (controller && controller.get('selectedService')) || service; + var serviceName = serviceData.get('serviceName'); + var displayName = serviceData.get('displayName'); + this.setProperties({ + isInstaller: !!controller, + serviceName: serviceName + }); + if (controller) { + configsController.set('isAddService', controller.get('content.controllerName') == 'addServiceController'); + } + return App.ModalPopup.show({ + + header: Em.I18n.t('services.service.config_groups_popup.header').format(displayName), + + bodyClass: App.MainServiceManageConfigGroupView.extend({ + serviceName: serviceName, + displayName: displayName, + controller: configsController + }), + + classNames: ['sixty-percent-width-modal', 'manage-configuration-group-popup'], + + primary: Em.I18n.t('common.save'), + + subViewController: function () { + return configsController; + }.property(), + + onPrimary: function () { + var modifiedConfigGroups = configsController.get('hostsModifiedConfigGroups'); + // Save modified config-groups + if (!!controller) { + controller.set('selectedService.configGroups', configsController.get('configGroups')); + controller.selectedServiceObserver(); + if (controller.get('name') == "wizardStep7Controller") { + if (controller.get('selectedService.selected') === false && modifiedConfigGroups.toDelete.length > 0) { + controller.setGroupsToDelete(modifiedConfigGroups.toDelete); + } + configsController.persistConfigGroups(); + this.updateConfigGroupOnServicePage(); + } + this.hide(); + return; + } + var self = this; + var errors = []; + var deleteQueriesCounter = modifiedConfigGroups.toClearHosts.length + modifiedConfigGroups.toDelete.length; + var createQueriesCounter = modifiedConfigGroups.toSetHosts.length + modifiedConfigGroups.toCreate.length; + var deleteQueriesRun = false; + var createQueriesRun = false; + var runNextQuery = function () { + if (!deleteQueriesRun && deleteQueriesCounter > 0) { + deleteQueriesRun = true; + modifiedConfigGroups.toClearHosts.forEach(function (cg) { + configsController.clearConfigurationGroupHosts(cg, finishFunction, finishFunction); + }, this); + modifiedConfigGroups.toDelete.forEach(function (cg) { + configsController.deleteConfigurationGroup(cg, finishFunction, finishFunction); + }, this); + } else if (!createQueriesRun && deleteQueriesCounter < 1) { + createQueriesRun = true; + modifiedConfigGroups.toSetHosts.forEach(function (cg) { + configsController.updateConfigurationGroup(cg, finishFunction, finishFunction); + }, this); + modifiedConfigGroups.toCreate.forEach(function (cg) { + configsController.postNewConfigurationGroup(cg, finishFunction); + }, this); + } + }; + var finishFunction = function (xhr, text, errorThrown) { + if (xhr && errorThrown) { + var error = xhr.status + "(" + errorThrown + ") "; + try { + var json = $.parseJSON(xhr.responseText); + error += json.message; + } catch (err) {} + errors.push(error); + } + createQueriesRun ? createQueriesCounter-- : deleteQueriesCounter--; + if (deleteQueriesCounter + createQueriesCounter < 1) { + if (errors.length > 0) { + self.get('subViewController').set('errorMessage', errors.join(". ")); + } else { + self.updateConfigGroupOnServicePage(); + self.hide(); + } + } else { + runNextQuery(); + } + }; + runNextQuery(); + }, + + updateConfigGroupOnServicePage: function () { + var selectedConfigGroup = configsController.get('selectedConfigGroup'); + var managedConfigGroups = configsController.get('configGroups'); + if (!controller) { + controller = App.router.get('mainServiceInfoConfigsController'); + controller.set('configGroups', managedConfigGroups); + } else { + controller.set('selectedService.configGroups', managedConfigGroups); + } + + var selectEventObject = {}; + //check whether selectedConfigGroup exists + if (selectedConfigGroup && controller.get('configGroups').someProperty('name', selectedConfigGroup.get('name'))) { + selectEventObject.context = selectedConfigGroup; + } else { + selectEventObject.context = managedConfigGroups.findProperty('isDefault', true); + } + controller.selectConfigGroup(selectEventObject); + }, + + updateButtons: function () { + var modified = this.get('subViewController.isHostsModified'); + this.set('disablePrimary', !modified); + }.observes('subViewController.isHostsModified'), + + didInsertElement: Em.K + }); + }, + + /** + * Persist config groups created in step7 wizard controller + * @method persistConfigGroups + */ + persistConfigGroups: function () { + var installerController = App.router.get('installerController'); + var step7Controller = App.router.get('wizardStep7Controller'); + installerController.saveServiceConfigGroups(step7Controller, step7Controller.get('content.controllerName') == 'addServiceController'); + App.clusterStatus.setClusterStatus({ + localdb: App.db.data + }); } + }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/app/controllers/wizard/step7_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step7_controller.js b/ambari-web/app/controllers/wizard/step7_controller.js index f8dad20..b5d84dc 100644 --- a/ambari-web/app/controllers/wizard/step7_controller.js +++ b/ambari-web/app/controllers/wizard/step7_controller.js @@ -1094,7 +1094,7 @@ App.WizardStep7Controller = Em.Controller.extend(App.ServerValidatorMixin, App.E * @method manageConfigurationGroup */ manageConfigurationGroup: function () { - App.router.get('mainServiceInfoConfigsController').manageConfigurationGroups(this); + App.router.get('manageConfigGroupsController').manageConfigurationGroups(this); }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/app/controllers/wizard/step8_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step8_controller.js b/ambari-web/app/controllers/wizard/step8_controller.js index e2a858a..9bfebdd 100644 --- a/ambari-web/app/controllers/wizard/step8_controller.js +++ b/ambari-web/app/controllers/wizard/step8_controller.js @@ -1679,7 +1679,7 @@ App.WizardStep8Controller = Em.Controller.extend(App.AddSecurityConfigs, App.wiz removeInstalledServicesConfigurationGroups: function (groupsToDelete) { var self = this; groupsToDelete.forEach(function (item) { - self.deleteConfigGroup(Em.Object.create(item)); + self.deleteConfigurationGroup(Em.Object.create(item)); }); }, http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/app/mixins/main/service/configs/config_overridable.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/main/service/configs/config_overridable.js b/ambari-web/app/mixins/main/service/configs/config_overridable.js index 0b3ead7..340ca5f 100644 --- a/ambari-web/app/mixins/main/service/configs/config_overridable.js +++ b/ambari-web/app/mixins/main/service/configs/config_overridable.js @@ -213,19 +213,6 @@ App.ConfigOverridable = Em.Mixin.create({ }, /** - * Persist config groups created in step7 wizard controller - * @method persistConfigGroups - */ - persistConfigGroups: function () { - var installerController = App.router.get('installerController'); - var step7Controller = App.router.get('wizardStep7Controller'); - installerController.saveServiceConfigGroups(step7Controller, step7Controller.get('content.controllerName') == 'addServiceController'); - App.clusterStatus.setClusterStatus({ - localdb: App.db.data - }); - }, - - /** * Create a new config-group for a service. * * @param {App.ConfigGroup} newConfigGroupData config group to post to server @@ -385,12 +372,12 @@ App.ConfigOverridable = Em.Mixin.create({ /** * Do request to delete config group * @param {App.ConfigGroup} configGroup - * @param {Function} successCallback - * @param {Function} errorCallback + * @param {Function} [successCallback] + * @param {Function} [errorCallback] * @return {$.ajax} - * @method deleteConfigGroup + * @method deleteConfigurationGroup */ - deleteConfigGroup: function (configGroup, successCallback, errorCallback) { + deleteConfigurationGroup: function (configGroup, successCallback, errorCallback) { var sendData = { name: 'common.delete.config_group', sender: this, @@ -429,6 +416,7 @@ App.ConfigOverridable = Em.Mixin.create({ * @method saveGroupConfirmationPopup */ saveGroupConfirmationPopup: function (groupName) { + var self = this; return App.ModalPopup.show({ header: Em.I18n.t('config.group.save.confirmation.header'), secondary: Em.I18n.t('config.group.save.confirmation.manage.button'), @@ -437,7 +425,7 @@ App.ConfigOverridable = Em.Mixin.create({ templateName: require('templates/common/configs/saveConfigGroup') }), onSecondary: function () { - App.router.get('mainServiceInfoConfigsController').manageConfigurationGroups(); + App.router.get('manageConfigGroupsController').manageConfigurationGroups(null, self.get('content')); this.hide(); } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/app/utils/hosts.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/hosts.js b/ambari-web/app/utils/hosts.js index f88ab4d..f2897a0 100644 --- a/ambari-web/app/utils/hosts.js +++ b/ambari-web/app/utils/hosts.js @@ -217,9 +217,6 @@ module.exports = { }); } this.hide(); - }, - onSecondary: function() { - this.hide(); } }); }, http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/app/views/common/configs/service_config_layout_tab_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/configs/service_config_layout_tab_view.js b/ambari-web/app/views/common/configs/service_config_layout_tab_view.js index 866e9b6..73f89fa 100644 --- a/ambari-web/app/views/common/configs/service_config_layout_tab_view.js +++ b/ambari-web/app/views/common/configs/service_config_layout_tab_view.js @@ -105,6 +105,7 @@ App.ServiceConfigLayoutTabView = Em.View.extend(App.ConfigOverridable, { /** * Mark isHiddenByFilter flag for configs, sub-sections, and tab + * @method filterEnhancedConfigs */ filterEnhancedConfigs: function () { var self = this; http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/app/views/main/service/manage_config_groups_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/service/manage_config_groups_view.js b/ambari-web/app/views/main/service/manage_config_groups_view.js index 8ddd460..8b44474 100644 --- a/ambari-web/app/views/main/service/manage_config_groups_view.js +++ b/ambari-web/app/views/main/service/manage_config_groups_view.js @@ -22,28 +22,101 @@ App.MainServiceManageConfigGroupView = Em.View.extend({ templateName: require('templates/main/service/manage_configuration_groups_popup'), + /** + * Select Config Group + * @type {App.ConfigGroup} + */ selectedConfigGroup: null, + /** + * Determines if "Delete Config Group" button is disabled + * @type {boolean} + */ isRemoveButtonDisabled: true, + /** + * Determines if "Rename Config Group" button is disabled + * @type {boolean} + */ isRenameButtonDisabled: true, + /** + * Determines if "Duplicate Config Group" button is disabled + * @type {boolean} + */ isDuplicateButtonDisabled: true, - //Disable actions remove and rename for Default config group - buttonObserver: function () { + /** + * Tooltip for "Add Config Group" button + * @type {string} + */ + addButtonTooltip: Em.I18n.t('services.service.config_groups_popup.addButton'), + + /** + * Tooltip for "Remove Config Group" button + * @type {string} + */ + removeButtonTooltip: Em.I18n.t('services.service.config_groups_popup.removeButton'), + + /** + * Tooltip for "Rename Config Group" button + * @type {string} + */ + renameButtonTooltip: Em.I18n.t('services.service.config_groups_popup.renameButton'), + + /** + * Tooltip for "Duplicate Config Group" button + * @type {string} + */ + duplicateButtonTooltip: Em.I18n.t('services.service.config_groups_popup.duplicateButton'), + + /** + * Tooltip for "Remove Host From Config Group" button + * @type {string} + */ + removeHostTooltip: Em.I18n.t('services.service.config_groups_popup.removeHost'), + + /** + * Tooltip for "Add Host To Config Group" button + * @type {string} + */ + addHostTooltip: function () { var selectedConfigGroup = this.get('controller.selectedConfigGroup'); - if(selectedConfigGroup.isDefault){ - this.set('isRemoveButtonDisabled', true); - this.set('isRenameButtonDisabled', true); - this.set('isDuplicateButtonDisabled', false); - }else{ - this.set('isRemoveButtonDisabled', false); - this.set('isRenameButtonDisabled', false); - this.set('isDuplicateButtonDisabled', false); + if (!selectedConfigGroup.get('isDefault') && selectedConfigGroup.get('isAddHostsDisabled')) { + return Em.I18n.t('services.service.config_groups_popup.addHostDisabled'); + } else { + return Em.I18n.t('services.service.config_groups_popup.addHost'); } + }.property('controller.selectedConfigGroup.isDefault', 'controller.selectedConfigGroup.isAddHostsDisabled'), + + willInsertElement: function() { + this.get('controller').loadHosts(); + }, + + didInsertElement: function () { + this.selectDefaultGroup(); + App.tooltip($('.properties-link')); + App.tooltip($("[rel='button-info']")); + App.tooltip($("[rel='button-info-dropdown']"), {placement: 'left'}); + }, + + /** + * Disable actions remove and rename for Default config group + * @method buttonObserver + */ + buttonObserver: function () { + var selectedConfigGroup = this.get('controller.selectedConfigGroup'); + this.set('isRemoveButtonDisabled', selectedConfigGroup.isDefault); + this.set('isRenameButtonDisabled', selectedConfigGroup.isDefault); + this.set('isDuplicateButtonDisabled', false); }.observes('controller.selectedConfigGroup'), + /** + * Prevent user to select more than 1 config group + * Select last one of "selected" + * Clean up <code>controller.selectedHosts</code> + * @method onGroupSelect + */ onGroupSelect: function () { var selectedConfigGroup = this.get('selectedConfigGroup'); // to unable user select more than one config group at a time @@ -56,50 +129,24 @@ App.MainServiceManageConfigGroupView = Em.View.extend({ this.set('controller.selectedHosts', []); }.observes('selectedConfigGroup'), + /** + * Select first config group after all groups are loaded + * @method onLoad + */ onLoad: function () { if (this.get('controller.isLoaded')) { this.set('selectedConfigGroup', this.get('controller.configGroups')[0]) } }.observes('controller.isLoaded', 'controller.configGroups'), - willInsertElement: function() { - this.get('controller').loadHosts(); - }, - - didInsertElement: function () { - this.selectDefaultGroup(); - App.tooltip($('.properties-link')); - App.tooltip($("[rel='button-info']")); - App.tooltip($("[rel='button-info-dropdown']"), {placement: 'left'}); - }, - + /** + * Select default config group after all config groups are loaded + * @method selectDefaultGroup + */ selectDefaultGroup: function () { if (this.get('controller.isLoaded')) { this.set('selectedConfigGroup', [this.get('controller.configGroups').findProperty('isDefault')]); } - }.observes('controller.isLoaded'), - - addButtonTooltip: function () { - return Em.I18n.t('services.service.config_groups_popup.addButton'); - }.property(), - removeButtonTooltip: function () { - return Em.I18n.t('services.service.config_groups_popup.removeButton'); - }.property(), - renameButtonTooltip: function () { - return Em.I18n.t('services.service.config_groups_popup.renameButton'); - }.property(), - duplicateButtonTooltip: function () { - return Em.I18n.t('services.service.config_groups_popup.duplicateButton'); - }.property(), - addHostTooltip: function () { - if (!this.get('controller.selectedConfigGroup.isDefault') && this.get('controller.selectedConfigGroup.isAddHostsDisabled')) { - return Em.I18n.t('services.service.config_groups_popup.addHostDisabled'); - } else { - return Em.I18n.t('services.service.config_groups_popup.addHost'); - } - }.property('controller.selectedConfigGroup.isDefault', 'controller.selectedConfigGroup.isAddHostsDisabled'), - removeHostTooltip: function () { - return Em.I18n.t('services.service.config_groups_popup.removeHost'); - }.property() + }.observes('controller.isLoaded') }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/test/controllers/main/service/info/config_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/main/service/info/config_test.js b/ambari-web/test/controllers/main/service/info/config_test.js index 9100741..0e9ef12 100644 --- a/ambari-web/test/controllers/main/service/info/config_test.js +++ b/ambari-web/test/controllers/main/service/info/config_test.js @@ -168,19 +168,6 @@ describe("App.MainServiceInfoConfigsController", function () { }); }); - describe("#manageConfigurationGroup", function () { - beforeEach(function () { - sinon.stub(mainServiceInfoConfigsController, "manageConfigurationGroups", Em.K); - }); - afterEach(function () { - mainServiceInfoConfigsController.manageConfigurationGroups.restore(); - }); - it("run manageConfigurationGroups", function () { - mainServiceInfoConfigsController.manageConfigurationGroup(); - expect(mainServiceInfoConfigsController.manageConfigurationGroups.calledOnce).to.equal(true); - }); - }); - describe("#addOverrideProperty", function () { var serviceConfigProperty = Em.Object.create({ overrides: [], http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/test/controllers/main/service/manage_config_groups_controller_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/main/service/manage_config_groups_controller_test.js b/ambari-web/test/controllers/main/service/manage_config_groups_controller_test.js index 0a95234..a1812c8 100644 --- a/ambari-web/test/controllers/main/service/manage_config_groups_controller_test.js +++ b/ambari-web/test/controllers/main/service/manage_config_groups_controller_test.js @@ -97,4 +97,64 @@ describe('App.ManageConfigGroupsController', function() { }); + describe('#isHostsModified', function () { + + Em.A([ + { + o: { + toClearHosts: [], + toDelete: [], + toSetHosts: [], + toCreate: [] + }, + e: false + }, + { + o: { + toClearHosts: [{}], + toDelete: [], + toSetHosts: [], + toCreate: [] + }, + e: true + }, + { + o: { + toClearHosts: [], + toDelete: [{}], + toSetHosts: [], + toCreate: [] + }, + e: true + }, + { + o: { + toClearHosts: [], + toDelete: [], + toSetHosts: [{}], + toCreate: [] + }, + e: true + }, + { + o: { + toClearHosts: [], + toDelete: [], + toSetHosts: [], + toCreate: [{}] + }, + e: true + } + ]).forEach(function (test, index) { + it('test #' + index, function () { + c.reopen({ + isLoaded: true, + hostsModifiedConfigGroups: test.o + }); + expect(c.get('isHostsModified')).to.equal(test.e); + }); + }); + + }); + }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c06fe29f/ambari-web/test/controllers/wizard/step8_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/wizard/step8_test.js b/ambari-web/test/controllers/wizard/step8_test.js index 470bc86..1b97f6d 100644 --- a/ambari-web/test/controllers/wizard/step8_test.js +++ b/ambari-web/test/controllers/wizard/step8_test.js @@ -988,15 +988,15 @@ describe('App.WizardStep8Controller', function () { describe('#removeInstalledServicesConfigurationGroups', function() { beforeEach(function() { - sinon.stub(installerStep8Controller, 'deleteConfigGroup', Em.K); + sinon.stub(installerStep8Controller, 'deleteConfigurationGroup', Em.K); }); afterEach(function() { - installerStep8Controller.deleteConfigGroup.restore(); + installerStep8Controller.deleteConfigurationGroup.restore(); }); it('should call App.config.deleteConfigGroup for each received group', function() { var groups = [{}, {}, {}]; installerStep8Controller.removeInstalledServicesConfigurationGroups(groups); - expect(installerStep8Controller.deleteConfigGroup.callCount).to.equal(groups.length); + expect(installerStep8Controller.deleteConfigurationGroup.callCount).to.equal(groups.length); }); });
