AMBARI-14682 Service uninstall: add UI elements. (atkach)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/fad7367e Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/fad7367e Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/fad7367e Branch: refs/heads/branch-dev-patch-upgrade Commit: fad7367ef5fb42064a7a10a90b091b66ec4f27fd Parents: 10a07da Author: Andrii Tkach <[email protected]> Authored: Fri Jan 15 12:15:02 2016 +0200 Committer: Andrii Tkach <[email protected]> Committed: Fri Jan 15 13:05:26 2016 +0200 ---------------------------------------------------------------------- ambari-web/app/controllers/main/service/item.js | 92 +++++++++++++++++++- ambari-web/app/messages.js | 11 +++ ambari-web/app/models/host_component.js | 6 ++ ambari-web/app/views/main/service/item.js | 19 ++-- .../test/controllers/main/service/item_test.js | 92 ++++++++++++++++++++ 5 files changed, 211 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/fad7367e/ambari-web/app/controllers/main/service/item.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service/item.js b/ambari-web/app/controllers/main/service/item.js index aa4ca5d..85e7910 100644 --- a/ambari-web/app/controllers/main/service/item.js +++ b/ambari-web/app/controllers/main/service/item.js @@ -44,6 +44,16 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow } }, + /** + * @type {boolean} + * @default true + */ + isPending: true, + + /** + * @type {boolean} + * @default false + */ isServicesInfoLoaded: false, initHosts: function() { @@ -938,5 +948,85 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow App.showAlertPopup(Em.I18n.t('services.service.actions.run.executeCustomCommand.error'), error); }, - isPending:true + /** + * delete service action + * @param {string} serviceName + */ + deleteService: function(serviceName) { + var dependentServices = App.StackService.find(serviceName).get('requiredServices').filter(function(_serviceName) { + return App.Service.find(_serviceName).get('isLoaded'); + }); + var self = this; + var displayName = App.format.role(serviceName); + + if (dependentServices.length > 0) { + this.dependentServicesWarning(serviceName, dependentServices); + } else if (App.Service.find(serviceName).get('workStatus') === 'INSTALLED') { + App.showConfirmationPopup( + function() {self.confirmDeleteService(serviceName)}, + Em.I18n.t('services.service.delete.popup.warning').format(displayName), + null, + Em.I18n.t('services.service.delete.popup.header'), + Em.I18n.t('common.delete'), + true + ); + } else { + App.ModalPopup.show({ + secondary: null, + header: Em.I18n.t('services.service.delete.popup.header'), + encodeBody: false, + body: Em.I18n.t('services.service.delete.popup.mustBeStopped').format(displayName) + }); + } + }, + + /** + * warning that show dependent services which must be deleted prior to chosen service deletion + * @param {string} origin + * @param {string} dependent + * @returns {App.ModalPopup} + */ + dependentServicesWarning: function(origin, dependent) { + var body = Em.I18n.t('services.service.delete.popup.dependentServices').format(App.format.role(origin)); + + body += '<ul>'; + dependent.forEach(function(serviceName) { + body += '<li>' + App.format.role(serviceName) + '</li>'; + }); + body += '</ul>'; + + return App.ModalPopup.show({ + secondary: null, + header: Em.I18n.t('services.service.delete.popup.header'), + bodyClass: Em.View.extend({ + template: Em.Handlebars.compile(body) + }) + }); + }, + + /** + * Confirmation popup of service deletion + * @param {string} serviceName + */ + confirmDeleteService: function (serviceName) { + var message = Em.I18n.t('services.service.confirmDelete.popup.body').format(App.format.role(serviceName)); + var confirmKey = 'yes'; + + App.ModalPopup.show({ + primary: Em.I18n.t('common.delete'), + primaryClass: 'btn-danger', + header: Em.I18n.t('services.service.confirmDelete.popup.header'), + confirmInput: '', + disablePrimary: Em.computed.notEqual('confirmInput', confirmKey), + bodyClass: Em.View.extend({ + confirmKey: confirmKey, + template: Em.Handlebars.compile(message + + '<form class="form-inline align-center"></br>' + + '<label><b>{{t common.enter}} {{view.confirmKey}}</b></label> ' + + '{{view Ember.TextField valueBinding="view.parentView.confirmInput" class="input-small"}}</br>' + + '</form>') + }) + }); + } + }); http://git-wip-us.apache.org/repos/asf/ambari/blob/fad7367e/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index e93aeff..2af22d1 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -293,6 +293,7 @@ Em.I18n.translations = { 'common.optional': 'Optional', 'common.running': 'Running', 'common.stopped': 'Stopped', + 'common.enter': 'Enter', 'common.timeout.warning.popup.header': 'Automatic Logout', 'common.timeout.warning.popup.body.before': 'You will be automatically logged out in ', 'common.timeout.warning.popup.body.after': ' seconds due to inactivity', @@ -1675,6 +1676,16 @@ Em.I18n.translations = { 'services.service.actions.run.immediateStopHawqCluster.error': 'Error during remote command: ', 'services.service.actions.manage_configuration_groups.short':'Manage Config Groups', 'services.service.actions.serviceActions':'Service Actions', + + 'services.service.delete.popup.header': 'Delete Service', + 'services.service.delete.popup.dependentServices': 'Prior to deleting <b>{0}</b>, you must delete the following dependent services:', + 'services.service.delete.popup.mustBeStopped': 'Prior to deleting <b>{0}</b>, you must stop the service.', + 'services.service.delete.popup.warning': 'The <b>{0} service will be removed from Ambari and all configurations' + + ' and configuration history will be lost</b>', + 'services.service.confirmDelete.popup.header': 'Confirm Delete', + 'services.service.confirmDelete.popup.body': 'You must confirm delete of <b>{0}</b> by typing "yes"' + + ' in the confirmation box. <b>This operation is not reversible and all configuration history will be lost.</b>', + 'services.service.summary.unknown':'unknown', 'services.service.summary.notRunning':'Not Running', 'services.service.summary.notAvailable':'n/a', http://git-wip-us.apache.org/repos/asf/ambari/blob/fad7367e/ambari-web/app/models/host_component.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/host_component.js b/ambari-web/app/models/host_component.js index e63f634..0c013b1 100644 --- a/ambari-web/app/models/host_component.js +++ b/ambari-web/app/models/host_component.js @@ -384,6 +384,12 @@ App.HostComponentActionMap = { cssClass: 'icon-play-circle', isHidden: false, disabled: false + }, + DELETE_SERVICE: { + action: 'deleteService', + context: ctx.get('serviceName'), + label: Em.I18n.t('common.delete'), + cssClass: 'icon-remove' } }; } http://git-wip-us.apache.org/repos/asf/ambari/blob/fad7367e/ambari-web/app/views/main/service/item.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/service/item.js b/ambari-web/app/views/main/service/item.js index 1dfa937..cb9782c 100644 --- a/ambari-web/app/views/main/service/item.js +++ b/ambari-web/app/views/main/service/item.js @@ -192,15 +192,16 @@ App.MainServiceItemView = Em.View.extend({ var hawqMasterCustomCommands = hawqMasterComponent.get('customCommands'); customCommandToStopCluster = 'IMMEDIATE_STOP_CLUSTER'; if (hawqMasterCustomCommands && hawqMasterCustomCommands.contains(customCommandToStopCluster)) { - options.push(self.createOption(actionMap.IMMEDIATE_STOP_CLUSTER, { - label: Em.I18n.t('services.service.actions.run.immediateStopHawqCluster.context'), - context: { + options.push(self.createOption(actionMap.IMMEDIATE_STOP_CLUSTER, { label: Em.I18n.t('services.service.actions.run.immediateStopHawqCluster.context'), - service: hawqMasterComponent.get('serviceName'), - component: hawqMasterComponent.get('componentName'), - command: customCommandToStopCluster - } - })) }; + context: { + label: Em.I18n.t('services.service.actions.run.immediateStopHawqCluster.context'), + service: hawqMasterComponent.get('serviceName'), + component: hawqMasterComponent.get('componentName'), + command: customCommandToStopCluster + } + })) + } } self.addActionMap().filterProperty('service', serviceName).forEach(function(item) { @@ -242,6 +243,8 @@ App.MainServiceItemView = Em.View.extend({ options.push(actionMap.DOWNLOAD_CLIENT_CONFIGS); } + options.push(actionMap.DELETE_SERVICE); + if (this.get('maintenance.length')) { this.get('maintenance').forEach(function(option, index) { if (JSON.stringify(option) != JSON.stringify(options[index])) { http://git-wip-us.apache.org/repos/asf/ambari/blob/fad7367e/ambari-web/test/controllers/main/service/item_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/main/service/item_test.js b/ambari-web/test/controllers/main/service/item_test.js index 7b75dbb..4fe46dc 100644 --- a/ambari-web/test/controllers/main/service/item_test.js +++ b/ambari-web/test/controllers/main/service/item_test.js @@ -1175,4 +1175,96 @@ describe('App.MainServiceItemController', function () { expect(App.showConfirmationPopup.calledOnce).to.equal(true); }); }); + + describe("#deleteService()", function() { + var mainServiceItemController; + + beforeEach(function() { + mainServiceItemController = App.MainServiceItemController.create({}); + this.mockStackService = sinon.stub(App.StackService, 'find'); + sinon.stub(mainServiceItemController, 'dependentServicesWarning'); + this.mockService = sinon.stub(App.Service, 'find'); + sinon.stub(App, 'showConfirmationPopup'); + sinon.stub(App.ModalPopup, 'show'); + sinon.stub(App.format, 'role', function(name) {return name}); + }); + afterEach(function() { + this.mockStackService.restore(); + this.mockService.restore(); + mainServiceItemController.dependentServicesWarning.restore(); + App.showConfirmationPopup.restore(); + App.ModalPopup.show.restore(); + App.format.role.restore(); + }); + + it("service has installed dependent services", function() { + this.mockStackService.returns(Em.Object.create({requiredServices: ['S2']})); + this.mockService.returns(Em.Object.create({workStatus: 'INSTALLED', isLoaded: true})); + mainServiceItemController.deleteService('S1'); + expect(mainServiceItemController.dependentServicesWarning.calledWith('S1', ['S2'])).to.be.true; + }); + + it("service has not-installed dependent services, and stopped", function() { + this.mockStackService.returns(Em.Object.create({requiredServices: ['S2']})); + this.mockService.returns(Em.Object.create({workStatus: 'INSTALLED', isLoaded: false})); + mainServiceItemController.deleteService('S1'); + expect(App.showConfirmationPopup.calledOnce).to.be.true; + }); + + it("service has not dependent services, and stopped", function() { + this.mockStackService.returns(Em.Object.create({requiredServices: []})); + this.mockService.returns(Em.Object.create({workStatus: 'INSTALLED', isLoaded: true})); + mainServiceItemController.deleteService('S1'); + expect(App.showConfirmationPopup.calledOnce).to.be.true; + }); + + it("service has not dependent services, and not stopped", function() { + this.mockStackService.returns(Em.Object.create({requiredServices: []})); + this.mockService.returns(Em.Object.create({workStatus: 'STARTED', isLoaded: true})); + mainServiceItemController.deleteService('S1'); + expect(App.ModalPopup.show.calledWith({ + secondary: null, + header: Em.I18n.t('services.service.delete.popup.header'), + encodeBody: false, + body: Em.I18n.t('services.service.delete.popup.mustBeStopped').format('S1') + })).to.be.true; + }); + }); + + describe("#dependentServicesWarning()", function() { + var mainServiceItemController; + + beforeEach(function() { + mainServiceItemController = App.MainServiceItemController.create({}); + sinon.stub(App.ModalPopup, 'show'); + sinon.stub(App.format, 'role', function(name) {return name}); + }); + afterEach(function() { + App.ModalPopup.show.restore(); + App.format.role.restore(); + }); + + it("App.ModalPopup.show should be called", function() { + mainServiceItemController.dependentServicesWarning('S1', ['S2']); + expect(App.ModalPopup.show.calledOnce).to.be.true; + }); + }); + + describe("#confirmDeleteService()", function() { + var mainServiceItemController; + + beforeEach(function() { + mainServiceItemController = App.MainServiceItemController.create({}); + sinon.stub(App.ModalPopup, 'show'); + }); + afterEach(function() { + App.ModalPopup.show.restore(); + }); + + it("App.ModalPopup.show should be called", function() { + mainServiceItemController.confirmDeleteService(); + expect(App.ModalPopup.show.calledOnce).to.be.true; + }); + }); + });
