Browse Source

AMBARI-14682 Service uninstall: add UI elements. (atkach)

Andrii Tkach 9 years ago
parent
commit
fad7367ef5

+ 91 - 1
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,
   isServicesInfoLoaded: false,
 
 
   initHosts: function() {
   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);
     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}}&nbsp;{{view.confirmKey}}</b></label>&nbsp;' +
+        '{{view Ember.TextField valueBinding="view.parentView.confirmInput" class="input-small"}}</br>' +
+        '</form>')
+      })
+    });
+  }
+
 });
 });

+ 11 - 0
ambari-web/app/messages.js

@@ -293,6 +293,7 @@ Em.I18n.translations = {
   'common.optional': 'Optional',
   'common.optional': 'Optional',
   'common.running': 'Running',
   'common.running': 'Running',
   'common.stopped': 'Stopped',
   'common.stopped': 'Stopped',
+  'common.enter': 'Enter',
   'common.timeout.warning.popup.header': 'Automatic Logout',
   '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.before': 'You will be automatically logged out in ',
   'common.timeout.warning.popup.body.after': ' seconds due to inactivity',
   '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.run.immediateStopHawqCluster.error': 'Error during remote command: ',
   'services.service.actions.manage_configuration_groups.short':'Manage Config Groups',
   'services.service.actions.manage_configuration_groups.short':'Manage Config Groups',
   'services.service.actions.serviceActions':'Service Actions',
   '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.unknown':'unknown',
   'services.service.summary.notRunning':'Not Running',
   'services.service.summary.notRunning':'Not Running',
   'services.service.summary.notAvailable':'n/a',
   'services.service.summary.notAvailable':'n/a',

+ 6 - 0
ambari-web/app/models/host_component.js

@@ -384,6 +384,12 @@ App.HostComponentActionMap = {
         cssClass: 'icon-play-circle',
         cssClass: 'icon-play-circle',
         isHidden: false,
         isHidden: false,
         disabled: false
         disabled: false
+      },
+      DELETE_SERVICE: {
+        action: 'deleteService',
+        context: ctx.get('serviceName'),
+        label: Em.I18n.t('common.delete'),
+        cssClass: 'icon-remove'
       }
       }
     };
     };
   }
   }

+ 11 - 8
ambari-web/app/views/main/service/item.js

@@ -192,15 +192,16 @@ App.MainServiceItemView = Em.View.extend({
         var hawqMasterCustomCommands = hawqMasterComponent.get('customCommands');
         var hawqMasterCustomCommands = hawqMasterComponent.get('customCommands');
         customCommandToStopCluster = 'IMMEDIATE_STOP_CLUSTER';
         customCommandToStopCluster = 'IMMEDIATE_STOP_CLUSTER';
         if (hawqMasterCustomCommands && hawqMasterCustomCommands.contains(customCommandToStopCluster)) {
         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'),
             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) {
       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.DOWNLOAD_CLIENT_CONFIGS);
     }
     }
 
 
+    options.push(actionMap.DELETE_SERVICE);
+
     if (this.get('maintenance.length')) {
     if (this.get('maintenance.length')) {
       this.get('maintenance').forEach(function(option, index) {
       this.get('maintenance').forEach(function(option, index) {
         if (JSON.stringify(option) != JSON.stringify(options[index])) {
         if (JSON.stringify(option) != JSON.stringify(options[index])) {

+ 92 - 0
ambari-web/test/controllers/main/service/item_test.js

@@ -1175,4 +1175,96 @@ describe('App.MainServiceItemController', function () {
       expect(App.showConfirmationPopup.calledOnce).to.equal(true);
       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;
+    });
+  });
+
 });
 });