Add batch actions to local user table

Add ability to remove, enable, disable local users in bulk.

- Updates to table-actions component to fix flickering issue
  by including track by $index in ng-repeat

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I67039e9b9d9cf7debe9f6ef87e71210bd4b64251
diff --git a/app/common/components/table/table-actions.js b/app/common/components/table/table-actions.js
index e1e47ec..d76d5e5 100644
--- a/app/common/components/table/table-actions.js
+++ b/app/common/components/table/table-actions.js
@@ -58,9 +58,9 @@
     };
 
     /**
-     * onInit Component lifecycle hook
+     * onChanges Component lifecycle hook
      */
-    this.$onInit = () => {
+    this.$onChanges = () => {
       this.actions = setActions(this.actions);
     };
   };
@@ -73,7 +73,7 @@
       class="btn  btn-tertiary"
       type="button"
       aria-label="{{action.type}}"
-      ng-repeat="action in $ctrl.actions"
+      ng-repeat="action in $ctrl.actions track by $index"
       ng-disabled="!action.enabled"
       ng-click="$ctrl.onClick(action.type)">
       <icon ng-if="action.file !== null" ng-file="{{action.file}}"></icon>
diff --git a/app/common/components/table/table.html b/app/common/components/table/table.html
index 7d906a1..387b18d 100644
--- a/app/common/components/table/table.html
+++ b/app/common/components/table/table.html
@@ -99,6 +99,7 @@
         <td ng-if="$ctrl.rowActionsEnabled"
             class="bmc-table__cell  bmc-table__row-actions">
           <table-actions
+            ng-if="row.actions"
             actions="row.actions"
             emit-action="$ctrl.onEmitRowAction(action, row)">
           </table-actions>
diff --git a/app/common/components/table/table.js b/app/common/components/table/table.js
index 2063555..a382429 100644
--- a/app/common/components/table/table.js
+++ b/app/common/components/table/table.js
@@ -288,6 +288,7 @@
       const dataChange = onChangesObj.data;
       if (dataChange) {
         prepData();
+        deselectAllRows();
       }
     };
   };
diff --git a/app/users/controllers/user-accounts-controller.html b/app/users/controllers/user-accounts-controller.html
index 9577196..31ba62d 100644
--- a/app/users/controllers/user-accounts-controller.html
+++ b/app/users/controllers/user-accounts-controller.html
@@ -26,7 +26,10 @@
         data="tableData"
         header="tableHeader"
         row-actions-enabled="true"
+        selectable="true"
+        batch-actions="tableBatchActions"
         emit-row-action="onEmitRowAction(value)"
+        emit-batch-action="onEmitBatchAction(value)"
         class="local-users__table">
       </bmc-table>
     </div>
