New observable notify design in pubsub

Implemented the new subscription mechanism for the pub sub notify function

Change-Id: I5e6484adf1a0413d48b54b55048cda1a59b387ee
Issue-ID: SDC-1178
Signed-off-by: Idan Amit <ia096e@intl.att.com>
diff --git a/catalog-ui/src/app/models/base-pubsub.ts b/catalog-ui/src/app/models/base-pubsub.ts
index ca313b1..b9ff788 100644
--- a/catalog-ui/src/app/models/base-pubsub.ts
+++ b/catalog-ui/src/app/models/base-pubsub.ts
@@ -5,10 +5,12 @@
     subscribers: Map<string, ISubscriber>;
     eventsCallbacks: Array<Function>;
     clientId: string;
+    eventsToWait: Map<string, Array<string>>;
 
     constructor(pluginId: string) {
         this.subscribers = new Map<string, ISubscriber>();
-        this.eventsCallbacks = new Array<Function>();
+        this.eventsCallbacks = [];
+        this.eventsToWait = new Map<string, Array<string>>();
         this.clientId = pluginId;
         this.onMessage = this.onMessage.bind(this);
 
@@ -29,7 +31,13 @@
     }
 
     public on(callback: Function) {
-        this.eventsCallbacks.push(callback);
+        let functionExists = this.eventsCallbacks.find((func: Function) => {
+            return callback.toString() == func.toString()
+        });
+
+        if (!functionExists) {
+            this.eventsCallbacks.push(callback);
+        }
     }
 
     public off(callback: Function) {
@@ -44,9 +52,49 @@
             originId: this.clientId
         } as IPubSubEvent;
 
-        this.subscribers.forEach( (subscriber: ISubscriber, id: string) => {
-            subscriber.window.postMessage(eventObj, subscriber.locationUrl)
+        this.subscribers.forEach( (subscriber: ISubscriber, subscriberId: string) => {
+            subscriber.window.postMessage(eventObj, subscriber.locationUrl);
+
         });
+
+        return {
+            subscribe: function(callbackFn) {
+
+                if(this.subscribers.size !== 0) {
+                    let subscribersToNotify = Array.from(this.subscribers.keys());
+
+                    const checkNotifyComplete = (subscriberId: string) => {
+
+                        let index = subscribersToNotify.indexOf(subscriberId);
+                        subscribersToNotify.splice(index, 1);
+
+                        if (subscribersToNotify.length === 0) {
+                            callbackFn();
+                        }
+                    };
+
+                    this.subscribers.forEach((subscriber: ISubscriber, subscriberId: string) => {
+                        if (this.eventsToWait.has(subscriberId) && this.eventsToWait.get(subscriberId).indexOf(eventType) !== -1) {
+
+                            const actionCompletedFunction = (eventData, subId = subscriberId) => {
+                                if (eventData.type == "ACTION_COMPLETED") {
+                                    checkNotifyComplete(subId);
+                                }
+                                this.off(actionCompletedFunction);
+
+                            };
+                            this.on(actionCompletedFunction);
+                        }
+                        else {
+                            checkNotifyComplete(subscriberId);
+                        }
+                    });
+                }
+                else {
+                    callbackFn();
+                }
+            }.bind(this)
+        }
     }
 
     protected onMessage(event: any) {
@@ -58,31 +106,6 @@
     }
 }
 
