Merge "Truncate message published on Kafka / Spike: Define solution for logs separation"
[ccsdk/cds.git] / cds-ui / designer-client / src / app / modules / feature-modules / packages / designer / designer.component.ts
index b19f569..5adce7e 100644 (file)
@@ -1,9 +1,39 @@
-import { Component, OnInit, ViewEncapsulation } from '@angular/core';
+/*
+============LICENSE_START==========================================
+===================================================================
+Copyright (C) 2019 Orange. All rights reserved.
+===================================================================
+
+Unless otherwise specified, all software contained herein is licensed
+under the Apache License, Version 2.0 (the License);
+you may not use this software 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.
+============LICENSE_END============================================
+*/
+
+import dagre from 'dagre';
+import graphlib from 'graphlib';
+import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core';
 import * as joint from 'jointjs';
 import './jointjs/elements/palette.function.element';
 import './jointjs/elements/action.element';
 import './jointjs/elements/board.function.element';
-
+import { DesignerStore } from './designer.store';
+import { ActionElementTypeName } from 'src/app/common/constants/app-constants';
+import { GraphUtil } from './graph.util';
+import { GraphGenerator } from './graph.generator.util';
+import { FunctionsStore } from './functions.store';
+import { Subject } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';
+import { distinctUntilChanged } from 'rxjs/operators';
 
 
 @Component({
@@ -12,23 +42,26 @@ import './jointjs/elements/board.function.element';
   styleUrls: ['./designer.component.css'],
   encapsulation: ViewEncapsulation.None
 })
