[CORD-2647] Handling deletion for _decl model
Change-Id: Iedbf09a59907ed85009e1e9946e6994a34ab6adf
diff --git a/src/app/datasources/index.ts b/src/app/datasources/index.ts
index d5869b5..45f2561 100644
--- a/src/app/datasources/index.ts
+++ b/src/app/datasources/index.ts
@@ -18,7 +18,7 @@
import {ModelRest} from './rest/model.rest';
import {AuthService} from './rest/auth.rest';
-import {WebSocketEvent} from './websocket/global';
+import {SocketIoService, WebSocketEvent} from './websocket/global';
import {XosModelStore} from './stores/model.store';
import {StoreHelpers} from './helpers/store.helpers';
import {SynchronizerStore} from './stores/synchronizer.store';
@@ -34,6 +34,7 @@
.module(xosDataSources, ['ngCookies', 'ngResource', xosCore])
.service('ModelRest', ModelRest)
.service('AuthService', AuthService)
+ .service('SocketIo', SocketIoService)
.service('WebSocket', WebSocketEvent)
.service('XosModeldefsCache', XosModeldefsCache)
.service('StoreHelpers', StoreHelpers)
diff --git a/src/app/datasources/websocket/global.spec.ts b/src/app/datasources/websocket/global.spec.ts
new file mode 100644
index 0000000..7adaf07
--- /dev/null
+++ b/src/app/datasources/websocket/global.spec.ts
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as angular from 'angular';
+import 'angular-mocks';
+import {IWSEvent, IWSEventService, WebSocketEvent} from './global';
+
+const MockAppCfg = {
+ apiEndpoint: 'http://xos-test:3000/api',
+ websocketClient: 'http://xos-test:3000'
+};
+
+class MockSocket {
+ private cbs = {};
+
+ public on(event: string, cb: any) {
+ this.cbs[event] = cb;
+ }
+
+ public send(evt: string, data: any) {
+ const cb = this.cbs[evt];
+ cb(data);
+ }
+
+ public clean() {
+ this.cbs = {};
+ }
+}
+
+describe('The WebSocket service', () => {
+
+ let service, observable, mockWS;
+
+ beforeEach(() => {
+
+ mockWS = new MockSocket();
+
+ angular
+ .module('WebSocketEvent', [])
+ .service('WebSocket', WebSocketEvent)
+ .constant('AppConfig', MockAppCfg)
+ .value('SocketIo', {socket: mockWS});
+
+ angular.mock.module('WebSocketEvent');
+ });
+
+ beforeEach(angular.mock.inject((
+ WebSocket: IWSEventService
+ ) => {
+ service = WebSocket;
+ observable = service['_events'];
+
+ spyOn(observable, 'next');
+ }));
+
+ afterEach(() => {
+ mockWS.clean();
+ observable.next.calls.reset();
+ });
+
+ it('should have a list method', () => {
+ expect(service.list).toBeDefined();
+ });
+
+ describe('the update event', () => {
+ it('should update the base class', () => {
+ const data: IWSEvent = {
+ model: 'Test',
+ msg: {
+ pk: 1,
+ changed_fields: ['name'],
+ object: {}
+ }
+ };
+ mockWS.send('update', data);
+ expect(observable.next).toHaveBeenCalledWith(data);
+ expect(observable.next.calls.count()).toBe(1);
+ });
+
+ it('should not update the class if the changed_fields are not useful to the UI', () => {
+ const data: IWSEvent = {
+ model: 'Test',
+ msg: {
+ pk: 1,
+ changed_fields: ['created', 'updated', 'backend_register', 'backend_status', 'policy_status'],
+ object: {}
+ }
+ };
+ mockWS.send('update', data);
+ expect(observable.next).not.toHaveBeenCalled();
+ });
+
+ it('should update parent classes (if any)', () => {
+ const data: IWSEvent = {
+ model: 'ONOSApp',
+ msg: {
+ pk: 1,
+ changed_fields: ['name'],
+ object: {
+ class_names: 'ONOSApp,ONOSApp_decl,ServiceInstance,XOSBase,Model,PlModelMixIn,AttributeMixin,object'
+ }
+ }
+ };
+ mockWS.send('update', data);
+ expect(observable.next).toHaveBeenCalledWith(data);
+ const siEvent = data;
+ siEvent.model = 'ServiceInstance';
+ siEvent.skip_notification = true;
+ expect(observable.next).toHaveBeenCalledWith(siEvent);
+ expect(observable.next.calls.count()).toBe(2);
+ });
+ });
+
+ describe('the remove event', () => {
+ it('should trigger the remove event', () => {
+ const data: IWSEvent = {
+ model: 'Test',
+ msg: {
+ pk: 1,
+ changed_fields: [],
+ object: {}
+ },
+ };
+ mockWS.send('remove', data);
+ expect(observable.next).toHaveBeenCalledWith(data);
+ });
+
+ it('should update parent classes (if any)', () => {
+ const data: IWSEvent = {
+ model: 'ONOSApp',
+ msg: {
+ pk: 1,
+ changed_fields: ['name'],
+ object: {
+ class_names: 'ONOSApp,ONOSApp_decl,ServiceInstance,XOSBase,Model,PlModelMixIn,AttributeMixin,object'
+ }
+ }
+ };
+ mockWS.send('remove', data);
+ expect(observable.next).toHaveBeenCalledWith(data);
+ const siEvent = data;
+ siEvent.model = 'ServiceInstance';
+ siEvent.skip_notification = true;
+ expect(observable.next).toHaveBeenCalledWith(siEvent);
+ expect(observable.next.calls.count()).toBe(2);
+ });
+
+ it('should update derived class if the original is _decl', () => {
+ const data: IWSEvent = {
+ model: 'ONOSApp_decl',
+ msg: {
+ pk: 1,
+ changed_fields: ['name'],
+ object: {
+ class_names: 'ONOSApp_decl,ServiceInstance,XOSBase,Model,PlModelMixIn,AttributeMixin,object'
+ }
+ }
+ };
+ mockWS.send('remove', data);
+
+ const nextData = angular.copy(data);
+ nextData.model = 'ONOSApp';
+ expect(observable.next).toHaveBeenCalledWith(nextData);
+ const declEvent = nextData;
+ declEvent.model = 'ServiceInstance';
+ declEvent.skip_notification = true;
+ expect(observable.next).toHaveBeenCalledWith(declEvent);
+ expect(observable.next.calls.count()).toBe(2);
+ });
+ });
+});
diff --git a/src/app/datasources/websocket/global.ts b/src/app/datasources/websocket/global.ts
index 8774173..54c7209 100644
--- a/src/app/datasources/websocket/global.ts
+++ b/src/app/datasources/websocket/global.ts
@@ -36,10 +36,11 @@
list(): Observable<IWSEvent>;
}
-export class WebSocketEvent {
+export class WebSocketEvent implements IWSEventService {
static $inject = [
'AppConfig',
+ 'SocketIo',
'$log'
];
@@ -48,18 +49,26 @@
private socket;
constructor(
private AppConfig: IXosAppConfig,
+ private SocketIo: any,
private $log: ng.ILogService
) {
// NOTE list of field that are not useful to the UI
- const ignoredFields: string[] = ['created', 'updated', 'backend_register'];
+ const ignoredFields: string[] = ['created', 'updated', 'backend_register', 'backend_status', 'policy_status'];
- this.socket = io(this.AppConfig.websocketClient);
+ this.socket = this.SocketIo.socket;
this.socket.on('remove', (data: IWSEvent): void => {
this.$log.info(`[WebSocket] Received Remove Event for: ${data.model} [${data.msg.pk}]`, data);
+
+ if (data.model.indexOf('_decl') > -1) {
+ // the GUI doesn't know about _decl models,
+ // send the event for the actual model
+ data.model = data.model.replace('_decl', '');
+ }
this._events.next(data);
- // TODO update observers of parent classes
+ // NOTE update observers of parent classes
+ this.updateParentClasses(data);
});
this.socket.on('update', (data: IWSEvent): void => {
@@ -74,22 +83,57 @@
this._events.next(data);
// NOTE update observers of parent classes
- if (data.msg.object.class_names && angular.isString(data.msg.object.class_names)) {
- const models = data.msg.object.class_names.split(',');
- let event: IWSEvent = angular.copy(data);
- _.forEach(models, (m: string) => {
- // send event only if the parent class is not the same as the model class
- if (event.model !== m && m !== 'object') {
- event.model = m;
- event.skip_notification = true;
- this._events.next(event);
- }
- });
- }
+ this.updateParentClasses(data);
});
}
- list() {
+
+ public list() {
return this._events.asObservable();
}
+
+ private updateParentClasses(data: IWSEvent) {
+ if (data.msg.object.class_names && angular.isString(data.msg.object.class_names)) {
+ const models = data.msg.object.class_names.split(',');
+ let event: IWSEvent = angular.copy(data);
+ _.forEach(models, (m: string) => {
+
+ switch (m) {
+ case 'object':
+ case 'XOSBase':
+ case 'Model':
+ case 'PlModelMixIn':
+ case 'AttributeMixin':
+ // do not send events for classes that we don't care about
+ break;
+ default:
+ if (m.indexOf('_decl') > -1 || event.model === m) {
+ // do not send events for _decl classes
+ // or if the parent class is the same as the model class
+ return;
+ }
+
+ event.model = m;
+ event.skip_notification = true;
+ this._events.next(event);
+ }
+ });
+ }
+ }
+}
+
+export class SocketIoService {
+
+ static $inject = [
+ 'AppConfig'
+ ];
+
+ public socket;
+
+ constructor(
+ private AppConfig: IXosAppConfig,
+ private $log: ng.ILogService
+ ) {
+ this.socket = io(this.AppConfig.websocketClient);
+ }
}