-export class PluginPubSub extends BasePubSub {
-
-    constructor(pluginId: string, parentUrl: string) {
-        super(pluginId);
-        this.register('sdc-hub', window.parent, parentUrl);
-        this.subscribe();
-    }
-
-    public subscribe() {
-        const registerData = {
-            pluginId: this.clientId
-        };
-
-        this.notify('PLUGIN_REGISTER', registerData);
-    }
-
-    public unsubscribe() {
-        const unregisterData = {
-            pluginId: this.clientId
-        };
-
-        this.notify('PLUGIN_UNREGISTER', unregisterData);
-    }
-}
-
 export interface IPubSubEvent {
     type: string;
     originId: string;
diff --git a/catalog-ui/src/app/models/plugin-pubsub.ts b/catalog-ui/src/app/models/plugin-pubsub.ts
new file mode 100644
index 0000000..3a34de9
--- /dev/null
+++ b/catalog-ui/src/app/models/plugin-pubsub.ts
@@ -0,0 +1,29 @@
+import {BasePubSub} from "./base-pubsub";
+
+declare const window: Window;
+
+export class PluginPubSub extends BasePubSub {
+
+    constructor(pluginId: string, parentUrl: string, eventsToWait?: Array<string>) {
+        super(pluginId);
+        this.register('sdc-hub', window.parent, parentUrl);
+        this.subscribe(eventsToWait);
+    }
+
+    public subscribe(eventsToWait?: Array<string>) {
+        const registerData = {
+            pluginId: this.clientId,
+            eventsToWait: eventsToWait || []
+        };
+
+        this.notify('PLUGIN_REGISTER', registerData);
+    }
+
+    public unsubscribe() {
+        const unregisterData = {
+            pluginId: this.clientId
+        };
+
+        this.notify('PLUGIN_UNREGISTER', unregisterData);
+    }
+}
diff --git a/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.ts b/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.ts
index 2ba7847..4759721 100644
--- a/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.ts
+++ b/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.ts
@@ -43,13 +43,16 @@
             this.pluginUrl += this.urlSearchParams.toString();
         }
 
-        this.eventBusService.on((eventData) => {
+        let readyEvent = (eventData) => {
             if (eventData.originId === this.plugin.pluginId) {
                 if (eventData.type == "READY") {
                     this.onLoadingDone.emit();
+                    this.eventBusService.off(readyEvent)
                 }
             }
-        });
+        };
+
+        this.eventBusService.on(readyEvent);
 
         // Listening to the stateChangeStart event in order to notify the plugin about it being closed
         // before moving to a new state
@@ -58,11 +61,11 @@
                 if (!this.isClosed) {
                     event.preventDefault();
 
-                    this.eventBusService.notify("WINDOW_OUT");
+                    this.eventBusService.notify("WINDOW_OUT").subscribe(() => {
+                        this.isClosed = true;
 
-                    this.isClosed = true;
+                        this.eventBusService.unregister(this.plugin.pluginId);
 
-                    setTimeout(() => {
                         this.$state.go(toState.name, toParams);
                     });
                 }
diff --git a/catalog-ui/src/app/ng2/services/event-bus.service.ts b/catalog-ui/src/app/ng2/services/event-bus.service.ts
index 7730a77..438437d 100644
--- a/catalog-ui/src/app/ng2/services/event-bus.service.ts
+++ b/catalog-ui/src/app/ng2/services/event-bus.service.ts
@@ -11,6 +11,18 @@
     protected handlePluginRegistration(eventData: IPubSubEvent, event: any) {
         if (eventData.type === 'PLUGIN_REGISTER') {
             this.register(eventData.data.pluginId, event.source, event.origin);
+
+            let newEventsList = [];
+
+            if (this.eventsToWait.has(eventData.data.pluginId)) {
+                newEventsList = _.union(this.eventsToWait.get(eventData.data.pluginId), eventData.data.eventsToWait);
+            }
+            else {
+                newEventsList = eventData.data.eventsToWait;
+            }
+
+            this.eventsToWait.set(eventData.data.pluginId, newEventsList);
+
         } else if (eventData.type === 'PLUGIN_UNREGISTER') {
             this.unregister(eventData.data.pluginId);
         }
@@ -21,8 +33,9 @@
             pluginId: pluginId
         };
 
-        this.notify('PLUGIN_CLOSE', unregisterData);
-        super.unregister(pluginId);
+        this.notify('PLUGIN_CLOSE', unregisterData).subscribe(() => {
+            super.unregister(pluginId);
+        });
     }
 
     protected onMessage(event: any) {
diff --git a/catalog-ui/src/app/services/components/component-service.ts b/catalog-ui/src/app/services/components/component-service.ts
index 8331bdb..0b68c8b 100644
--- a/catalog-ui/src/app/services/components/component-service.ts
+++ b/catalog-ui/src/app/services/components/component-service.ts
@@ -24,7 +24,6 @@
 import {ComponentInstanceFactory, CommonUtils} from "app/utils";
 import {SharingService} from "../sharing-service";
 import {ComponentMetadata} from "../../models/component-metadata";
-import {EventBusService} from "../../ng2/services/event-bus.service";
 
 export interface IComponentService {
 
@@ -82,16 +81,14 @@
         'sdcConfig',
         'Sdc.Services.SharingService',
         '$q',
-        '$base64',
-        'EventBusService'
+        '$base64'
     ];
 
     constructor(protected restangular:restangular.IElement,
                 protected sdcConfig:IAppConfigurtaion,
                 protected sharingService:SharingService,
                 protected $q:ng.IQService,
-                protected $base64:any,
-                protected eventBusService:EventBusService
+                protected $base64:any
                ) {
 
         this.restangular.setBaseUrl(sdcConfig.api.root + sdcConfig.api.component_api_root);
@@ -230,16 +227,6 @@
             deferred.reject(err);
         });
 
-        // Notifying about events before executing the actual actions
-        switch (state) {
-            case "lifecycleState/CHECKIN":
-                this.eventBusService.notify("CHECK_IN");
-                break;
-            case "lifecycleState/certificationRequest":
-                this.eventBusService.notify("SUBMIT_FOR_TESTING");
-                break;
-        }
-
         return deferred.promise;
     };
 