diff --git a/app/users/controllers/user-accounts-controller.js b/app/users/controllers/user-accounts-controller.js
index 9f31beb..2e7605c 100644
--- a/app/users/controllers/user-accounts-controller.js
+++ b/app/users/controllers/user-accounts-controller.js
@@ -10,8 +10,8 @@
   'use strict';
 
   angular.module('app.users').controller('userAccountsController', [
-    '$scope', 'APIUtils', 'toastService', '$uibModal',
-    function($scope, APIUtils, toastService, $uibModal) {
+    '$scope', 'APIUtils', 'toastService', '$uibModal', '$q',
+    function($scope, APIUtils, toastService, $uibModal, $q) {
       $scope.loading;
       $scope.accountSettings;
       $scope.userRoles;
@@ -21,6 +21,19 @@
       $scope.tableHeader = [
         {label: 'Username'}, {label: 'Privilege'}, {label: 'Account status'}
       ];
+      $scope.tableBatchActions = [
+        {type: 'delete', label: 'Remove'},
+        {type: 'enable', label: 'Enable'},
+        {type: 'disable', label: 'Disable'},
+      ];
+
+      /**
+       * Returns true if username is 'root'
+       * @param {*} user
+       */
+      function checkIfRoot(user) {
+        return user.UserName === 'root' ? true : false;
+      }
 
       /**
        * Data table mapper
@@ -33,10 +46,10 @@
         const editAction = {type: 'Edit', enabled: true, file: 'icon-edit.svg'};
         const deleteAction = {
           type: 'Delete',
-          enabled: user.UserName === 'root' ? false : true,
+          enabled: checkIfRoot(user) ? false : true,
           file: 'icon-trashcan.svg'
         };
-
+        user.selectable = checkIfRoot(user) ? false : true;
         user.actions = [editAction, deleteAction];
         user.uiData = [user.UserName, user.RoleId, accountStatus];
         return user;
@@ -144,21 +157,74 @@
       }
 
       /**
-       * API call to delete user
-       * @param {*} username
+       * API call to delete users
+       * @param {*} users : Array of users to delete
        */
-      function deleteUser(username) {
+      function deleteUsers(users = []) {
         $scope.loading = true;
-        APIUtils.deleteUser(username)
+        const promises =
+            users.map((user) => APIUtils.deleteUser(user.UserName));
+        $q.all(promises)
             .then(() => {
-              getLocalUsers();
-              toastService.success(`User '${username}' has been deleted.`);
+              let message;
+              if (users.length > 1) {
+                message = 'Users have been removed.'
+              } else {
+                message = `User '${users[0].UserName}' has been removed.`
+              }
+              toastService.success(message);
             })
             .catch((error) => {
               console.log(JSON.stringify(error));
-              toastService.error(`Failed to delete user '${username}'.`);
+              let message;
+              if (users.length > 1) {
+                message = 'Failed to remove users.'
+              } else {
+                message = `Failed to remove user '${users[0].UserName}'.`
+              }
+              toastService.error(message);
             })
             .finally(() => {
+              getLocalUsers();
+              $scope.loading = false;
+            });
+      }
+
+      /**
+       * API call to update user status enabled/disabled
+       * @param {*} users : Array of users to update
+       * @param {boolean} enabled : status
+       */
+      function updateUserStatus(users = [], enabled = true) {
+        $scope.loading = true;
+        const promises = users.map(
+            (user) => APIUtils.updateUser(
+                user.UserName, null, null, null, enabled, null));
+        $q.all(promises)
+            .then(() => {
+              let message;
+              let statusLabel = enabled ? 'enabled' : 'disabled';
+              if (users.length > 1) {
+                message = `Users ${statusLabel}.`
+              } else {
+                message = `User '${users[0].UserName}' ${statusLabel}.`;
+              }
+              toastService.success(message);
+            })
+            .catch((error) => {
+              console.log(JSON.stringify(error));
+              let message;
+              let statusLabel = enabled ? 'enable' : 'disable';
+              if (users.length > 1) {
+                message = `Failed to ${statusLabel} users.`
+              } else {
+                message =
+                    `Failed to ${statusLabel} user '${users[0].UserName}'.`
+              }
+              toastService.error(message);
+            })
+            .finally(() => {
+              getLocalUsers();
               $scope.loading = false;
             });
       }
@@ -257,7 +323,7 @@
                 // Some form controls will be disabled for root users:
                 // edit enabled status, edit username, edit role
                 const isRoot =
-                    newUser ? false : user.UserName === 'root' ? true : false;
+                    newUser ? false : checkIfRoot(user) ? true : false;
                 // Array of existing usernames (excluding current user instance)
                 const existingUsernames =
                     $scope.localUsers.reduce((acc, val) => {
@@ -328,10 +394,10 @@
       }
 
       /**
-       * Intiate remove user modal
-       * @param {*} user
+       * Intiate remove users modal
+       * @param {*} users
        */
-      function initRemoveModal(user) {
+      function initRemoveModal(users) {
         const template = require('./user-accounts-modal-remove.html');
         $uibModal
             .open({
@@ -340,17 +406,12 @@
               ariaLabelledBy: 'dialog_label',
               controllerAs: 'modalCtrl',
               controller: function() {
-                this.user = user.UserName;
+                this.users = users;
               }
             })
             .result
             .then(() => {
-              const isRoot = user.UserName === 'root' ? true : false;
-              if (isRoot) {
-                toastService.error(`Cannot delete 'root' user.`)
-                return;
-              }
-              deleteUser(user.UserName);
+              deleteUsers(users);
             })
             .catch(
                 () => {
@@ -368,13 +429,32 @@
             initUserModal(value.row);
             break;
           case 'Delete':
-            initRemoveModal(value.row);
+            initRemoveModal([value.row]);
             break;
           default:
         }
       };
 
       /**
+       * Callback when batch action emitted from table
+       */
+      $scope.onEmitBatchAction = (value) => {
+        switch (value.action) {
+          case 'delete':
+            initRemoveModal(value.filteredRows);
+            break;
+          case 'enable':
+            updateUserStatus(value.filteredRows, true)
+            break;
+          case 'disable':
+            updateUserStatus(value.filteredRows, false)
+            break;
+          default:
+            break;
+        }
+      };
+
+      /**
        * Callback when 'Account settings policy' button clicked
        */
       $scope.onClickAccountSettingsPolicy = () => {
diff --git a/app/users/controllers/user-accounts-modal-remove.html b/app/users/controllers/user-accounts-modal-remove.html
index e615251..4a3efce 100644
--- a/app/users/controllers/user-accounts-modal-remove.html
+++ b/app/users/controllers/user-accounts-modal-remove.html
@@ -8,7 +8,8 @@
     </button>
   </div>
   <div class="modal-body">
-    <p>Are you sure you want to remove user '{{modalCtrl.user}}'? This action cannot be undone.</p>
+    <p ng-if="modalCtrl.users.length > 1">Are you sure you want to remove {{modalCtrl.users.length}} users? This action cannot be undone.</p>
+    <p ng-if="modalCtrl.users.length === 1">Are you sure you want to remove user '{{modalCtrl.users[0].UserName}}'? This action cannot be undone.</p>
   </div>
   <div class="modal-footer">
     <button class="btn btn-secondary" ng-click="$dismiss()" type="button">