You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2020/06/06 10:20:37 UTC

[incubator-streampipes] branch STREAMPIPES-145 updated: [STREAMPIPES-145] Add pipeline component

This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-145
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git


The following commit(s) were added to refs/heads/STREAMPIPES-145 by this push:
     new 53a561e  [STREAMPIPES-145] Add pipeline component
53a561e is described below

commit 53a561e02936a2b350088776223b999c669cae04
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Sat Jun 6 12:20:20 2020 +0200

    [STREAMPIPES-145] Add pipeline component
---
 .../pipeline-assembly.component.html               |  16 +-
 .../pipeline-assembly.component.ts                 |  13 +-
 .../pipeline-element-icon-stand.component.html     |   5 +-
 .../pipeline-element-icon-stand.component.ts       |  44 ++-
 .../pipeline-element/pipeline-element.component.ts |   5 +-
 .../components/pipeline/pipeline.component.css     |   0
 .../components/pipeline/pipeline.component.html    |  47 +++
 .../components/pipeline/pipeline.component.ts      | 350 +++++++++++++++++++++
 ui/src/app/editor-v2/constants/editor.constants.ts |  25 ++
 ui/src/app/editor-v2/editor.component.html         |   2 +-
 ui/src/app/editor-v2/editor.component.ts           |  77 +++--
 ui/src/app/editor-v2/editor.module.ts              |  10 +-
 ui/src/app/editor-v2/model/editor.model.ts         |  53 +++-
 .../editor-v2/services/jsplumb-bridge.service.ts   |   2 +
 ui/src/app/editor-v2/services/jsplumb.service.ts   |  42 +--
 .../editor-v2/services/pipeline-editor.service.ts  |   6 +-
 ui/src/app/editor-v2/utils/editor.utils.ts         |  97 ++++++
 17 files changed, 692 insertions(+), 102 deletions(-)

diff --git a/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.html b/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.html
index 8f0545a..ddd04dc 100644
--- a/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.html
+++ b/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.html
@@ -102,14 +102,14 @@
         </div>
     </div>
     <div id="outerAssemblyArea" class="outerAssembly sp-blue-border-nopadding">
-        <div mwlDroppable id="assembly" class="canvas" (drop)="elementDropped($event)">
-<!--            <pipeline pipeline-valid="ctrl.pipelineValid"-->
-<!--                      canvas-id="assembly"-->
-<!--                      raw-pipeline-model="ctrl.rawPipelineModel"-->
-<!--                      all-elements="ctrl.allElements"-->
-<!--                      preview="false"-->
-<!--                      pipeline-cached="ctrl.pipelineCached"-->
-<!--                      pipeline-cache-running="ctrl.pipelineCacheRunning"></pipeline>-->
+        <div id="assembly" class="canvas">
+            <pipeline [pipelineValid]="pipelineValid"
+                      [canvasId]="'assembly'"
+                      [rawPipelineModel]="rawPipelineModel"
+                      [allElements]="allElements"
+                      [preview]="false"
+                      [pipelineCached]="pipelineCached"
+                      [pipelineCacheRunning]="pipelineCacheRunning"></pipeline>
         </div>
     </div>
 </div>
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.ts b/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.ts
index b4fccc4..62c10a4 100644
--- a/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.ts
+++ b/ui/src/app/editor-v2/components/pipeline-assembly/pipeline-assembly.component.ts
@@ -28,6 +28,11 @@ import {JsplumbService} from "../../services/jsplumb.service";
 import {RestApi} from "../../../services/rest-api.service";
 import {TransitionService} from "../../../services/transition.service";
 import {ShepherdService} from "../../../services/tour/shepherd.service";