diff --git a/catalog-ui/src/app/services/components/resource-service.ts b/catalog-ui/src/app/services/components/resource-service.ts
index aabc14f..15ef51e 100644
--- a/catalog-ui/src/app/services/components/resource-service.ts
+++ b/catalog-ui/src/app/services/components/resource-service.ts
@@ -26,7 +26,6 @@
 import {IComponentService, ComponentService} from "./component-service";
 import {PropertyModel, IAppConfigurtaion, Resource, Component} from "../../models";
 import {SharingService} from "../sharing-service";
-import {EventBusService} from "../../ng2/services/event-bus.service";
 
 export interface IResourceService extends IComponentService {
     updateResourceGroupProperties(uniqueId:string, groupId:string, properties:Array<PropertyModel>):ng.IPromise<Array<PropertyModel>>
@@ -39,18 +38,16 @@
         'sdcConfig',
         'Sdc.Services.SharingService',
         '$q',
-        '$base64',
-        'EventBusService'
+        '$base64'
     ];
 
     constructor(protected restangular:restangular.IElement,
                 protected sdcConfig:IAppConfigurtaion,
                 protected sharingService:SharingService,
                 protected $q:ng.IQService,
-                protected $base64:any,
-                protected eventBusService:EventBusService
+                protected $base64:any
                 ) {
-        super(restangular, sdcConfig, sharingService, $q, $base64, eventBusService);
+        super(restangular, sdcConfig, sharingService, $q, $base64);
 
         this.restangular = restangular.one("resources");
     }
diff --git a/catalog-ui/src/app/services/components/service-service.ts b/catalog-ui/src/app/services/components/service-service.ts
index ee3a023..cce0759 100644
--- a/catalog-ui/src/app/services/components/service-service.ts
+++ b/catalog-ui/src/app/services/components/service-service.ts
@@ -26,7 +26,6 @@
 import {IComponentService, ComponentService} from "./component-service";
 import {Distribution, DistributionComponent, Service, PropertyModel, Component, IAppConfigurtaion} from "app/models";
 import {SharingService} from "../sharing-service";
-import {EventBusService} from "../../ng2/services/event-bus.service";
 
 export interface IServiceService extends IComponentService {
     getDistributionsList(uuid:string):ng.IPromise<Array<Distribution>>;
@@ -42,8 +41,7 @@
         'sdcConfig',
         'Sdc.Services.SharingService',
         '$q',
-        '$base64',
-        'EventBusService'
+        '$base64'
     ];
 
     public distribution:string = "distribution";
