[CORD-1943] New service graph
- labels
- enforcing service position
- started documentation
- toggling service instances
- toggle fullscreen
Change-Id: I01b71fb2607fb58711d4624f6b5b6479609b2f4f
diff --git a/src/app/service-graph/services/graph.store.ts b/src/app/service-graph/services/graph.store.ts
new file mode 100644
index 0000000..4d46c88
--- /dev/null
+++ b/src/app/service-graph/services/graph.store.ts
@@ -0,0 +1,309 @@
+/*
+ * 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 _ from 'lodash';
+import {Graph} from 'graphlib';
+import {IXosModelStoreService} from '../../datasources/stores/model.store';
+import {IXosDebouncer} from '../../core/services/helpers/debounce.helper';
+import {Subscription} from 'rxjs/Subscription';
+import {BehaviorSubject} from 'rxjs/BehaviorSubject';
+import {Observable} from 'rxjs/Observable';
+import {IXosBaseModel, IXosSgLink, IXosSgNode} from '../interfaces';
+
+
+export interface IXosGraphStore {
+ get(): Observable<Graph>;
+ nodesFromGraph(graph: Graph): IXosSgNode[];
+ linksFromGraph(graph: Graph): IXosSgLink[];
+ toggleServiceInstances(): Graph;
+}
+
+export class XosGraphStore implements IXosGraphStore {
+ static $inject = [
+ '$log',
+ 'XosModelStore',
+ 'XosDebouncer'
+ ];
+
+ // state
+ private serviceInstanceShown: boolean = false;
+
+ // graphs
+ private serviceGraph: any;
+ private ServiceGraphSubject: BehaviorSubject<any>;
+
+ // datastore
+ private ServiceSubscription: Subscription;
+ private ServiceDependencySubscription: Subscription;
+ private ServiceInstanceSubscription: Subscription;
+ private ServiceInstanceLinkSubscription: Subscription;
+
+ // debounced
+ private efficientNext = this.XosDebouncer.debounce(this.callNext, 500, this, false);
+
+ constructor (
+ private $log: ng.ILogService,
+ private XosModelStore: IXosModelStoreService,
+ private XosDebouncer: IXosDebouncer
+ ) {
+ this.$log.info('[XosGraphStore] Setup');
+
+ this.serviceGraph = new Graph();
+ this.ServiceGraphSubject = new BehaviorSubject(this.serviceGraph);
+
+ this.loadData();
+ }
+
+ $onDestroy() {
+ this.ServiceSubscription.unsubscribe();
+ this.ServiceDependencySubscription.unsubscribe();
+ }
+
+ public nodesFromGraph(graph: Graph): IXosSgNode[] {
+ return _.map(graph.nodes(), (n: string) => {
+ const nodeData = graph.node(n);
+ return {
+ id: n,
+ type: this.getModelType(nodeData),
+ data: nodeData
+ };
+ });
+ }
+
+ public linksFromGraph(graph: Graph): IXosSgLink[] {
+ const nodes = this.nodesFromGraph(graph);
+
+ // NOTE we'll need some intelligence here to differentiate between:
+ // - ServiceDependency
+ // - ServiceInstanceLinks
+ // - Owners
+
+ return _.map(graph.edges(), l => {
+ const link = graph.edge(l);
+ const linkType = this.getModelType(link);
+
+ // FIXME consider ownership links
+ let sourceId, targetId;
+
+ switch (linkType) {
+ case 'servicedependency':
+ sourceId = this.getServiceId(link.subscriber_service_id);
+ targetId = this.getServiceId(link.provider_service_id);
+ break;
+ case 'serviceinstancelink':
+ sourceId = this.getServiceInstanceId(link.subscriber_service_instance_id);
+ targetId = this.getServiceInstanceId(link.provider_service_instance_id);
+ break;
+ case 'ownership':
+ sourceId = this.getServiceId(link.service);
+ targetId = this.getServiceInstanceId(link.service_instance);
+ }
+
+ // NOTE help while debugging
+ if (!sourceId || !targetId) {
+ this.$log.warn(`Link ${l.v}-${l.w} has missing source or target:`, l, link);
+ }
+
+ return {
+ id: `${l.v}-${l.w}`,
+ type: this.getModelType(link),
+ source: _.findIndex(nodes, {id: sourceId}),
+ target: _.findIndex(nodes, {id: targetId}),
+ data: link
+ };
+ });
+ }
+
+ public toggleServiceInstances(): Graph {
+ if (this.serviceInstanceShown) {
+ // NOTE remove subscriptions
+ this.ServiceInstanceSubscription.unsubscribe();
+ this.ServiceInstanceLinkSubscription.unsubscribe();
+
+ // remove nodes from the graph
+ this.removeElementsFromGraph('serviceinstance'); // NOTE links are automatically removed by the graph library
+ }
+ else {
+ // NOTE subscribe to ServiceInstance and ServiceInstanceLink observables
+ this.loadServiceInstances();
+ this.loadServiceInstanceLinks();
+ }
+ this.serviceInstanceShown = !this.serviceInstanceShown;
+ return this.serviceGraph;
+ }
+
+ public get(): Observable<Graph> {
+ return this.ServiceGraphSubject.asObservable();
+ }
+
+ private loadData() {
+ this.loadServices();
+ this.loadServiceDependencies();
+ }
+
+ // graph operations
+ private addNode(node: IXosBaseModel) {
+ const nodeId = this.getNodeId(node);
+ this.serviceGraph.setNode(nodeId, node);
+
+ const nodeType = this.getModelType(node);
+ if (nodeType === 'serviceinstance') {
+ // NOTE adding owner link
+ this.addOwnershipEdge({
+ service: node.owner_id,
+ service_instance: node.id,
+ type: 'ownership'
+ });
+ }
+ }
+
+ private addEdge(link: IXosBaseModel) {
+ const linkType = this.getModelType(link);
+ if (linkType === 'servicedependency') {
+ const sourceId = this.getServiceId(link.subscriber_service_id);
+ const targetId = this.getServiceId(link.provider_service_id);
+ this.serviceGraph.setEdge(sourceId, targetId, link);
+ }
+ if (linkType === 'serviceinstancelink') {
+ // NOTE serviceinstancelink can point also to services, networks, ...
+ const sourceId = this.getServiceInstanceId(link.provider_service_instance_id);
+ if (angular.isDefined(link.subscriber_service_instance_id)) {
+ const targetId = this.getServiceInstanceId(link.subscriber_service_instance_id);
+ this.serviceGraph.setEdge(sourceId, targetId, link);
+ }
+ }
+ }
+
+ private addOwnershipEdge(link: any) {
+ const sourceId = this.getServiceInstanceId(link.service_instance);
+ const targetId = this.getServiceId(link.service);
+ this.serviceGraph.setEdge(sourceId, targetId, link);
+ }
+
+ private removeElementsFromGraph(type: string) {
+ _.forEach(this.serviceGraph.nodes(), (n: string) => {
+ const node = this.serviceGraph.node(n);
+ const nodeType = this.getModelType(node);
+ if (nodeType === type) {
+ this.serviceGraph.removeNode(n);
+ }
+ });
+ // NOTE update the observable
+ this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+ }
+
+ // helpers
+ private getModelType(node: IXosBaseModel): string {
+ if (node.type) {
+ // NOTE we'll add "ownership" links
+ return node.type;
+ }
+ return node.class_names.split(',')[0].toLowerCase();
+ }
+
+ private getServiceId(id: number): string {
+ return `service~${id}`;
+ }
+
+ private getServiceInstanceId(id: number): string {
+ return `serviceinstance~${id}`;
+ }
+
+ private getNodeId(node: IXosBaseModel): string {
+
+ const nodeType = this.getModelType(node);
+ switch (nodeType) {
+ case 'service':
+ return this.getServiceId(node.id);
+ case 'serviceinstance':
+ return this.getServiceInstanceId(node.id);
+ }
+ }
+
+ // data loaders
+ private loadServices() {
+ this.ServiceSubscription = this.XosModelStore.query('Service', '/core/services')
+ .subscribe(
+ (res) => {
+ if (res.length > 0) {
+ _.forEach(res, n => {
+ this.addNode(n);
+ });
+ this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+ }
+ },
+ (err) => {
+ this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
+ }
+ );
+ }
+
+ private loadServiceDependencies() {
+ this.ServiceDependencySubscription = this.XosModelStore.query('ServiceDependency', '/core/servicedependencys')
+ .subscribe(
+ (res) => {
+ if (res.length > 0) {
+ _.forEach(res, l => {
+ this.addEdge(l);
+ });
+ this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+ }
+ },
+ (err) => {
+ this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
+ }
+ );
+ }
+
+ private loadServiceInstances() {
+ this.ServiceInstanceSubscription = this.XosModelStore.query('ServiceInstance', '/core/serviceinstances')
+ .subscribe(
+ (res) => {
+ if (res.length > 0) {
+ _.forEach(res, n => {
+ this.addNode(n);
+ });
+ this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+ }
+ },
+ (err) => {
+ this.$log.error(`[XosServiceGraphStore] ServiceInstance Observable: `, err);
+ }
+ );
+ }
+
+ private loadServiceInstanceLinks() {
+ this.ServiceInstanceLinkSubscription = this.XosModelStore.query('ServiceInstanceLink', '/core/serviceinstancelinks')
+ .subscribe(
+ (res) => {
+ if (res.length > 0) {
+ _.forEach(res, l => {
+ this.addEdge(l);
+ });
+ this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+ }
+ },
+ (err) => {
+ this.$log.error(`[XosServiceGraphStore] ServiceInstanceLinks Observable: `, err);
+ }
+ );
+ }
+
+ private callNext(subject: BehaviorSubject<any>, data: any) {
+ subject.next(data);
+ }
+}