+import {
+    PipelineElementConfig,
+    PipelineElementHolder,
+    PipelineElementUnion
+} from "../../model/editor.model";
 
 
 @Component({
@@ -45,7 +50,7 @@ export class PipelineAssemblyComponent implements OnInit {
     preview: any;
 
     @Input()
-    rawPipelineModel: any;
+    rawPipelineModel: PipelineElementConfig[];
     selectMode: any;
     currentPipelineName: any;
     currentPipelineDescription: any;
@@ -54,7 +59,7 @@ export class PipelineAssemblyComponent implements OnInit {
     currentModifiedPipelineId: any;
 
     @Input()
-    allElements: any;
+    allElements: PipelineElementUnion[];
 
     errorMessagesDisplayed: any = false;
 
@@ -229,8 +234,4 @@ export class PipelineAssemblyComponent implements OnInit {
         return this.rawPipelineModel.length === 0 || this.rawPipelineModel.every(pe => pe.settings.disabled);
     }
 
-    elementDropped($event) {
-        console.log($event);
-    }
-
 }
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.html b/ui/src/app/editor-v2/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.html
index f797a9d..bdfac01 100644
--- a/ui/src/app/editor-v2/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.html
+++ b/ui/src/app/editor-v2/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.html
@@ -74,10 +74,11 @@
     </div>
 </div>
 <div flex id="editor-icon-stand" class="icon-stand" *ngIf="currentElements">
-    <span mwlDraggable id="{{ element.name }}" (mouseenter)="updateMouseOver(element.name)"
+    <span id="{{ element.appId }}" (mouseenter)="updateMouseOver(element.name)"
           (mouseleave)="updateMouseOver('')"
           *ngFor="let element of currentElements"
-          class="draggable-icon tt" [dropData]="element"
+          class="draggable-icon tt"
+          [attr.data-pe]="element.elementId"
           [ngClass]="activeCssClass">
         <span id="container" style="position:relative;display:block;width:80px;height:80px;">
         <pipeline-element id="pe-icon-stand-{{ element.appId }}" style="margin-left:-3%" [iconStandSize]="true" [pipelineElement]="element"
diff --git a/ui/src/app/editor-v2/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts b/ui/src/app/editor-v2/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts
index 784f89e..574ecb8 100644
--- a/ui/src/app/editor-v2/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts
+++ b/ui/src/app/editor-v2/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.ts
@@ -27,7 +27,8 @@ import {
     SpDataSet,
     SpDataStream
 } from "../../../core-model/gen/streampipes-model";
-import {EditorComponent} from "../../editor.component";
+import {PipelineElementType, PipelineElementUnion} from "../../model/editor.model";
+import {PipelineElementTypeUtils} from "../../utils/editor.utils";
 
 
 @Component({
@@ -38,13 +39,13 @@ import {EditorComponent} from "../../editor.component";
 export class PipelineElementIconStandComponent implements OnInit {
 
     @Input()
-    currentElements: (SpDataSet | SpDataStream | DataProcessorInvocation | DataSinkInvocation)[];
+    currentElements: PipelineElementUnion[];
 
     elementFilter: string;
     availableOptions: any = [];
     selectedOptions: any = [];
 
-    _activeType: string;
+    _activeType: PipelineElementType;
     activeCssClass: string;
 
     currentElementName: string;
@@ -54,10 +55,13 @@ export class PipelineElementIconStandComponent implements OnInit {
     }
 
     ngOnInit(): void {
-        console.log(this.activeType);
         this.loadOptions(this.activeType);
     }
 
+    ngAfterViewInit() {
+        this.makeDraggable();
+    }
+
     openHelpDialog(pipelineElement) {
         //this.EditorDialogManager.openHelpDialog(pipelineElement);
     }
@@ -114,21 +118,31 @@ export class PipelineElementIconStandComponent implements OnInit {
     }
 
     @Input()
-    set activeType(value: string) {
+    set activeType(value: PipelineElementType) {
         this._activeType = value;
         this.activeCssClass = this.makeActiveCssClass(value);
+        setTimeout(() => {
+            this.makeDraggable();
+        })
     };
 
-    makeActiveCssClass(elementType: string) {
-        if (EditorComponent.DATA_STREAM_IDENTIFIER === elementType) {
-            return "stream";
-        } else if (EditorComponent.DATA_SET_IDENTIFIER === elementType) {
-            return "set";
-        } else if (EditorComponent.DATA_PROCESSOR_IDENTIFIER === elementType) {
-            return "sepa";
-        } else {
-            return "action";
-        }
+    makeActiveCssClass(elementType: PipelineElementType): string {
+        return PipelineElementTypeUtils.toCssShortHand(elementType);
     }
 
+    makeDraggable() {
+        (<any>$('.draggable-icon')).draggable({
+            revert: 'invalid',
+            helper: 'clone',
+            stack: '.draggable-icon',
+            start: function (el, ui) {
+                ui.helper.appendTo('#content');
+                $('#outerAssemblyArea').css('border', '3px dashed #39b54a');
+            },
+            stop: function (el, ui) {
+                $('#outerAssemblyArea').css('border', '3px solid rgb(156, 156, 156)');
+            }
+        });
+    };
+
 }
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/components/pipeline-element/pipeline-element.component.ts b/ui/src/app/editor-v2/components/pipeline-element/pipeline-element.component.ts
index e6dc952..00620aa 100644
--- a/ui/src/app/editor-v2/components/pipeline-element/pipeline-element.component.ts
+++ b/ui/src/app/editor-v2/components/pipeline-element/pipeline-element.component.ts
@@ -24,6 +24,7 @@ import * as angular from "angular";
 import {RestApi} from "../../../services/rest-api.service";
 import {ElementIconText} from "../../../services/get-element-icon-text.service";
 import {ImageChecker} from "../../../services/image-checker.service";
+import {PipelineElementUnion} from "../../model/editor.model";
 
 
 @Component({
@@ -37,7 +38,7 @@ export class PipelineElementComponent implements OnInit {
     iconText: any;
 
     @Input()
-    pipelineElement: any;
+    pipelineElement: PipelineElementUnion;
 
     @Input()
     preview: any;
@@ -57,7 +58,6 @@ export class PipelineElementComponent implements OnInit {
     }
 
     ngOnInit(): void {
-        console.log(this.pipelineElement);
         this.iconText =  this.ElementIconText.getElementIconText(this.pipelineElement.name);
         this.checkImageAvailable();
     }
@@ -94,5 +94,4 @@ export class PipelineElementComponent implements OnInit {
         }
     }
 
-
 }
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/components/pipeline/pipeline.component.css b/ui/src/app/editor-v2/components/pipeline/pipeline.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/ui/src/app/editor-v2/components/pipeline/pipeline.component.html b/ui/src/app/editor-v2/components/pipeline/pipeline.component.html
new file mode 100644
index 0000000..9579975
--- /dev/null
+++ b/ui/src/app/editor-v2/components/pipeline/pipeline.component.html
@@ -0,0 +1,47 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You 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.
+  ~
+  -->
+
+<div *ngFor="let pipelineElement of rawPipelineModel" style="width:100%;height:100%;z-index:1">
+    <div *ngIf="pipelineElement.settings.disabled == undefined || !pipelineElement.settings.disabled">
+    <span id="{{pipelineElement.payload.dom}}" style="{{getElementCss(pipelineElement.settings)}}"
+          (click)="updateOptionsClick(pipelineElement.payload.dom)" (mouseenter)="updateMouseover(pipelineElement.payload.dom)" (mouseleave)="updateMouseover('')">
+        <span style="z-index:5;"
+              [ngClass]="getElementCssClasses(pipelineElement)">
+            <div class="pipeline-element-progress-container sp-fade" *ngIf="pipelineElement.settings.loadingStatus">
+<!--                 <md-progress-circular md-mode="indeterminate" class="pipeline-element-progress" md-diameter="40px"></md-progress-circular>-->
+            </div>
+            <div class="pipeline-element-loading-container sp-fade-opacity" *ngIf="pipelineElement.settings.loadingStatus"></div>
+            <div class="pipeline-element-configuration-invalid {{pipelineElement.type === 'stream' ? 'pi-stream' : 'pi-processor'}}" *ngIf="pipelineElement.payload.uncompleted">
+                <i class="material-icons pipeline-element-configuration-invalid-icon">
+                warning
+                </i>
+            </div>
+            <pipeline-element [pipelineElement]="pipelineElement.payload" [preview]="preview"></pipeline-element>
+        </span>
+<!--         <pipeline-element-options ng-if="!ctrl.preview" delete-function="ctrl.handleDeleteOption"-->
+<!--                                   current-mouse-over-element="ctrl.currentMouseOverElement"-->
+<!--                                   pipeline-valid="ctrl.pipelineValid"-->
+<!--                                   all-elements="ctrl.allElements"-->
+<!--                                   pipeline-element="pipelineElement"-->
+<!--                                   raw-pipeline-model="ctrl.rawPipelineModel"-->
+<!--                                   pipeline-element-id="pipelineElement.type == 'stream' ? pipelineElement.payload.elementId : pipelineElement.payload.belongsTo)"-->
+<!--                                   internal-id="" {{pipelineElement.payload.DOM}}">-->
+<!--        </pipeline-element-options>-->
+    </span>
+</div>
+</div>
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/components/pipeline/pipeline.component.ts b/ui/src/app/editor-v2/components/pipeline/pipeline.component.ts
new file mode 100644
index 0000000..a2ab01c
--- /dev/null
+++ b/ui/src/app/editor-v2/components/pipeline/pipeline.component.ts
@@ -0,0 +1,350 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 {PipelineValidationService} from "../../../editor-v2/services/pipeline-validation.service";
+import {RestApi} from "../../../services/rest-api.service";
+import {JsplumbService} from "../../services/jsplumb.service";
+import {PipelineEditorService} from "../../services/pipeline-editor.service";
+import {JsplumbBridge} from "../../services/jsplumb-bridge.service";
+import {ShepherdService} from "../../../services/tour/shepherd.service";
+import {Component, Input, OnInit} from "@angular/core";
+import {
+  PipelineElementConfig,
+  PipelineElementHolder,
+  PipelineElementUnion
+} from "../../model/editor.model";
+import {SpDataStream} from "../../../core-model/gen/streampipes-model";
+
+@Component({
+  selector: 'pipeline',
+  templateUrl: './pipeline.component.html',
+  styleUrls: ['./pipeline.component.css']
+})
+export class PipelineComponent implements OnInit {
+
+  @Input()
+  pipelineValid: boolean;
+
+  @Input()
+  canvasId: string;
+
+  @Input()
+  rawPipelineModel: PipelineElementConfig[];
+
+  @Input()
+  allElements: PipelineElementUnion[];
+
+  @Input()
+  preview: boolean;
+
+  @Input()
+  pipelineCached: boolean;
+
+  @Input()
+  pipelineCacheRunning: boolean;
+
+  availablePipelineElementCache: PipelineElementUnion[];
+
+  DialogBuilder: any;
+  plumbReady: any;
+  objectProvider: any;
+  EditorDialogManager: any;
+  currentMouseOverElement: any;
+  currentPipelineModel: any;
+  idCounter: any;
+  currentZoomLevel: any;
+  TransitionService: any;
+
+  // remove later
+
+  constructor(private JsplumbService: JsplumbService,
+              private PipelineEditorService: PipelineEditorService,
+              private JsplumbBridge: JsplumbBridge,
+              //DialogBuilder,
+              //EditorDialogManager,
+              // TransitionService,
+              private ShepherdService: ShepherdService,
+              private PipelineValidationService: PipelineValidationService,
+              private RestApi: RestApi) {
+    this.plumbReady = false;
+    this.currentMouseOverElement = "";
+    this.currentPipelineModel = {};
+    this.idCounter = 0;
+
+    this.currentZoomLevel = 1;
+  }
+
+  ngOnInit() {
+    this.JsplumbBridge.setContainer(this.canvasId);
+    this.initAssembly();
+    this.initPlumb();
+  }
+
+  validatePipeline() {
+    //this.$timeout(() => {
+      this.pipelineValid = this.PipelineValidationService.isValidPipeline(this.rawPipelineModel);
+    //}, 200);
+  }
+
+  ngOnDestroy() {
+    this.JsplumbBridge.deleteEveryEndpoint();
+    this.plumbReady = false;
+  }
+
+  updateMouseover(elementId) {
+    this.currentMouseOverElement = elementId;
+  }
+
+  updateOptionsClick(elementId) {
+    if (this.currentMouseOverElement == elementId) {
+      this.currentMouseOverElement = "";
+    } else {
+      this.currentMouseOverElement = elementId;
+    }
+  }
+
+  getElementCss(currentPipelineElementSettings) {
+    return "position:absolute;"
+        + (this.preview ? "width:75px;" : "width:110px;")
+        + (this.preview ? "height:75px;" : "height:110px;")
+        + "left: " + currentPipelineElementSettings.position.x + "px; "
+        + "top: " + currentPipelineElementSettings.position.y + "px; "
+  }
+
+  getElementCssClasses(currentPipelineElement) {
+    return currentPipelineElement.type + " " + (currentPipelineElement.settings.openCustomize ? "" : "")
+        + currentPipelineElement.settings.connectable + " "
+        + currentPipelineElement.settings.displaySettings;
+  }
+
+  isStreamInPipeline() {
+    return this.isInPipeline('stream');
+  }
+
+  isSetInPipeline() {
+    return this.isInPipeline('set');
+  }
+
+  isInPipeline(type) {
+    return this.rawPipelineModel.some(x => (x.type == type && !(x.settings.disabled)));
+  }
+
+  showMixedStreamAlert() {
+    this.EditorDialogManager.showMixedStreamAlert();
+  }
+
+  findPipelineElementByElementId(elementId: string) {
+    return this.allElements.find(a => a.elementId === elementId);
+  }
+
+
+  initAssembly() {
+    ($('#assembly') as any).droppable({
+      tolerance: "fit",
+      drop: (element, ui) => {
+        let pipelineElementId = ui.draggable.data("pe");
+        let pipelineElement: PipelineElementUnion = this.findPipelineElementByElementId(pipelineElementId);
+        if (ui.draggable.hasClass('draggable-icon')) {
+          //this.TransitionService.makePipelineAssemblyEmpty(false);
+          var pipelineElementConfig = this.JsplumbService.createNewPipelineElementConfig(pipelineElement, this.PipelineEditorService.getCoordinates(ui, this.currentZoomLevel), false);
+          if ((this.isStreamInPipeline() && pipelineElementConfig.type == 'set') ||
+              this.isSetInPipeline() && pipelineElementConfig.type == 'stream') {
+            this.showMixedStreamAlert();
+          } else {
+            this.rawPipelineModel.push(pipelineElementConfig);
+            if (ui.draggable.hasClass('set')) {
+              setTimeout(() => {
+                setTimeout(() => {
+                  this.JsplumbService.setDropped(pipelineElementConfig.payload.dom, pipelineElementConfig.payload, true, false);
+                });
+              });
+            }
+            else if (ui.draggable.hasClass('stream')) {
+              this.checkTopicModel(pipelineElementConfig);
+            } else if (ui.draggable.hasClass('sepa')) {
+              setTimeout(() => {
+                setTimeout(() => {
+                  this.JsplumbService.sepaDropped(pipelineElementConfig.payload.dom, pipelineElementConfig.payload, true, false);
+                });
+              });
+              //Droppable Actions
+            } else if (ui.draggable.hasClass('action')) {
+              setTimeout(() => {
+                setTimeout(() => {
+                  this.JsplumbService.actionDropped(pipelineElementConfig.payload.dom, pipelineElementConfig.payload, true, false);
+                });
+              });
+            }
+            if (this.ShepherdService.isTourActive()) {
+              this.ShepherdService.trigger("drop-" +pipelineElementConfig.type);
+            }
+          }
+        }
+        this.JsplumbBridge.repaintEverything();
+        this.validatePipeline();
+        this.triggerPipelineCacheUpdate();
+      }
+
+    }); //End #assembly.droppable()
+  }
+
+  checkTopicModel(pipelineElementConfig: PipelineElementConfig) {
+    setTimeout(() => {
+      setTimeout(() => {
+        this.JsplumbService.streamDropped(pipelineElementConfig.payload.dom, pipelineElementConfig.payload, true, false);
+      });
+    });
+
+    var streamDescription = pipelineElementConfig.payload as SpDataStream;
+    if (streamDescription
+        .eventGrounding
+        .transportProtocols[0]
+        .topicDefinition["@class"] === "org.apache.streampipes.model.grounding.WildcardTopicDefinition") {
+      this.EditorDialogManager.showCustomizeStreamDialog(streamDescription);
+    }
+  }
+
+  handleDeleteOption(pipelineElement) {
+    this.JsplumbBridge.removeAllEndpoints(pipelineElement.payload.dom);
+    angular.forEach(this.rawPipelineModel, pe => {
+      if (pe.payload.dom == pipelineElement.payload.DOM) {
+        pe.settings.disabled = true;
+      }
+    });
+    if (this.rawPipelineModel.every(pe => pe.settings.disabled)) {
+      this.TransitionService.makePipelineAssemblyEmpty(true);
+    }
+    this.JsplumbBridge.repaintEverything();
+    this.RestApi.updateCachedPipeline(this.rawPipelineModel);
+  }
+
+  initPlumb() {
+
+    this.JsplumbService.prepareJsplumb();
+
+    this.JsplumbBridge.unbind("connection");
+
+    this.JsplumbBridge.bind("connectionMoved", (info, originalEvent) => {
+      var pe = this.objectProvider.findElement(info.newTargetEndpoint.elementId, this.rawPipelineModel);
+      var oldPe = this.objectProvider.findElement(info.originalTargetEndpoint.elementId, this.rawPipelineModel);
+      oldPe.payload.configured = false;
+      pe.payload.configured = false;
+    });
+
+    this.JsplumbBridge.bind("connectionDetached", (info, originalEvent) => {
+      var pe = this.objectProvider.findElement(info.targetEndpoint.elementId, this.rawPipelineModel);
+      pe.payload.configured = false;
+      pe.settings.openCustomize = true;
+      info.targetEndpoint.setType("empty");
+      this.validatePipeline();
+    });
+
+    this.JsplumbBridge.bind("connectionDrag", connection => {
+      this.JsplumbBridge.selectEndpoints().each(function (endpoint) {
+        if (endpoint.isTarget && endpoint.connections.length === 0) {
+          endpoint.setType("highlight");
+        }
+      });
+
+    });
+    this.JsplumbBridge.bind("connectionAborted", connection => {
+      this.JsplumbBridge.selectEndpoints().each(endpoint => {
+        if (endpoint.isTarget && endpoint.connections.length === 0) {
+          endpoint.setType("empty");
+        }
+      });
+    })
+
+    this.JsplumbBridge.bind("connection", (info, originalEvent) => {
+      var pe = this.objectProvider.findElement(info.target.id, this.rawPipelineModel);
+      if (pe.settings.openCustomize) {
+        this.currentPipelineModel = this.objectProvider.makePipeline(this.rawPipelineModel);
+        pe.settings.loadingStatus = true;
+        this.objectProvider.updatePipeline(this.currentPipelineModel)
+            .then(msg => {
+              let data = msg.data;
+              pe.settings.loadingStatus = false;
+              if (data.success) {
+                info.targetEndpoint.setType("token");
+                this.validatePipeline();
+                this.modifyPipeline(data.pipelineModifications);
+                var sourceEndpoint = this.JsplumbBridge.selectEndpoints({element: info.targetEndpoint.elementId});
+                if (this.PipelineEditorService.isFullyConnected(pe)) {
+                  if ((pe.payload.staticProperties && pe.payload.staticProperties.length > 0) || this.isCustomOutput(pe)) {
+                    this.EditorDialogManager.showCustomizeDialog($("#" +pe.payload.DOM), sourceEndpoint, pe.payload, false)
+                        .then(() => {
+                          this.JsplumbService.activateEndpoint(pe.payload.DOM, !pe.payload.uncompleted);
+                        }, () => {
+                          this.JsplumbService.activateEndpoint(pe.payload.DOM, !pe.payload.uncompleted);
+                        });
+                  } else {
+                    //this.$rootScope.$broadcast("SepaElementConfigured", pe.payload.DOM);
+                    pe.payload.configured = true;
+                  }
+                }
+              } else {
+                this.JsplumbBridge.detach(info.connection);
+                this.EditorDialogManager.showMatchingErrorDialog(data);
+              }
+            });
+      }
+    });
+
+    window.onresize = (event) => {
+      this.JsplumbBridge.repaintEverything();
+    };
+
+    setTimeout(() => {
+      this.plumbReady = true;
+    }, 100);
+  }
+
+  modifyPipeline(pipelineModifications) {
+    for (var i = 0, modification; modification = pipelineModifications[i]; i++) {
+      var id = modification.domId;
+      if (id !== "undefined") {
+        var pe = this.objectProvider.findElement(id, this.rawPipelineModel);
+        pe.payload.staticProperties = modification.staticProperties;
+        pe.payload.outputStrategies = modification.outputStrategies;
+        pe.payload.inputStreams = modification.inputStreams;
+      }
+    }
+  }
+
+  isCustomOutput(pe) {
+    var custom = false;
+    angular.forEach(pe.payload.outputStrategies, strategy => {
+      if (strategy.type == 'org.apache.streampipes.model.output.CustomOutputStrategy') {
+        custom = true;
+      }
+    });
+    return custom;
+  }
+
+  triggerPipelineCacheUpdate() {
+    this.pipelineCacheRunning = true;
+    this.RestApi.updateCachedPipeline(this.rawPipelineModel).then(msg => {
+      this.pipelineCacheRunning = false;
+      this.pipelineCached = true;
+    });
+  }
+
+
+}
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/constants/editor.constants.ts b/ui/src/app/editor-v2/constants/editor.constants.ts
new file mode 100644
index 0000000..8b5fc20
--- /dev/null
+++ b/ui/src/app/editor-v2/constants/editor.constants.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ *
+ */
+
+export class EditorConstants {
+
+  static readonly DATA_STREAM_IDENTIFIER = "org.apache.streampipes.model.SpDataStream";
+  static readonly DATA_SET_IDENTIFIER = "org.apache.streampipes.model.SpDataSet";
+  static readonly DATA_PROCESSOR_IDENTIFIER = "org.apache.streampipes.model.graph.DataProcessorInvocation";
+  static readonly DATA_SINK_IDENTIFIER = "org.apache.streampipes.model.graph.DataSinkInvoation";
+}
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/editor.component.html b/ui/src/app/editor-v2/editor.component.html
index 4fd7ad4..23f9e22 100644
--- a/ui/src/app/editor-v2/editor.component.html
+++ b/ui/src/app/editor-v2/editor.component.html
@@ -34,7 +34,7 @@
              style="background-color:#f6f6f6;padding:0px;border-bottom:1px solid #ffffff">
             <pipeline-element-icon-stand [activeType]="activeType"
                                          [currentElements]="currentElements"
-                                         element-filter="ctrl.elementFilter">
+                                         element-filter="ctrl.elementFilter" *ngIf="allElementsLoaded">
             </pipeline-element-icon-stand>
         </div>
         <pipeline-assembly [rawPipelineModel]="rawPipelineModel" [allElements]="allElements"
diff --git a/ui/src/app/editor-v2/editor.component.ts b/ui/src/app/editor-v2/editor.component.ts
index 4a8fb91..444ff62 100644
--- a/ui/src/app/editor-v2/editor.component.ts
+++ b/ui/src/app/editor-v2/editor.component.ts
@@ -21,11 +21,18 @@ import {EditorService} from "./services/editor.service";
 import {
     DataProcessorInvocation,
     DataSinkInvocation,
-    DataSourceDescription, SpDataSet,
+    DataSourceDescription,
     SpDataStream
 } from "../core-model/gen/streampipes-model";
 import {PipelineElementService} from "../platform-services/apis/pipeline-element.service";
-import {PipelineElementHolder} from "./model/editor.model";
+import {
+    PipelineElementConfig,
+    PipelineElementHolder,
+    PipelineElementType,
+    PipelineElementUnion
+} from "./model/editor.model";
+import {EditorConstants} from "./constants/editor.constants";
+import {PipelineElementTypeUtils} from "./utils/editor.utils";
 
 @Component({
     selector: 'editor',
@@ -34,40 +41,38 @@ import {PipelineElementHolder} from "./model/editor.model";
 })
 export class EditorComponent implements OnInit {
 
-    static readonly DATA_STREAM_IDENTIFIER = "org.apache.streampipes.model.SpDataStream";
-    static readonly DATA_SET_IDENTIFIER = "org.apache.streampipes.model.SpDataSet";
-    static readonly DATA_PROCESSOR_IDENTIFIER = "org.apache.streampipes.model.graph.DataProcessorInvocation";
-    static readonly DATA_SINK_IDENTIFIER = "org.apache.streampipes.model.graph.DataSinkInvoation";
-
     selectedIndex: number = 1;
-    activeType: string = EditorComponent.DATA_STREAM_IDENTIFIER;
+    activeType: PipelineElementType = PipelineElementType.DataStream;
 
     availableDataStreams: SpDataStream[] = [];
     availableDataProcessors: DataProcessorInvocation[] = [];
     availableDataSinks: DataSinkInvocation[] = [];
 
-    allElements: PipelineElementHolder[] = [];
-    currentElements: (SpDataStream | DataProcessorInvocation | DataSinkInvocation)[] = [];
+    allElements: PipelineElementUnion[] = [];
+    currentElements: Array<(SpDataStream | DataProcessorInvocation | DataSinkInvocation)> = [];
 
-    rawPipelineModel: any = [];
+    rawPipelineModel: PipelineElementConfig[] = [];
     currentModifiedPipelineId: any;
 
+    elementsLoaded = [false, false, false];
+    allElementsLoaded: boolean = false;
+
     tabs = [
         {
             title: 'Data Sets',
-            type: EditorComponent.DATA_SET_IDENTIFIER
+            type: PipelineElementType.DataSet
         },
         {
             title: 'Data Streams',
-            type: EditorComponent.DATA_STREAM_IDENTIFIER
+            type: PipelineElementType.DataStream
         },
         {
             title: 'Data Processors',
-            type: EditorComponent.DATA_PROCESSOR_IDENTIFIER
+            type: PipelineElementType.DataProcessor
         },
         {
             title: 'Data Sinks',
-            type: EditorComponent.DATA_SINK_IDENTIFIER
+            type: PipelineElementType.DataSink
         }
     ];
 
@@ -78,23 +83,31 @@ export class EditorComponent implements OnInit {
     ngOnInit() {
         this.pipelineElementService.getDataProcessors().subscribe(processors => {
             this.availableDataProcessors = processors;
-            this.allElements[EditorComponent.DATA_PROCESSOR_IDENTIFIER] = this.availableDataProcessors;
+            this.allElements = this.allElements.concat(processors);
+            this.afterPipelineElementLoaded(0);
         });
         this.pipelineElementService.getDataSources().subscribe(sources => {
             this.availableDataStreams = this.collectStreams(sources);
-            this.allElements[EditorComponent.DATA_STREAM_IDENTIFIER] = this.availableDataStreams
-                .filter(ds => ds["@class"] == EditorComponent.DATA_STREAM_IDENTIFIER );
-            this.allElements[EditorComponent.DATA_SET_IDENTIFIER] = this.availableDataStreams
-                .filter(ds => ds["@class"] == EditorComponent.DATA_SET_IDENTIFIER );
-            this.currentElements = this.allElements[EditorComponent.DATA_STREAM_IDENTIFIER];
+            this.allElements = this.allElements.concat(this.availableDataStreams);
+            this.selectPipelineElements(1);
+            this.afterPipelineElementLoaded(1);
         });
         this.pipelineElementService.getDataSinks().subscribe(sinks => {
             this.availableDataSinks = sinks;
-            this.allElements[EditorComponent.DATA_SINK_IDENTIFIER] = this.availableDataSinks;
+            this.allElements = this.allElements.concat(this.availableDataSinks);
+            this.afterPipelineElementLoaded(2);
         })
 
     }
 
+    afterPipelineElementLoaded(index: number) {
+        this.elementsLoaded[index] = true;
+        if (this.elementsLoaded.every(e => e === true)) {
+            this.allElementsLoaded = true;
+            //this.makeDraggable();
+        }
+    }
+
     private collectStreams(sources: Array<DataSourceDescription>) {
         let streams: SpDataStream[] = [];
         sources.forEach(source => {
@@ -108,24 +121,10 @@ export class EditorComponent implements OnInit {
     selectPipelineElements(index : number) {
         this.selectedIndex = index;
         this.activeType = this.tabs[index].type;
-        this.currentElements = this.allElements[this.activeType];
-        this.makeDraggable();
+        this.currentElements = this.allElements
+            .filter(pe => pe instanceof PipelineElementTypeUtils.toType(this.activeType));
+        console.log(this.currentElements);
     }
 
-    makeDraggable() {
-        console.log("makuing drabbg");
-        (<any>$('.draggable-icon')).draggable({
-            revert: 'invalid',
-            helper: 'clone',
-            stack: '.draggable-icon',
-            start: function (el, ui) {
-                ui.helper.appendTo('#content');
-                $('#outerAssemblyArea').css('border', '3px dashed #39b54a');
-            },
-            stop: function (el, ui) {
-                $('#outerAssemblyArea').css('border', '3px solid rgb(156, 156, 156)');
-            }
-        });
-    };
 
 }
diff --git a/ui/src/app/editor-v2/editor.module.ts b/ui/src/app/editor-v2/editor.module.ts
index 54b1bae..8d3a5b4 100644
--- a/ui/src/app/editor-v2/editor.module.ts
+++ b/ui/src/app/editor-v2/editor.module.ts
@@ -40,6 +40,9 @@ import {JsplumbConfigService} from "./services/jsplumb-config.service";
 import {PipelineEditorService} from "./services/pipeline-editor.service";
 import {PipelineValidationService} from "./services/pipeline-validation.service";
 import {DragAndDropModule} from "angular-draggable-droppable";
+import {PipelineComponent} from "./components/pipeline/pipeline.component";
+import {DragDropModule} from "@angular/cdk/drag-drop";
+import {DragulaModule} from "ng2-dragula";
 
 
 @NgModule({
@@ -53,13 +56,16 @@ import {DragAndDropModule} from "angular-draggable-droppable";
         CustomMaterialModule,
         FormsModule,
         ConnectModule,
-        DragAndDropModule
+        DragAndDropModule,
+        DragDropModule,
+        DragulaModule
     ],
     declarations: [
         EditorComponent,
         PipelineAssemblyComponent,
         PipelineElementIconStandComponent,
-        PipelineElementComponent
+        PipelineElementComponent,
+        PipelineComponent
     ],
     providers: [
         EditorService,
diff --git a/ui/src/app/editor-v2/model/editor.model.ts b/ui/src/app/editor-v2/model/editor.model.ts
index 4662d0c..fb302f4 100644
--- a/ui/src/app/editor-v2/model/editor.model.ts
+++ b/ui/src/app/editor-v2/model/editor.model.ts
@@ -1,9 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 {
   DataProcessorInvocation,
-  DataSinkInvocation,
+  DataSinkInvocation, SpDataSet,
   SpDataStream
 } from "../../core-model/gen/streampipes-model";
+import {EditorConstants} from "../constants/editor.constants";
+
+export type PipelineElementHolder = {
+  [key: string]: Array<PipelineElementUnion>;
+};
+
+export interface PipelineElementConfig {
+  type: string,
+  settings: {
+    openCustomize: boolean,
+    preview: boolean,
+    displaySettings: string,
+    connectable: string,
+    disabled: boolean,
+    loadingStatus: boolean
+    position: {
+      x: number,
+      y: number
+    }
+  },
+  payload: PipelineElementUnion
+}
+
+export enum PipelineElementType {
+  DataSet,
+  DataStream,
+  DataProcessor,
+  DataSink
+}
 
-export interface PipelineElementHolder {
-  [key: string]: (SpDataStream | DataProcessorInvocation | DataSinkInvocation);
-}
\ No newline at end of file
+export type PipelineElementUnion = SpDataSet | SpDataStream | DataProcessorInvocation | DataSinkInvocation;
\ No newline at end of file
diff --git a/ui/src/app/editor-v2/services/jsplumb-bridge.service.ts b/ui/src/app/editor-v2/services/jsplumb-bridge.service.ts
index 82631d1..3b64e88 100644
--- a/ui/src/app/editor-v2/services/jsplumb-bridge.service.ts
+++ b/ui/src/app/editor-v2/services/jsplumb-bridge.service.ts
@@ -39,6 +39,8 @@ export class JsplumbBridge {
     }
 
     setContainer(container) {
+        console.log("container");
+        console.log(container);
         jsPlumb.setContainer(container);
     }
 
diff --git a/ui/src/app/editor-v2/services/jsplumb.service.ts b/ui/src/app/editor-v2/services/jsplumb.service.ts
index 45a7277..943e70e 100644
--- a/ui/src/app/editor-v2/services/jsplumb.service.ts
+++ b/ui/src/app/editor-v2/services/jsplumb.service.ts
@@ -19,6 +19,8 @@
 import {JsplumbConfigService} from "./jsplumb-config.service";
 import {JsplumbBridge} from "./jsplumb-bridge.service";
 import {Inject, Injectable} from "@angular/core";
+import {PipelineElementConfig, PipelineElementUnion} from "../model/editor.model";
+import {PipelineElementTypeUtils} from "../utils/editor.utils";
 
 @Injectable()
 export class JsplumbService {
@@ -88,7 +90,7 @@ export class JsplumbService {
         var pipelineElementConfig = this.createNewPipelineElementConfigWithFixedCoordinates(pipelineElementDom, pipelineElement, false);
         pipelineModel.push(pipelineElementConfig);
         this.$timeout(() => {
-            this.createAssemblyElement(pipelineElementConfig.payload.DOM, pipelineElementConfig.payload, pipelineElementDom);
+            this.createAssemblyElement(pipelineElementConfig.payload.dom, pipelineElementConfig.payload, pipelineElementDom);
         });
     }
 
@@ -136,23 +138,27 @@ export class JsplumbService {
         return this.createNewPipelineElementConfig(json, coord, isPreview);
     }
 
-    createNewPipelineElementConfig(json, coordinates, isPreview) {
-        var displaySettings = isPreview ? 'connectable-preview' : 'connectable-editor';
-        var connectable = "connectable";
-        var pipelineElementConfig = {
-            type: json.type, settings: {
-                openCustomize: !json.configured,
-                preview: isPreview,
-                displaySettings: displaySettings,
-                connectable: connectable,
-                position: {
-                    x: coordinates.x,
-                    y: coordinates.y
-                }
-            }, payload: Object.assign({}, json)
-        };
-        if (!pipelineElementConfig.payload.DOM) {
-            pipelineElementConfig.payload.DOM = "jsplumb_" + this.idCounter + "_" + this.makeId(4);
+    createNewPipelineElementConfig(pipelineElement: PipelineElementUnion,
+                                   coordinates,
+                                   isPreview: boolean): PipelineElementConfig {
+        let displaySettings = isPreview ? 'connectable-preview' : 'connectable-editor';
+        let connectable = "connectable";
+        let pipelineElementConfig = {} as PipelineElementConfig;
+        pipelineElementConfig.type = PipelineElementTypeUtils
+            .toCssShortHand(PipelineElementTypeUtils.fromType(pipelineElement))
+        pipelineElementConfig.payload = pipelineElement;
+        pipelineElementConfig.settings = {connectable: connectable,
+            openCustomize: (pipelineElement as any).configured,
+            preview: isPreview,
+            disabled: false,
+            loadingStatus: false,
+            displaySettings: displaySettings,
+            position: {
+                x: coordinates.x,
+                y: coordinates.y
+            }};
+        if (!pipelineElementConfig.payload.dom) {
+            pipelineElementConfig.payload.dom = "jsplumb_" + this.idCounter + "_" + this.makeId(4);
             this.idCounter++;
         }
 
diff --git a/ui/src/app/editor-v2/services/pipeline-editor.service.ts b/ui/src/app/editor-v2/services/pipeline-editor.service.ts
index 6d142a3..2d2b3a3 100644
--- a/ui/src/app/editor-v2/services/pipeline-editor.service.ts
+++ b/ui/src/app/editor-v2/services/pipeline-editor.service.ts
@@ -17,14 +17,12 @@
  */
 
 import {Injectable} from "@angular/core";
+import {JsplumbBridge} from "./jsplumb-bridge.service";
 
 @Injectable()
 export class PipelineEditorService {
 
-    JsplumbBridge: any;
-
-    constructor(JsplumbBridge) {
-        this.JsplumbBridge = JsplumbBridge;
+    constructor(private JsplumbBridge: JsplumbBridge) {
     }
 
     getCoordinates(ui, currentZoomLevel) {
diff --git a/ui/src/app/editor-v2/utils/editor.utils.ts b/ui/src/app/editor-v2/utils/editor.utils.ts
new file mode 100644
index 0000000..cae04e5
--- /dev/null
+++ b/ui/src/app/editor-v2/utils/editor.utils.ts
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 {EditorConstants} from "../constants/editor.constants";
+import {PipelineElementType, PipelineElementUnion} from "../model/editor.model";
+import {
+  DataProcessorInvocation,
+  DataSinkInvocation,
+  SpDataSet,
+  SpDataStream
+} from "../../core-model/gen/streampipes-model";
+
+export class PipelineElementTypeUtils {
+
+  static toClassName(element: PipelineElementType): string {
+    if (element === PipelineElementType.DataSet) {
+      return EditorConstants.DATA_SET_IDENTIFIER;
+    } else if (element === PipelineElementType.DataStream) {
+      return EditorConstants.DATA_STREAM_IDENTIFIER;
+    } else if (element === PipelineElementType.DataProcessor) {
+      return EditorConstants.DATA_PROCESSOR_IDENTIFIER;
+    } else {
+      return EditorConstants.DATA_SINK_IDENTIFIER;
+    }
+  }
+
+  static fromClassName(className: string): PipelineElementType {
+    if (className === EditorConstants.DATA_SET_IDENTIFIER) {
+      return PipelineElementType.DataSet;
+    } else if (className === EditorConstants.DATA_STREAM_IDENTIFIER) {
+      return PipelineElementType.DataStream;
+    } else if (className === EditorConstants.DATA_PROCESSOR_IDENTIFIER) {
+      return PipelineElementType.DataProcessor;
+    } else {
+      return PipelineElementType.DataSink;
+    }
+  }
+
+  static toCssShortHand(elementType: PipelineElementType) {
+    if (PipelineElementType.DataStream === elementType) {
+      return "stream";
+    } else if (PipelineElementType.DataSet === elementType) {
+      return "set";
+    } else if (PipelineElementType.DataProcessor === elementType) {
+      return "sepa";
+    } else {
+      return "action";
+    }
+  }
+
+  static fromType(pipelineElement: PipelineElementUnion) {
+    if (pipelineElement instanceof SpDataSet) {
+      return PipelineElementType.DataSet;
+    } else if (pipelineElement instanceof SpDataStream) {
+      return PipelineElementType.DataStream;
+    } else if (pipelineElement instanceof DataProcessorInvocation) {
+      return PipelineElementType.DataProcessor;
+    } else {
+      return PipelineElementType.DataSink;
+    }
+  }
+
+  static toType(elementType: PipelineElementType) {
+    if (PipelineElementType.DataStream === elementType) {
+      return SpDataStream;
+    } else if (PipelineElementType.DataSet === elementType) {
+      return SpDataSet;
+    } else if (PipelineElementType.DataProcessor === elementType) {
+      return DataProcessorInvocation;
+    } else {
+      return DataSinkInvocation;
+    }
+  }
+
+  static toString(pipelineElement: PipelineElementType): string {
+    return PipelineElementType[pipelineElement];
+  }
+
+  static parse(pipelineElement: string): PipelineElementType {
+    return PipelineElementType[pipelineElement];
+  }
+}
\ No newline at end of file