@@ -52,9 +50,8 @@
                 protected sdcConfig:IAppConfigurtaion,
                 protected sharingService:SharingService,
                 protected $q:ng.IQService,
-                protected $base64:any,
-                protected eventBusService:EventBusService) {
-        super(restangular, sdcConfig, sharingService, $q, $base64, eventBusService);
+                protected $base64:any) {
+        super(restangular, sdcConfig, sharingService, $q, $base64);
 
         this.restangular = restangular.one("services");
     }
diff --git a/catalog-ui/src/app/utils/change-lifecycle-state-handler.ts b/catalog-ui/src/app/utils/change-lifecycle-state-handler.ts
index d32730d..f1c6544 100644
--- a/catalog-ui/src/app/utils/change-lifecycle-state-handler.ts
+++ b/catalog-ui/src/app/utils/change-lifecycle-state-handler.ts
@@ -7,9 +7,9 @@
  * 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.
@@ -24,6 +24,7 @@
 import {AsdcComment} from "../models/comments";
 import {ModalsHandler} from "./modals-handler";
 import {ServiceServiceNg2} from "../ng2/services/component-services/service.service";
+import {EventBusService} from "../ng2/services/event-bus.service";
 
 /**
  * Created by obarda on 2/11/2016.
@@ -37,7 +38,8 @@
         'ComponentFactory',
         '$filter',
         'ModalsHandler',
-        'ServiceServiceNg2'
+        'ServiceServiceNg2',
+        'EventBusService'
     ];
 
     constructor(private sdcConfig:IAppConfigurtaion,
@@ -45,7 +47,8 @@
                 private ComponentFactory:ComponentFactory,
                 private $filter:ng.IFilterService,
                 private ModalsHandler:ModalsHandler,
-                private ServiceServiceNg2:ServiceServiceNg2) {
+                private ServiceServiceNg2:ServiceServiceNg2,
+                private eventBusService:EventBusService) {
 
     }
 
@@ -101,7 +104,12 @@
             let onOk = (confirmationText):void => {
                 comment.userRemarks = confirmationText;
                 scope.isLoading = true;
-                component.changeLifecycleState(data.url, comment).then(onSuccess, onError);
+
+                if (data.url === "lifecycleState/CHECKIN") {
+                    this.eventBusService.notify("CHECK_IN").subscribe(() => {
+                        component.changeLifecycleState(data.url, comment).then(onSuccess, onError);
+                    });
+                }
             };
 
             let onCancel = ():void => {
@@ -118,10 +126,14 @@
             // Show email dialog if defined in menu.json
             //-------------------------------------------------
             let onOk = (resource):void => {
-                if (resource) {
-                    onSuccess(resource);
-                } else {
-                    onError("Error changing life cycle state");
+                if (data.url === "lifecycleState/certificationRequest") {
+                    this.eventBusService.notify("SUBMIT_FOR_TESTING").subscribe(() => {
+                        if (resource) {
+                            onSuccess(resource);
+                        } else {
+                            onError("Error changing life cycle state");
+                        }
+                    });
                 }
             };
 
diff --git a/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts b/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts
index 5d83c34..7fa2517 100644
--- a/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts
+++ b/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts
@@ -283,15 +283,14 @@
                 version: this.$scope.changeVersion.selectedVersion.versionNumber
             };
 
-            this.eventBusService.notify("VERSION_CHANGED", eventData);
-
-            this.$state.go(this.$state.current.name, {
-                id: selectedId,
-                type: this.$scope.componentType.toLowerCase(),
-                mode: WorkspaceMode.VIEW,
-                components: this.$state.params['components']
-            }, {reload: true});
-
+            this.eventBusService.notify("VERSION_CHANGED", eventData).subscribe(() => {
+                this.$state.go(this.$state.current.name, {
+                    id: selectedId,
+                    type: this.$scope.componentType.toLowerCase(),
+                    mode: WorkspaceMode.VIEW,
+                    components: this.$state.params['components']
+                }, {reload: true});
+            });
         };
 
         this.$scope.getLatestVersion = ():void => {
@@ -472,36 +471,38 @@
 
                 switch (url) {
                     case 'lifecycleState/CHECKOUT':
-                        // only checkOut get the full component from server
-                        //   this.$scope.component = component;
-                        // Work around to change the csar version
-                        if (this.cacheService.get(CHANGE_COMPONENT_CSAR_VERSION_FLAG)) {
-                            (<Resource>this.$scope.component).csarVersion = this.cacheService.get(CHANGE_COMPONENT_CSAR_VERSION_FLAG);
-                        }
+                        this.eventBusService.notify("CHECK_OUT", eventData).subscribe(() => {
+                            // only checkOut get the full component from server
+                            //   this.$scope.component = component;
+                            // Work around to change the csar version
+                            if (this.cacheService.get(CHANGE_COMPONENT_CSAR_VERSION_FLAG)) {
+                                (<Resource>this.$scope.component).csarVersion = this.cacheService.get(CHANGE_COMPONENT_CSAR_VERSION_FLAG);
+                            }
 
-                        //when checking out a minor version uuid remains
-                        const bcIdx = _.findIndex(this.components, (item) => {
-                            return item.uuid === component.uuid;
-                        });
-                        if (bcIdx !== -1) {
-                            this.components[bcIdx] = component;
-                        } else {
-                            //when checking out a major(certified) version
-                            this.components.unshift(component);
-                        }
-                        // this.$state.go(this.$state.current.name, {
-                        //     id: component.uniqueId,
-                        //     type: component.componentType.toLowerCase(),
-                        //     components: this.components
-                        // });
-                        this.$scope.mode = this.initViewMode();
-                        this.initChangeLifecycleStateButtons();
-                        this.initVersionObject();
-                        this.$scope.isLoading = false;
-                        this.EventListenerService.notifyObservers(EVENTS.ON_CHECKOUT, component);
-                        this.Notification.success({
-                            message: this.$filter('translate')("CHECKOUT_SUCCESS_MESSAGE_TEXT"),
-                            title: this.$filter('translate')("CHECKOUT_SUCCESS_MESSAGE_TITLE")
+                            //when checking out a minor version uuid remains
+                            const bcIdx = _.findIndex(this.components, (item) => {
+                                return item.uuid === component.uuid;
+                            });
+                            if (bcIdx !== -1) {
+                                this.components[bcIdx] = component;
+                            } else {
+                                //when checking out a major(certified) version
+                                this.components.unshift(component);
+                            }
+                            // this.$state.go(this.$state.current.name, {
+                            //     id: component.uniqueId,
+                            //     type: component.componentType.toLowerCase(),
+                            //     components: this.components
+                            // });
+                            this.$scope.mode = this.initViewMode();
+                            this.initChangeLifecycleStateButtons();
+                            this.initVersionObject();
+                            this.$scope.isLoading = false;
+                            this.EventListenerService.notifyObservers(EVENTS.ON_CHECKOUT, component);
+                            this.Notification.success({
+                                message: this.$filter('translate')("CHECKOUT_SUCCESS_MESSAGE_TEXT"),
+                                title: this.$filter('translate')("CHECKOUT_SUCCESS_MESSAGE_TITLE")
+                            });
                         });
                         break;
                     case 'lifecycleState/CHECKIN':
@@ -512,7 +513,7 @@
                         });
                         break;
                     case 'lifecycleState/UNDOCHECKOUT':
-                        setTimeout(() => {
+                        this.eventBusService.notify("UNDO_CHECK_OUT", eventData).subscribe(() => {
                             defaultActionAfterChangeLifecycleState();
                             this.Notification.success({
                                 message: this.$filter('translate')("DELETE_SUCCESS_MESSAGE_TEXT"),