-export class DesignerComponent implements OnInit {
+export class DesignerComponent implements OnInit, OnDestroy {
 
   private controllerSideBar: boolean;
   private attributesSideBar: boolean;
-  //to generate Ids for dragged function elements
-  private fuctionIdCounter=0;
-  private actionIdCounter=0;
 
   boardGraph: joint.dia.Graph;
   boardPaper: joint.dia.Paper;
 
   paletteGraph: joint.dia.Graph;
   palettePaper: joint.dia.Paper;
+  private ngUnsubscribe = new Subject();
+  private opt = { tx: 100, ty: 100 };
 
-  constructor() {
+  constructor(private designerStore: DesignerStore,
+              private functionStore: FunctionsStore,
+              private graphUtil: GraphUtil,
+              private graphGenerator: GraphGenerator) {
     this.controllerSideBar = true;
     this.attributesSideBar = false;
+
   }
   private _toggleSidebar1() {
     this.controllerSideBar = !this.controllerSideBar;
@@ -53,23 +86,66 @@ export class DesignerComponent implements OnInit {
   ngOnInit() {
     this.initializeBoard();
     this.initializePalette();
-    // this.createEditBarOverThePaper();
-    const list = [
-      { modelName: 'component-netconf-executor'},
-      { modelName: 'component-remote-ansible-executor' },
-      { modelName: 'dg-generic' },
-      { modelName: 'component-resource-resolution' }];
+    this.stencilPaperEventListeners();
 
-    const cells = this.buildPaletteGraphFromList(list);
-    this.paletteGraph.resetCells(cells);
+    /**
+     * the code to retrieve from server is commented
+     */
+    this.functionStore.state$
+      .pipe(
+        distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
+        takeUntil(this.ngUnsubscribe))
+      .subscribe(state => {
+
+        if (state.serverFunctions) {
+          console.log('inside subscriotn on functions store -->', state.serverFunctions);
+          console.log(state);
+          // this.viewedFunctions = state.functions;
+          const list = state.serverFunctions;
+
+          const cells = this.graphUtil.buildPaletteGraphFromList(list);
+          this.paletteGraph.resetCells(cells);
+
+          let idx = 0;
+          cells.forEach(cell => {
+            cell.translate(5, (cell.attributes.size.height + 5) * idx++);
+          });
+        }
+      });
 
-    let idx = 0;
-    cells.forEach(cell => {
-      console.log(cell);
-      cell.translate(5, (cell.attributes.size.height + 5) * idx++);
+    this.designerStore.state$
+      .pipe(
+        distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
+        takeUntil(this.ngUnsubscribe))
+      .subscribe(state => {
+        if (state.sourceContent) {
+          console.log('inside desinger.component---> ', state);
+          // generate graph from store objects if exist
+          const topologtTemplate = JSON.parse(state.sourceContent);
+          console.log(topologtTemplate);
+          delete state.sourceContent;
+          this.graphGenerator.populate(topologtTemplate, this.boardGraph);
+
+          console.log('all cells', this.boardGraph.getCells());
+          /**
+           * auto arrange elements in graph
+           * https://resources.jointjs.com/docs/jointjs/v3.1/joint.html#layout.DirectedGraph
+           */
+          joint.layout.DirectedGraph.layout( this.boardGraph.getCells(), {
+            dagre,
+            graphlib,
+            setLinkVertices: false,
+            marginX: 10,
+            marginY: 10,
+            clusterPadding: { top: 100, left: 30, right: 10, bottom: 100 },
+            rankDir: 'TB'
+          });
+        }
+      });
+
+    // action triggering
+    this.functionStore.retrieveFuntions();
 
-    });
-    this.stencilPaperEventListeners();
   }
 
   initializePalette() {
@@ -78,22 +154,26 @@ export class DesignerComponent implements OnInit {
       this.palettePaper = new joint.dia.Paper({
         el: $('#palette-paper'),
         model: this.paletteGraph,
-        height: 300,
         width: 300,
-        gridSize: 1,
+        height: $('#palette-paper').height(),
+        // background: {
+        //   color: 'rgba(0, 255, 0, 0.3)'
+        // },
         interactive: false
+        // elements in paletter need to be fixed, please refer to flying paper concept
       });
     }
   }
 
   initializeBoard() {
     if (!this.boardGraph) {
+      console.log('initializeBoard...');
       this.boardGraph = new joint.dia.Graph();
       this.boardPaper = new joint.dia.Paper({
           el: $('#board-paper'),
           model: this.boardGraph,
           height: 720,
-          width: 1200,
+          width: 1100,
           gridSize: 10,
           drawGrid: true,
           // background: {
@@ -120,22 +200,21 @@ export class DesignerComponent implements OnInit {
 
       this.boardGraph.on('change:position', (cell) => {
 
-        var parentId = cell.get('parent');
-        if (!parentId) return;
+        const parentId = cell.get('parent');
+        if (!parentId) {
+          // this is action
+          return;
+        }
 
-        var parent = this.boardGraph.getCell(parentId);
-        
-        var parentBbox = parent.getBBox();
-        var cellBbox = cell.getBBox();
+        const parent = this.boardGraph.getCell(parentId);
 
-        console.log("parent ", parentBbox);
-        console.log("cell ", cellBbox);
+        const parentBbox = parent.getBBox();
+        const cellBbox = cell.getBBox();
         if (parentBbox.containsPoint(cellBbox.origin()) &&
           parentBbox.containsPoint(cellBbox.topRight()) &&
           parentBbox.containsPoint(cellBbox.corner()) &&
           parentBbox.containsPoint(cellBbox.bottomLeft())) {
 
-          
           // All the four corners of the child are inside
           // the parent area.
           return;
@@ -145,54 +224,18 @@ export class DesignerComponent implements OnInit {
         cell.set('position', cell.previous('position'));
       });
     }
+    console.log('done initializing Board...');
   }
 
   insertCustomActionIntoBoard() {
-    this.actionIdCounter++;
-    const element = this.createCustomAction("action_"+ this.actionIdCounter, 'Action' + this.actionIdCounter);
-    this.boardGraph.addCell(element);
-  }
-
-  createCustomAction(id: string, label: string) {
-    const element = new joint.shapes.app.ActionElement({
-      id: id
-    });
-    element.attr('#label/text', label);
-    return element;
-  }
-
-  buildPaletteGraphFromList(list: any) {
-    const elements = [];
-
-    console.log(list);
-    list.forEach(element => {
-      elements.push(this.createFuctionElementForPalette(element.modelName));
-    });
-
-    return elements;
-  }
-
-  createFuctionElementForPalette(label: string) {
-      const element = new joint.shapes.palette.FunctionElement({
-        id: label
-      });
-      element.attr('#label/text', label);
-      element.attr('type', label);
-      return element;
-    }
-
-  createFuctionElementForBoard(id :String, label :string, type :string) {
-    const boardElement = new joint.shapes.board.FunctionElement({
-      id: id
-    });
-    boardElement.attr('#label/text', label);
-    boardElement.attr('#type/text', type);
-    return boardElement;
+    console.log('saving action to store action workflow....');
+    const actionName = this.graphUtil.generateNewActionName();
+    this.graphUtil.createCustomActionWithName(actionName, this.boardGraph);
+    this.designerStore.addDeclarativeWorkFlow(actionName);
   }
 
   stencilPaperEventListeners() {
     this.palettePaper.on('cell:pointerdown', (draggedCell, pointerDownEvent, x, y) => {
-      console.log('pointerdown 2');
 
       $('body').append(`
         <div id="flyPaper"
@@ -232,116 +275,62 @@ export class DesignerComponent implements OnInit {
         if (mouseupX > target.left &&
           mouseupX < target.left + this.boardPaper.$el.width() &&
           mouseupY > target.top && y < target.top + this.boardPaper.$el.height()) {
-          // const clonedShape = flyShape.clone();
-          const type = flyShape.attributes.attrs.type;
-          console.log(type);
-
-          //create board function element of the same type of palette function
-          //board function element is different in design from the palette function element
-          this.fuctionIdCounter++;
-          console.log(this.fuctionIdCounter);
-          const functionElementForBoard = 
-            this.createFuctionElementForBoard("fucntion_" + this.fuctionIdCounter, 'execute', type);
-
-          functionElementForBoard.position(mouseupX - target.left - offset.x, mouseupY - target.top - offset.y);
-          this.boardGraph.addCell(functionElementForBoard);
-          const cellViewsBelow =
-            this.boardPaper.findViewsFromPoint(functionElementForBoard.getBBox().center());
-          console.log(cellViewsBelow);
-          if (cellViewsBelow.length) {
-            let cellViewBelow;
-            cellViewsBelow.forEach( cellItem => {
-              if (cellItem.model.id !== functionElementForBoard.id) {
-                cellViewBelow = cellItem;
+          const functionType = this.graphUtil.getFunctionTypeFromPaletteFunction(flyShape);
+          // step name is CDS realted terminology, please refer to tosca types
+          const stepName = functionType;
+          const functionElementForBoard = this.graphUtil.dropFunctionOverActionWithPosition(
+              stepName, functionType,
+              mouseupX, mouseupY,
+              target, offset,
+              this.boardGraph);
+
+          const parentCell = this.graphUtil.getParent(functionElementForBoard, this.boardPaper);
+
+          if (parentCell &&
+              parentCell.model.attributes.type === ActionElementTypeName &&
+            this.graphUtil.canEmpedMoreChildern(parentCell.model, this.boardGraph)) {
+
+            if (this.graphUtil.isEmptyParent(parentCell.model)) {
+              // first function in action
+              const actionName = parentCell.model.attributes.attrs['#label'].text;
+              this.designerStore.addStepToDeclarativeWorkFlow(actionName, stepName, functionType);
+              if (functionType === 'dg-generic') {
+                this.designerStore.addDgGenericNodeTemplate(stepName);
+              } else {
+                this.designerStore.addNodeTemplate(stepName, functionType);
               }
-            });
+            } else {
+              // second action means there was a dg-generic node before
+              this.designerStore.addNodeTemplate(stepName, functionType);
+              // this will fail if multiple dg-generic were added
+              // TODO prevent multi functions of the same type inside the same action
+              const dgGenericNode = this.graphUtil.getDgGenericChild(parentCell.model, this.boardGraph)[0];
+              const dgGenericNodeName = this.graphUtil.getFunctionNameFromBoardFunction(dgGenericNode);
+              this.designerStore.addDgGenericDependency(dgGenericNodeName, stepName);
+            }
+
 
             // Prevent recursive embedding.
-            if (cellViewBelow && cellViewBelow.model.get('parent') !== functionElementForBoard.id) {
-              console.log(cellViewBelow);
-              cellViewBelow.model.embed(functionElementForBoard);
+            if (parentCell &&
+              parentCell.model.get('parent') !== functionElementForBoard.id) {
+              parentCell.model.embed(functionElementForBoard);
             }
-            console.log(this.boardGraph);
+          } else {
+            console.log('function dropped outside action or not allowed, rolling back...');
+            alert('function dropped outside action or not allowed, rolling back...');
+            functionElementForBoard.remove();
           }
-
         }
         $('body').off('mousemove.fly').off('mouseup.fly');
         // flyShape.remove();
         $('#flyPaper').remove();
       });
     });
+    console.log('done stencilPaperEventListeners()...');
   }
 
-  /**
-   * this is a way to add the button like zoom in , zoom out , and source over jointjs paper
-   * may be used if no other way is found
-   */
-  // createEditBarOverThePaper() {
-  //   joint.shapes["html"] = {};
-  //   joint.shapes["html"].Element = joint.shapes.basic.Rect.extend({
-  //     defaults: joint.util.deepSupplement({
-  //       type: 'html.Element'
-  //     }, joint.shapes.basic.Rect.prototype.defaults)
-  //   });
-  //   joint.shapes["html"].ElementView = joint.dia.ElementView.extend({
-
-  //     template: [
-  //       '<div>',
-  //       '<div id="editbar" class="editBar text-center">',
-  //       '<div class="btn-group mr-2" role="group" aria-label="First group">',
-  //       '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Undo">',
-  //       '<img src="/assets/img/icon-undoActive.svg">',
-  //       '</button>',
-  //       '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Redo">',
-  //       '<img src="/assets/img/icon-redo.svg">',
-  //       '</button>',
-  //       '</div>',
-  //       '<div class="btn-group mr-2" role="group" aria-label="Second group">',
-  //       '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Zoom Out">',
-  //       '<img src="/assets/img/icon-zoomOut.svg">',
-  //       '</button>',
-  //       '<button type="button" class="btn btn-secondary pl-0 pr-0">100%</button>',
-  //       '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Zoom In">',
-  //       '<img src="/assets/img/icon-zoomIn.svg">',
-  //       '</button>',
-  //       '</div>',
-  //       '<div class="btn-group viewBtns" role="group" aria-label="Third group">',
-  //       '<button type="button" class="btn btn-secondary topologySource active">View</button>',
-  //       '<button type="button" class="btn btn-secondary topologyView">Source</button>',
-  //       '</div>',
-  //       '</div>',
-  //       '</div>'
-  //     ].join(''),
-  //     initialize: function () {
-  //       _.bindAll(this, 'updateBox');
-  //       joint.dia.ElementView.prototype.initialize.apply(this, arguments);
-
-  //       this.$box = $(_.template(this.template)());
-  //       // Prevent paper from handling pointerdown.
-  //       this.$box.find('input,select').on('mousedown click', function (evt) {
-  //         evt.stopPropagation();
-  //       });
-  //       this.model.on('change', this.updateBox, this);
-  //       this.updateBox();
-  //     },
-  //     render: function () {
-  //       joint.dia.ElementView.prototype.render.apply(this, arguments);
-  //       this.paper.$el.prepend(this.$box);
-  //       this.updateBox();
-  //       return this;
-  //     },
-  //     updateBox: function () {
-  //       // Set the position and dimension of the box so that it covers the JointJS element.
-  //       var bbox = this.model.getBBox();
-  //       this.$box.css({
-  //         width: bbox.width,
-  //         height: bbox.height,
-  //         left: bbox.x,
-  //         top: bbox.y,
-  //         transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)'
-  //       });
-  //     }
-  //   });
-
-  // }
+  ngOnDestroy() {
+    this.ngUnsubscribe.next();
+    this.ngUnsubscribe.complete();
+  }
 }