[CORD-2277] Two stage delete for models

Change-Id: Ic1b1d59a9f1d6d963d10951e694cf963f41d84d5
diff --git a/src/app/core/header/header.spec.ts b/src/app/core/header/header.spec.ts
index 3437b99..023bb7d 100644
--- a/src/app/core/header/header.spec.ts
+++ b/src/app/core/header/header.spec.ts
@@ -25,6 +25,7 @@
 import {xosHeader, INotification} from './header';
 import {Subject} from 'rxjs';
 import {IXosDebugService} from '../debug/debug.service';
+import {IWSEvent} from '../../datasources/websocket/global';
 
 let element, scope: angular.IRootScopeService, compile: ng.ICompileService, isolatedScope;
 const events = new Subject();
@@ -38,7 +39,9 @@
 };
 
 const MockToastr = {
-  info: jasmine.createSpy('info')
+  info: jasmine.createSpy('info'),
+  success: jasmine.createSpy('success'),
+  error: jasmine.createSpy('error')
 };
 
 const MockAuth = {
@@ -168,7 +171,7 @@
     sendEvent(infoNotification);
     scope.$digest();
 
-    expect(MockToastr.info).toHaveBeenCalledWith('Synchronization started for: TestName', 'TestModel', {extraData: {dest: null}});
+    expect(MockToastr.info).toHaveBeenCalledWith('Synchronization in progress for: TestName', 'TestModel', {extraData: {dest: null}});
   });
 
   it('should not display a toastr for a new event that use skip_notification', () => {
@@ -178,5 +181,61 @@
     expect(MockToastr.info).not.toHaveBeenCalled();
   });
 
+  it('should send a synchronization success notification', () => {
+    const event: IWSEvent = {
+      model: 'TestModel',
+      msg: {
+      changed_fields: ['backend_status', 'backend_code'],
+        pk: 1,
+        object: {
+          name: 'TestName',
+          backend_status: 'OK',
+          backend_code: 1
+        }
+      }
+    };
+    sendEvent(event);
+    scope.$digest();
+
+    expect(MockToastr.success).toHaveBeenCalledWith('Synchronization succedeed for: TestName', 'TestModel', {extraData: {dest: null}});
+  });
+
+  it('should send a synchronization error notification', () => {
+    const event: IWSEvent = {
+      model: 'TestModel',
+      msg: {
+        changed_fields: ['backend_status', 'backend_code'],
+        pk: 1,
+        object: {
+          name: 'TestName',
+          backend_status: 'Failed',
+          backend_code: 2
+        }
+      }
+    };
+    sendEvent(event);
+    scope.$digest();
+
+    expect(MockToastr.error).toHaveBeenCalledWith('Synchronization failed for: TestName', 'TestModel', {extraData: {dest: null}});
+  });
+
+  it('should send a removal success notification', () => {
+    const event: IWSEvent = {
+      model: 'TestModel',
+      deleted: true,
+      msg: {
+        changed_fields: ['backend_status', 'backend_code'],
+        pk: 1,
+        object: {
+          name: 'TestName'
+        }
+      }
+    };
+    sendEvent(event);
+    scope.$digest();
+
+    expect(MockToastr.info).toHaveBeenCalledWith('Deleted object: TestName', 'TestModel', {extraData: {dest: null}});
+  });
+
   // TODO test error and success toaster call
 });
diff --git a/src/app/core/header/header.ts b/src/app/core/header/header.ts
index 26f6fd0..8bb60a4 100644
--- a/src/app/core/header/header.ts
+++ b/src/app/core/header/header.ts
@@ -134,29 +134,38 @@
 
             if (event.model === 'Diag') {
               // NOTE skip notifications for Diag model
+              // this should not arrive, but a check won't harm
               return;
             }
 
+            const isRemoval: boolean = event.deleted || false;
+
             let toastrMsg: string;
             let toastrLevel: string;
-            if (event.msg.object.backend_code === 0) {
-              toastrMsg = 'Synchronization started for:';
+            if (!isRemoval) {
+              if (event.msg.object.backend_code === 0) {
+                toastrMsg = 'Synchronization in progress for:';
+                toastrLevel = 'info';
+              }
+              else if (event.msg.object.backend_code === 1) {
+                toastrMsg = 'Synchronization succedeed for:';
+                toastrLevel = 'success';
+              }
+              else if (event.msg.object.backend_code === 2) {
+                toastrMsg = 'Synchronization failed for:';
+                toastrLevel = 'error';
+              }
+            }
+            else {
+              toastrMsg = 'Deleted object:';
               toastrLevel = 'info';
             }
-            else if (event.msg.object.backend_code === 1) {
-              toastrMsg = 'Synchronization succedeed for:';
-              toastrLevel = 'success';
-            }
-            else if (event.msg.object.backend_code === 2) {
-              toastrMsg = 'Synchronization failed for:';
-              toastrLevel = 'error';
-            }
 
             if (toastrLevel && toastrMsg) {
               let modelName = event.msg.object.name;
               let modelClassName = event.model;
               if (angular.isUndefined(event.msg.object.name) || event.msg.object.name === null) {
-                modelName = `${event.msg.object.leaf_model_name} [${event.msg.object.id}]`;
+                modelName = `${modelClassName} [${event.msg.object.id}]`;
               }
 
               const dest = this.ConfigHelpers.stateWithParamsForJs(modelClassName, event.msg.object);
diff --git a/src/app/core/services/helpers/config.helpers.ts b/src/app/core/services/helpers/config.helpers.ts
index 16958de..4750ac1 100644
--- a/src/app/core/services/helpers/config.helpers.ts
+++ b/src/app/core/services/helpers/config.helpers.ts
@@ -171,7 +171,7 @@
                   // TODO understand why it does not go directly in catch
                   throw new Error();
                 }
-                this.toastr.info(`${model.name} ${objName} successfully deleted`);
+                this.toastr.info(`Requested removal for ${model.name} ${objName}`);
               })
               .catch(() => {
                 this.toastr.error(`Error while deleting ${objName}`);
diff --git a/src/app/core/table/table.html b/src/app/core/table/table.html
index 6a128c7..479ee45 100644
--- a/src/app/core/table/table.html
+++ b/src/app/core/table/table.html
@@ -14,6 +14,7 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
+
 <div ng-show="vm.data.length > 0 && vm.loader == false">
     <div class="row" ng-if="vm.config.filter == 'fulltext'">
         <div class="col-xs-12">
@@ -64,7 +65,7 @@
             </tr>
         </tbody>
         <tbody>
-        <tr ng-class="{active: vm.config.selectedRow == $index}" ng-repeat="item in (vm.config.filteredData = (vm.data | filter:vm.query | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length))) track by $index">
+        <tr ng-class="{active: vm.config.selectedRow == $index, deleted: item.deleted}" ng-repeat="item in (vm.config.filteredData = (vm.data | filter:vm.query | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length))) track by $index">
             <td ng-repeat="col in vm.config.columns" xos-link-wrapper>
                 <span ng-if="!col.type || col.type === 'text'">{{item[col.prop]}}</span>
                 <span ng-if="col.type === 'boolean'">
diff --git a/src/app/core/table/table.scss b/src/app/core/table/table.scss
index b20895b..5935d62 100644
--- a/src/app/core/table/table.scss
+++ b/src/app/core/table/table.scss
@@ -56,4 +56,8 @@
       margin-left: $padding-base-horizontal;
     }
   }
+
+  tr.deleted {
+    background: rgba(255, 0, 0, 0.3) !important;
+  }
 }
\ No newline at end of file