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 2021/05/24 21:19:11 UTC

[incubator-streampipes] branch dev updated (db0c2d4 -> bd07903)

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

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


    from db0c2d4  [hotfix] Remove failed pipeline element invocations from running instance map
     new 5dcd9ef  [hotfix] Do not change original pipeline name in save dialog
     new bd07903  [STREAMPIPES-375] Persist position of pipeline elements on pipeline canvas

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../backend/StreamPipesResourceConfig.java         |   2 +
 .../streampipes/model/canvas/CanvasPosition.java   |  25 +++--
 .../model/canvas/PipelineCanvasComment.java        |  31 ++++--
 .../PipelineCanvasMetadata.java}                   |  63 +++++++-----
 .../model/canvas/PipelineElementMetadata.java      |  30 +++---
 .../rest/impl/AbstractRestResource.java            |  10 --
 ...Cache.java => PipelineCanvasMetadataCache.java} |  22 ++---
 .../rest/impl/PipelineCanvasMetadataResource.java  | 106 +++++++++++++++++++++
 .../streampipes/storage/api/INoSqlStorage.java     |   2 +
 ...ge.java => IPipelineCanvasMetadataStorage.java} |  14 +--
 .../storage/couchdb/CouchDbStorageManager.java     |   5 +-
 ...java => PipelineCanvasMetadataStorageImpl.java} |  38 ++++----
 .../streampipes/storage/couchdb/utils/Utils.java   |   4 +
 ui/src/app/core-model/gen/streampipes-model.ts     |  75 ++++++++++++++-
 .../pipeline-assembly.component.html               |   1 +
 .../pipeline-assembly.component.ts                 |  60 +++++++++---
 .../components/pipeline/pipeline.component.ts      |  48 +++++-----
 .../save-pipeline/save-pipeline.component.html     |   4 +-
 .../save-pipeline/save-pipeline.component.ts       |  39 ++++++--
 ui/src/app/editor/services/editor.service.ts       |  23 ++++-
 .../services/pipeline-positioning.service.ts       |  76 ++++++++++++---
 .../apis/pipeline-canvas-metadata.service.ts       |  65 +++++++++++++
 ui/src/app/platform-services/platform.module.ts    |   2 +
 23 files changed, 574 insertions(+), 171 deletions(-)
 copy streampipes-data-explorer/src/main/java/org/apache/streampipes/dataexplorer/param/QueryParams.java => streampipes-model/src/main/java/org/apache/streampipes/model/canvas/CanvasPosition.java (72%)
 copy ui/src/app/core-ui/image/components/image-container/image-container.component.css => streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineCanvasComment.java (65%)
 copy streampipes-model/src/main/java/org/apache/streampipes/model/{dashboard/DashboardEntity.java => canvas/PipelineCanvasMetadata.java} (55%)
 copy streampipes-dataformat-fst/src/main/java/org/apache/streampipes/dataformat/fst/FstDataFormatFactory.java => streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineElementMetadata.java (65%)
 copy streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/{PipelineCache.java => PipelineCanvasMetadataCache.java} (63%)
 create mode 100644 streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataResource.java
 copy streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/{CRUDStorage.java => IPipelineCanvasMetadataStorage.java} (77%)
 copy streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/{PipelineElementTemplateStorageImpl.java => PipelineCanvasMetadataStorageImpl.java} (50%)
 create mode 100644 ui/src/app/platform-services/apis/pipeline-canvas-metadata.service.ts

[incubator-streampipes] 02/02: [STREAMPIPES-375] Persist position of pipeline elements on pipeline canvas

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit bd07903ab31a033934c118b7b13c472429c31fe4
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Mon May 24 23:18:58 2021 +0200

    [STREAMPIPES-375] Persist position of pipeline elements on pipeline canvas
---
 .../backend/StreamPipesResourceConfig.java         |   2 +
 .../streampipes/model/canvas/CanvasPosition.java   |  43 +++++++++
 .../model/canvas/PipelineCanvasComment.java        |  43 +++++++++
 .../model/canvas/PipelineCanvasMetadata.java       |  82 ++++++++++++++++
 .../model/canvas/PipelineElementMetadata.java      |  43 +++++++++
 .../rest/impl/AbstractRestResource.java            |  10 --
 .../rest/impl/PipelineCanvasMetadataCache.java     |  54 +++++++++++
 .../rest/impl/PipelineCanvasMetadataResource.java  | 106 +++++++++++++++++++++
 .../streampipes/storage/api/INoSqlStorage.java     |   2 +
 .../api/IPipelineCanvasMetadataStorage.java        |  25 +++++
 .../storage/couchdb/CouchDbStorageManager.java     |   5 +-
 .../impl/PipelineCanvasMetadataStorageImpl.java    |  69 ++++++++++++++
 .../streampipes/storage/couchdb/utils/Utils.java   |   4 +
 ui/src/app/core-model/gen/streampipes-model.ts     |  75 ++++++++++++++-
 .../pipeline-assembly.component.html               |   1 +
 .../pipeline-assembly.component.ts                 |  60 +++++++++---
 .../components/pipeline/pipeline.component.ts      |  48 +++++-----
 .../save-pipeline/save-pipeline.component.ts       |  30 +++++-
 ui/src/app/editor/services/editor.service.ts       |  23 ++++-
 .../services/pipeline-positioning.service.ts       |  76 ++++++++++++---
 .../apis/pipeline-canvas-metadata.service.ts       |  65 +++++++++++++
 ui/src/app/platform-services/platform.module.ts    |   2 +
 22 files changed, 794 insertions(+), 74 deletions(-)

diff --git a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesResourceConfig.java b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesResourceConfig.java
index e6d9521..f2fd784 100644
--- a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesResourceConfig.java
+++ b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesResourceConfig.java
@@ -70,6 +70,8 @@ public class StreamPipesResourceConfig extends ResourceConfig {
     register(OntologyKnowledge.class);
     register(OntologyMeasurementUnit.class);
     register(OntologyPipelineElement.class);
+    register(PipelineCanvasMetadataCache.class);
+    register(PipelineCanvasMetadataResource.class);
     register(PipelineCache.class);
     register(PipelineCategory.class);
     register(PipelineElementAsset.class);
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/CanvasPosition.java b/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/CanvasPosition.java
new file mode 100644
index 0000000..bb94bf2
--- /dev/null
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/CanvasPosition.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ *
+ */
+package org.apache.streampipes.model.canvas;
+
+public class CanvasPosition {
+
+  private float x;
+  private float y;
+
+  public CanvasPosition() {
+  }
+
+  public float getX() {
+    return x;
+  }
+
+  public void setX(float x) {
+    this.x = x;
+  }
+
+  public float getY() {
+    return y;
+  }
+
+  public void setY(float y) {
+    this.y = y;
+  }
+}
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineCanvasComment.java b/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineCanvasComment.java
new file mode 100644
index 0000000..620e0b6
--- /dev/null
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineCanvasComment.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ *
+ */
+package org.apache.streampipes.model.canvas;
+
+public class PipelineCanvasComment {
+
+  private String comment;
+  private CanvasPosition position;
+
+  public PipelineCanvasComment() {
+  }
+
+  public String getComment() {
+    return comment;
+  }
+
+  public void setComment(String comment) {
+    this.comment = comment;
+  }
+
+  public CanvasPosition getPosition() {
+    return position;
+  }
+
+  public void setPosition(CanvasPosition position) {
+    this.position = position;
+  }
+}
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineCanvasMetadata.java b/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineCanvasMetadata.java
new file mode 100644
index 0000000..d22c9be
--- /dev/null
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineCanvasMetadata.java
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ *
+ */
+package org.apache.streampipes.model.canvas;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
+import org.apache.streampipes.model.shared.annotation.TsModel;
+
+import java.util.List;
+import java.util.Map;
+
+@TsModel
+public class PipelineCanvasMetadata {
+
+  @JsonProperty("_id")
+  private @SerializedName("_id") String id;
+
+  @JsonProperty("_rev")
+  private @SerializedName("_rev") String rev;
+
+  private String pipelineId;
+  private Map<String, PipelineElementMetadata> pipelineElementMetadata;
+  private List<PipelineCanvasComment> comments;
+
+  public PipelineCanvasMetadata() {
+  }
+
+  public String getPipelineId() {
+    return pipelineId;
+  }
+
+  public void setPipelineId(String pipelineId) {
+    this.pipelineId = pipelineId;
+  }
+
+  public Map<String, PipelineElementMetadata> getPipelineElementMetadata() {
+    return pipelineElementMetadata;
+  }
+
+  public void setPipelineElementMetadata(Map<String, PipelineElementMetadata> pipelineElementMetadata) {
+    this.pipelineElementMetadata = pipelineElementMetadata;
+  }
+
+  public List<PipelineCanvasComment> getComments() {
+    return comments;
+  }
+
+  public void setComments(List<PipelineCanvasComment> comments) {
+    this.comments = comments;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public String getRev() {
+    return rev;
+  }
+
+  public void setRev(String rev) {
+    this.rev = rev;
+  }
+}
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineElementMetadata.java b/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineElementMetadata.java
new file mode 100644
index 0000000..42012d6
--- /dev/null
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/canvas/PipelineElementMetadata.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ *
+ */
+package org.apache.streampipes.model.canvas;
+
+public class PipelineElementMetadata {
+
+  private CanvasPosition position;
+  private String customName;
+
+  public PipelineElementMetadata() {
+  }
+
+  public CanvasPosition getPosition() {
+    return position;
+  }
+
+  public void setPosition(CanvasPosition position) {
+    this.position = position;
+  }
+
+  public String getCustomName() {
+    return customName;
+  }
+
+  public void setCustomName(String customName) {
+    this.customName = customName;
+  }
+}
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AbstractRestResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AbstractRestResource.java
index a40ed88..6680401 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AbstractRestResource.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AbstractRestResource.java
@@ -59,16 +59,6 @@ public abstract class AbstractRestResource extends AbstractSharedRestInterface {
     }
   }
 
-  protected <T> String toJsonLd(String rootElementUri, T object) {
-    try {
-      return JsonLdUtils.asString(new JsonLdTransformer(rootElementUri).toJsonLd(object));
-    } catch (IllegalAccessException | InvocationTargetException | InvalidRdfException | ClassNotFoundException e) {
-      return toJson(constructErrorMessage(new Notification(NotificationType.UNKNOWN_ERROR.title(),
-              NotificationType.UNKNOWN_ERROR.description(),
-              e.getMessage())));
-    }
-  }
-
   protected IPipelineElementDescriptionStorageCache getPipelineElementRdfStorage() {
     return StorageManager.INSTANCE.getPipelineElementStorage();
   }
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataCache.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataCache.java
new file mode 100644
index 0000000..83e8b52
--- /dev/null
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataCache.java
@@ -0,0 +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.
+ *
+ */
+package org.apache.streampipes.rest.impl;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Path("/v2/users/{username}/pipeline-canvas-cache")
+public class PipelineCanvasMetadataCache extends AbstractRestResource {
+
+  private static ConcurrentHashMap<String, String> cachedCanvasMetadata = new ConcurrentHashMap<>();
+
+  @POST
+  @Produces(MediaType.APPLICATION_JSON)
+  public Response updateCachedCanvasMetadata(@PathParam("username") String user,
+                                             String canvasMetadata) {
+    cachedCanvasMetadata.put(user, canvasMetadata);
+    return ok();
+  }
+
+  @GET
+  @Produces(MediaType.APPLICATION_JSON)
+  public Response getCachedCanvasMetadata(@PathParam("username") String user) {
+    if (cachedCanvasMetadata.containsKey(user)) {
+      return ok(cachedCanvasMetadata.get(user));
+    } else {
+      return ok();
+    }
+  }
+
+  @DELETE
+  @Produces(MediaType.APPLICATION_JSON)
+  public Response removeCanvasMetadataFromCache(@PathParam("username") String user) {
+    cachedCanvasMetadata.remove(user);
+    return ok();
+  }
+}
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataResource.java
new file mode 100644
index 0000000..0aaaa00
--- /dev/null
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineCanvasMetadataResource.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ *
+ */
+package org.apache.streampipes.rest.impl;
+
+import org.apache.streampipes.model.canvas.PipelineCanvasMetadata;
+import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
+import org.apache.streampipes.storage.api.IPipelineCanvasMetadataStorage;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+@Path("/v2/users/{username}/pipeline-canvas-metadata")
+public class PipelineCanvasMetadataResource extends AbstractRestResource {
+
+  @GET
+  @Path("/pipeline/{pipelineId}")
+  @Produces(MediaType.APPLICATION_JSON)
+  @JacksonSerialized
+  public Response getPipelineCanvasMetadataForPipeline(@PathParam("pipelineId") String pipelineId) {
+    try {
+      return ok(getPipelineCanvasMetadataStorage()
+              .getPipelineCanvasMetadataForPipeline(pipelineId));
+    } catch (IllegalArgumentException e) {
+      return badRequest();
+    }
+  }
+
+  @GET
+  @Path("{canvasId}")
+  @Produces(MediaType.APPLICATION_JSON)
+  @JacksonSerialized
+  public Response getPipelineCanvasMetadata(@PathParam("canvasId") String pipelineCanvasId) {
+    try {
+      return ok(getPipelineCanvasMetadataStorage()
+              .getElementById(pipelineCanvasId));
+    } catch (IllegalArgumentException e) {
+      return badRequest();
+    }
+  }
+
+  @POST
+  @Produces(MediaType.APPLICATION_JSON)
+  @JacksonSerialized
+  public Response storePipelineCanvasMetadata(PipelineCanvasMetadata pipelineCanvasMetadata) {
+    getPipelineCanvasMetadataStorage().createElement(pipelineCanvasMetadata);
+    return ok();
+  }
+
+  @DELETE
+  @Produces(MediaType.APPLICATION_JSON)
+  @Path("{canvasId}")
+  @JacksonSerialized
+  public Response deletePipelineCanvasMetadata(@PathParam("canvasId") String pipelineCanvasId) {
+    PipelineCanvasMetadata metadata = find(pipelineCanvasId);
+    getPipelineCanvasMetadataStorage().deleteElement(metadata);
+    return ok();
+  }
+
+  @DELETE
+  @Produces(MediaType.APPLICATION_JSON)
+  @Path("/pipeline/{pipelineId}")
+  @JacksonSerialized
+  public Response deletePipelineCanvasMetadataForPipeline(@PathParam("pipelineId") String pipelineId) {
+    PipelineCanvasMetadata metadata = getPipelineCanvasMetadataStorage().getPipelineCanvasMetadataForPipeline(pipelineId);
+    getPipelineCanvasMetadataStorage().deleteElement(metadata);
+    return ok();
+  }
+
+  @PUT
+  @Produces(MediaType.APPLICATION_JSON)
+  @Path("{canvasId}")
+  @JacksonSerialized
+  public Response updatePipelineCanvasMetadata(@PathParam("canvasId") String pipelineCanvasId,
+                                               PipelineCanvasMetadata pipelineCanvasMetadata) {
+    try {
+      getPipelineCanvasMetadataStorage().updateElement(pipelineCanvasMetadata);
+    } catch (IllegalArgumentException e) {
+      getPipelineCanvasMetadataStorage().createElement(pipelineCanvasMetadata);
+    }
+    return ok();
+  }
+
+  private PipelineCanvasMetadata find(String canvasId) {
+    return getPipelineCanvasMetadataStorage().getElementById(canvasId);
+  }
+
+  private IPipelineCanvasMetadataStorage getPipelineCanvasMetadataStorage() {
+    return getNoSqlStorage().getPipelineCanvasMetadataStorage();
+  }
+}
diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java
index 4f1c176..806735b 100644
--- a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/INoSqlStorage.java
@@ -59,4 +59,6 @@ public interface INoSqlStorage {
 
   IPipelineElementTemplateStorage getPipelineElementTemplateStorage();
 
+  IPipelineCanvasMetadataStorage getPipelineCanvasMetadataStorage();
+
 }
diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IPipelineCanvasMetadataStorage.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IPipelineCanvasMetadataStorage.java
new file mode 100644
index 0000000..fa7c647
--- /dev/null
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IPipelineCanvasMetadataStorage.java
@@ -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.
+ *
+ */
+package org.apache.streampipes.storage.api;
+
+import org.apache.streampipes.model.canvas.PipelineCanvasMetadata;
+
+public interface IPipelineCanvasMetadataStorage extends CRUDStorage<String, PipelineCanvasMetadata> {
+
+  PipelineCanvasMetadata getPipelineCanvasMetadataForPipeline(String pipelineId);
+}
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java
index 1bcd490..dce977f 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/CouchDbStorageManager.java
@@ -120,5 +120,8 @@ public enum CouchDbStorageManager implements INoSqlStorage {
     return new PipelineElementTemplateStorageImpl();
   }
 
-
+  @Override
+  public IPipelineCanvasMetadataStorage getPipelineCanvasMetadataStorage() {
+    return new PipelineCanvasMetadataStorageImpl();
+  }
 }
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/PipelineCanvasMetadataStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/PipelineCanvasMetadataStorageImpl.java
new file mode 100644
index 0000000..be9f77d
--- /dev/null
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/PipelineCanvasMetadataStorageImpl.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ *
+ */
+package org.apache.streampipes.storage.couchdb.impl;
+
+import org.apache.streampipes.model.canvas.PipelineCanvasMetadata;
+import org.apache.streampipes.storage.api.IPipelineCanvasMetadataStorage;
+import org.apache.streampipes.storage.couchdb.dao.AbstractDao;
+import org.apache.streampipes.storage.couchdb.utils.Utils;
+
+import java.util.List;
+
+public class PipelineCanvasMetadataStorageImpl extends AbstractDao<PipelineCanvasMetadata>
+        implements IPipelineCanvasMetadataStorage {
+
+  public PipelineCanvasMetadataStorageImpl() {
+    super(Utils::getCouchDbPipelineCanvasMetadataClient, PipelineCanvasMetadata.class);
+  }
+
+  @Override
+  public List<PipelineCanvasMetadata> getAll() {
+    return findAll();
+  }
+
+  @Override
+  public void createElement(PipelineCanvasMetadata element) {
+    persist(element);
+  }
+
+  @Override
+  public PipelineCanvasMetadata getElementById(String id) {
+    return find(id).orElseThrow(IllegalArgumentException::new);
+  }
+
+  @Override
+  public PipelineCanvasMetadata updateElement(PipelineCanvasMetadata element) {
+    update(element);
+    return find(element.getId()).orElseThrow(IllegalAccessError::new);
+  }
+
+  @Override
+  public void deleteElement(PipelineCanvasMetadata element) {
+    delete(element.getId());
+  }
+
+  @Override
+  public PipelineCanvasMetadata getPipelineCanvasMetadataForPipeline(String pipelineId) {
+    // TODO add CouchDB view
+    return findAll()
+            .stream()
+            .filter(p -> p.getPipelineId().equals(pipelineId))
+            .findFirst()
+            .orElseThrow(IllegalArgumentException::new);
+  }
+}
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
index f81a084..2679d0d 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
@@ -28,6 +28,10 @@ public class Utils {
     return getCouchDbGsonClient("pipelineelementtemplate");
   }
 
+  public static CouchDbClient getCouchDbPipelineCanvasMetadataClient() {
+    return getCouchDbGsonClient("pipelinecanvasmetadata");
+  }
+
   public static CouchDbClient getCouchDbCategoryClient() {
     return getCouchDbGsonClient("category");
   }
diff --git a/ui/src/app/core-model/gen/streampipes-model.ts b/ui/src/app/core-model/gen/streampipes-model.ts
index 0fc0532..4fa3957 100644
--- a/ui/src/app/core-model/gen/streampipes-model.ts
+++ b/ui/src/app/core-model/gen/streampipes-model.ts
@@ -16,11 +16,10 @@
  *
  */
 
-
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 2.27.744 on 2021-05-22 23:26:12.
+// Generated using typescript-generator version 2.27.744 on 2021-05-24 18:53:06.
 
 export class AbstractStreamPipesEntity {
     "@class": "org.apache.streampipes.model.base.AbstractStreamPipesEntity" | "org.apache.streampipes.model.base.NamedStreamPipesEntity" | "org.apache.streampipes.model.connect.adapter.AdapterDescription" | "org.apache.streampipes.model.connect.adapter.AdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.GenericAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.SpecificAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.AdapterStre [...]
@@ -152,8 +151,8 @@ export class NamedStreamPipesEntity extends AbstractStreamPipesEntity {
         instance.applicationLinks = __getCopyArrayFn(ApplicationLink.fromData)(data.applicationLinks);
         instance.internallyManaged = data.internallyManaged;
         instance.connectedTo = __getCopyArrayFn(__identity<string>())(data.connectedTo);
-        instance.uri = data.uri;
         instance.dom = data.dom;
+        instance.uri = data.uri;
         return instance;
     }
 }
@@ -190,9 +189,9 @@ export class AdapterDescription extends NamedStreamPipesEntity {
         instance.rules = __getCopyArrayFn(TransformationRuleDescription.fromDataUnion)(data.rules);
         instance.category = __getCopyArrayFn(__identity<string>())(data.category);
         instance.createdAt = data.createdAt;
-        instance.streamRules = __getCopyArrayFn(__identity<any>())(data.streamRules);
-        instance.schemaRules = __getCopyArrayFn(__identity<any>())(data.schemaRules);
         instance.valueRules = __getCopyArrayFn(__identity<any>())(data.valueRules);
+        instance.schemaRules = __getCopyArrayFn(__identity<any>())(data.schemaRules);
+        instance.streamRules = __getCopyArrayFn(__identity<any>())(data.streamRules);
         instance.couchDBId = data.couchDBId;
         instance._rev = data._rev;
         return instance;
@@ -601,6 +600,21 @@ export class ApplicationLink extends UnnamedStreamPipesEntity {
     }
 }
 
+export class CanvasPosition {
+    x: number;
+    y: number;
+
+    static fromData(data: CanvasPosition, target?: CanvasPosition): CanvasPosition {
+        if (!data) {
+            return data;
+        }
+        const instance = target || new CanvasPosition();
+        instance.x = data.x;
+        instance.y = data.y;
+        return instance;
+    }
+}
+
 export class Category {
     _id: string;
     _rev: string;
@@ -2077,6 +2091,42 @@ export class Pipeline extends ElementComposition {
     }
 }
 
+export class PipelineCanvasComment {
+    comment: string;
+    position: CanvasPosition;
+
+    static fromData(data: PipelineCanvasComment, target?: PipelineCanvasComment): PipelineCanvasComment {
+        if (!data) {
+            return data;
+        }
+        const instance = target || new PipelineCanvasComment();
+        instance.comment = data.comment;
+        instance.position = CanvasPosition.fromData(data.position);
+        return instance;
+    }
+}
+
+export class PipelineCanvasMetadata {
+    _id: string;
+    _rev: string;
+    comments: PipelineCanvasComment[];
+    pipelineElementMetadata: { [index: string]: PipelineElementMetadata };
+    pipelineId: string;
+
+    static fromData(data: PipelineCanvasMetadata, target?: PipelineCanvasMetadata): PipelineCanvasMetadata {
+        if (!data) {
+            return data;
+        }
+        const instance = target || new PipelineCanvasMetadata();
+        instance.pipelineId = data.pipelineId;
+        instance.pipelineElementMetadata = __getCopyObjectFn(PipelineElementMetadata.fromData)(data.pipelineElementMetadata);
+        instance.comments = __getCopyArrayFn(PipelineCanvasComment.fromData)(data.comments);
+        instance._id = data._id;
+        instance._rev = data._rev;
+        return instance;
+    }
+}
+
 export class PipelineCategory {
     _id: string;
     _rev: string;
@@ -2096,6 +2146,21 @@ export class PipelineCategory {
     }
 }
 
+export class PipelineElementMetadata {
+    customName: string;
+    position: CanvasPosition;
+
+    static fromData(data: PipelineElementMetadata, target?: PipelineElementMetadata): PipelineElementMetadata {
+        if (!data) {
+            return data;
+        }
+        const instance = target || new PipelineElementMetadata();
+        instance.position = CanvasPosition.fromData(data.position);
+        instance.customName = data.customName;
+        return instance;
+    }
+}
+
 export class PipelineElementMonitoringInfo {
     consumedMessageInfoExists: boolean;
     consumedMessagesInfos: ConsumedMessagesInfo[];
diff --git a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
index bf42643..6a616a4 100644
--- a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
+++ b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.html
@@ -145,6 +145,7 @@
                           [allElements]="allElements"
                           [preview]="false"
                           [pipelineCached]="pipelineCached"
+                          [pipelineCanvasMetadata]="pipelineCanvasMetadata"
                           [pipelineCacheRunning]="pipelineCacheRunning"
                           (pipelineCachedChanged)="pipelineCached=$event"
                           (pipelineCacheRunningChanged)="pipelineCacheRunning=$event">
diff --git a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
index e78ee18..ed3c629 100644
--- a/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
+++ b/ui/src/app/editor/components/pipeline-assembly/pipeline-assembly.component.ts
@@ -40,11 +40,13 @@ import {ConfirmDialogComponent} from "../../../core-ui/dialog/confirm-dialog/con
 import {MatDialog} from "@angular/material/dialog";
 import {EditorService} from "../../services/editor.service";
 import {PipelineService} from "../../../platform-services/apis/pipeline.service";
-import {PipelineCanvasScrollingService} from "../../services/pipeline-canvas-scrolling.service";
 import {JsplumbFactoryService} from "../../services/jsplumb-factory.service";
 import Panzoom, {PanzoomObject} from "@panzoom/panzoom";
 import {PipelineElementDraggedService} from "../../services/pipeline-element-dragged.service";
 import {PipelineComponent} from "../pipeline/pipeline.component";
+import {PipelineCanvasMetadata} from "../../../core-model/gen/streampipes-model";
+import {forkJoin} from 'rxjs';
+import {PipelineCanvasMetadataService} from "../../../platform-services/apis/pipeline-canvas-metadata.service";
 
 
 @Component({
@@ -85,6 +87,9 @@ export class PipelineAssemblyComponent implements OnInit {
     pipelineCacheRunning: boolean = false;
     pipelineCached: boolean = false;
 
+    pipelineCanvasMetadata: PipelineCanvasMetadata = new PipelineCanvasMetadata();
+    pipelineCanvasMetadataAvailable: boolean = false;
+
     config: any = {};
     @ViewChild("outerCanvas") pipelineCanvas: ElementRef;
 
@@ -104,7 +109,8 @@ export class PipelineAssemblyComponent implements OnInit {
                 private dialogService: DialogService,
                 private dialog: MatDialog,
                 private ngZone: NgZone,
-                private pipelineElementDraggedService: PipelineElementDraggedService) {
+                private pipelineElementDraggedService: PipelineElementDraggedService,
+                private pipelineCanvasMetadataService: PipelineCanvasMetadataService) {
 
         this.selectMode = true;
         this.currentZoomLevel = 1;
@@ -200,7 +206,10 @@ export class PipelineAssemblyComponent implements OnInit {
         this.currentZoomLevel = 1;
         this.JsplumbBridge.setZoom(this.currentZoomLevel);
         this.JsplumbBridge.repaintEverything();
-        this.EditorService.removePipelineFromCache().subscribe(msg => {
+
+        let removePipelineFromCache = this.EditorService.removePipelineFromCache();
+        let removeCanvasMetadataFromCache = this.EditorService.removeCanvasMetadataFromCache();
+        forkJoin([removePipelineFromCache, removeCanvasMetadataFromCache]).subscribe(msg => {
             this.pipelineCached = false;
             this.pipelineCacheRunning = false;
         });
@@ -211,7 +220,7 @@ export class PipelineAssemblyComponent implements OnInit {
      */
     submit() {
         var pipeline = this.ObjectProvider.makeFinalPipeline(this.rawPipelineModel);
-
+        this.PipelinePositioningService.collectPipelineElementPositions(this.pipelineCanvasMetadata, this.rawPipelineModel);
         pipeline.name = this.currentPipelineName;
         pipeline.description = this.currentPipelineDescription;
         if (this.currentModifiedPipelineId) {
@@ -223,34 +232,55 @@ export class PipelineAssemblyComponent implements OnInit {
             title: "Save pipeline",
             data: {
                 "pipeline": pipeline,
-                "currentModifiedPipelineId": this.currentModifiedPipelineId
+                "currentModifiedPipelineId": this.currentModifiedPipelineId,
+                "pipelineCanvasMetadata": this.pipelineCanvasMetadata
             }
         });
     }
 
     checkAndDisplayCachedPipeline() {
-        this.EditorService.getCachedPipeline().subscribe(msg => {
-            if (msg) {
-                this.rawPipelineModel = msg;
-                this.displayPipelineInEditor(true);
+        let cachedPipeline = this.EditorService.getCachedPipeline();
+        let cachedCanvasMetadata = this.EditorService.getCachedPipelineCanvasMetadata();
+        forkJoin([cachedPipeline, cachedCanvasMetadata]).subscribe(results => {
+            if (results[0] && results[0].length > 0) {
+                this.rawPipelineModel = results[0] as PipelineElementConfig[];
+                this.handleCanvasMetadataResponse(results[1]);
+                this.displayPipelineInEditor(!this.pipelineCanvasMetadataAvailable, this.pipelineCanvasMetadata);
             }
         });
     }
 
     displayPipelineById() {
-        this.PipelineService.getPipelineById(this.currentModifiedPipelineId)
-            .subscribe((msg) => {
-                let pipeline = msg;
+        let pipelineRequest = this.PipelineService.getPipelineById(this.currentModifiedPipelineId);
+        let canvasRequest = this.pipelineCanvasMetadataService.getPipelineCanvasMetadata(this.currentModifiedPipelineId);
+        pipelineRequest.subscribe(pipelineResp => {
+                let pipeline = pipelineResp;
                 this.currentPipelineName = pipeline.name;
                 this.currentPipelineDescription = pipeline.description;
                 this.rawPipelineModel = this.JsplumbService.makeRawPipeline(pipeline, false);
-                this.displayPipelineInEditor(true);
+                canvasRequest.subscribe(canvasResp => {
+                    this.handleCanvasMetadataResponse(canvasResp);
+                }, error => {
+                    this.handleCanvasMetadataResponse(undefined);
+                });
             });
     };
 
-    displayPipelineInEditor(autoLayout) {
+    handleCanvasMetadataResponse(canvasMetadata: PipelineCanvasMetadata) {
+        if (canvasMetadata) {
+            this.pipelineCanvasMetadata = canvasMetadata;
+            this.pipelineCanvasMetadataAvailable = true;
+        } else {
+            this.pipelineCanvasMetadataAvailable = false;
+            this.pipelineCanvasMetadata = new PipelineCanvasMetadata();
+        }
+        this.displayPipelineInEditor(!this.pipelineCanvasMetadataAvailable, this.pipelineCanvasMetadata);
+    }
+
+    displayPipelineInEditor(autoLayout,
+                            pipelineCanvasMetadata?: PipelineCanvasMetadata) {
         setTimeout(() => {
-            this.PipelinePositioningService.displayPipeline(this.rawPipelineModel, "#assembly", false, autoLayout);
+            this.PipelinePositioningService.displayPipeline(this.rawPipelineModel, "#assembly", false, autoLayout, pipelineCanvasMetadata);
             this.EditorService.makePipelineAssemblyEmpty(false);
             this.ngZone.run(() => {
                 this.pipelineValid = this.PipelineValidationService
diff --git a/ui/src/app/editor/components/pipeline/pipeline.component.ts b/ui/src/app/editor/components/pipeline/pipeline.component.ts
index c478963..08288fc 100644
--- a/ui/src/app/editor/components/pipeline/pipeline.component.ts
+++ b/ui/src/app/editor/components/pipeline/pipeline.component.ts
@@ -21,15 +21,7 @@ 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 {
-  ChangeDetectorRef,
-  Component, ElementRef,
-  EventEmitter,
-  Input,
-  NgZone, OnDestroy,
-  OnInit,
-  Output, ViewChild
-} from "@angular/core";
+import {Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output} from "@angular/core";
 import {
   InvocablePipelineElementUnion,
   PipelineElementConfig,
@@ -37,9 +29,13 @@ import {
 } from "../../model/editor.model";
 import {
   CustomOutputStrategy,
-  DataProcessorInvocation, DataSinkInvocation, ErrorMessage,
-  Pipeline, PipelinePreviewModel, SpDataSet,
-  SpDataStream, SpDataStreamUnion
+  DataProcessorInvocation,
+  DataSinkInvocation,
+  Pipeline,
+  PipelineCanvasMetadata,
+  PipelinePreviewModel,
+  SpDataSet,
+  SpDataStream
 } from "../../../core-model/gen/streampipes-model";
 import {ObjectProvider} from "../../services/object-provider.service";
 import {CustomizeComponent} from "../../dialog/customize/customize.component";
@@ -51,10 +47,9 @@ import {MatchingErrorComponent} from "../../dialog/matching-error/matching-error
 import {Tuple2} from "../../../core-model/base/Tuple2";
 import {ConfirmDialogComponent} from "../../../core-ui/dialog/confirm-dialog/confirm-dialog.component";
 import {MatDialog} from "@angular/material/dialog";
-import {Subject} from "rxjs";
-import {PipelineElementDraggedService} from "../../services/pipeline-element-dragged.service";
-import {PipelineCanvasScrollingService} from "../../services/pipeline-canvas-scrolling.service";
+import {forkJoin} from "rxjs";
 import {JsplumbFactoryService} from "../../services/jsplumb-factory.service";
+import {PipelinePositioningService} from "../../services/pipeline-positioning.service";
 
 @Component({
   selector: 'pipeline',
@@ -87,6 +82,9 @@ export class PipelineComponent implements OnInit, OnDestroy {
   @Input()
   pipelineCacheRunning: boolean;
 
+  @Input()
+  pipelineCanvasMetadata: PipelineCanvasMetadata;
+
   @Output()
   pipelineCacheRunningChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
 
@@ -109,6 +107,7 @@ export class PipelineComponent implements OnInit, OnDestroy {
 
   constructor(private JsplumbService: JsplumbService,
               private PipelineEditorService: PipelineEditorService,
+              private PipelinePositioningService: PipelinePositioningService,
               private JsplumbFactoryService: JsplumbFactoryService,
               private ObjectProvider: ObjectProvider,
               private EditorService: EditorService,
@@ -392,13 +391,18 @@ export class PipelineComponent implements OnInit, OnDestroy {
   }
 
   triggerPipelineCacheUpdate() {
-    this.pipelineCacheRunning = true;
-    this.pipelineCacheRunningChanged.emit(this.pipelineCacheRunning);
-    this.EditorService.updateCachedPipeline(this.rawPipelineModel).subscribe(msg => {
-      this.pipelineCacheRunning = false;
-      this.pipelineCacheRunningChanged.emit(this.pipelineCacheRunning)
-      this.pipelineCached = true;
-      this.pipelineCachedChanged.emit(this.pipelineCached);
+    setTimeout(() => {
+      this.pipelineCacheRunning = true;
+      this.pipelineCacheRunningChanged.emit(this.pipelineCacheRunning);
+      this.PipelinePositioningService.collectPipelineElementPositions(this.pipelineCanvasMetadata, this.rawPipelineModel);
+      let updateCachedPipeline = this.EditorService.updateCachedPipeline(this.rawPipelineModel);
+      let updateCachedCanvasMetadata = this.EditorService.updateCachedCanvasMetadata(this.pipelineCanvasMetadata);
+      forkJoin([updateCachedPipeline, updateCachedCanvasMetadata]).subscribe(msg => {
+        this.pipelineCacheRunning = false;
+        this.pipelineCacheRunningChanged.emit(this.pipelineCacheRunning)
+        this.pipelineCached = true;
+        this.pipelineCachedChanged.emit(this.pipelineCached);
+      });
     });
   }
 
diff --git a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
index 5660ba1..4041746 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
@@ -18,13 +18,14 @@
 
 import {Component, Input, OnInit} from "@angular/core";
 import {DialogRef} from "../../../core-ui/dialog/base-dialog/dialog-ref";
-import {Message, Pipeline} from "../../../core-model/gen/streampipes-model";
+import {Message, Pipeline, PipelineCanvasMetadata} from "../../../core-model/gen/streampipes-model";
 import {ObjectProvider} from "../../services/object-provider.service";
 import {EditorService} from "../../services/editor.service";
 import {PipelineService} from "../../../platform-services/apis/pipeline.service";
 import {ShepherdService} from "../../../services/tour/shepherd.service";
 import {FormControl, FormGroup, Validators} from "@angular/forms";
 import {Router} from "@angular/router";
+import {PipelineCanvasMetadataService} from "../../../platform-services/apis/pipeline-canvas-metadata.service";
 
 @Component({
   selector: 'save-pipeline',
@@ -48,20 +49,24 @@ export class SavePipelineComponent implements OnInit {
   @Input()
   currentModifiedPipelineId: string;
 
+  @Input()
+  pipelineCanvasMetadata: PipelineCanvasMetadata;
+
   saving: boolean = false;
   saved: boolean = false;
 
   storageError: boolean = false;
   errorMessage: string = '';
 
- currentPipelineName: string;
+  currentPipelineName: string;
 
   constructor(private editorService: EditorService,
               private dialogRef: DialogRef<SavePipelineComponent>,
               private objectProvider: ObjectProvider,
               private pipelineService: PipelineService,
               private Router: Router,
-              private ShepherdService: ShepherdService) {
+              private ShepherdService: ShepherdService,
+              private pipelineCanvasService: PipelineCanvasMetadataService) {
     this.pipelineCategories = [];
     this.updateMode = "update";
   }
@@ -112,8 +117,9 @@ export class SavePipelineComponent implements OnInit {
 
   savePipeline(switchTab) {
     let storageRequest;
+    let updateMode = this.currentModifiedPipelineId && this.updateMode === 'update';
 
-    if (this.currentModifiedPipelineId && this.updateMode === 'update') {
+    if (updateMode) {
       storageRequest = this.pipelineService.updatePipeline(this.pipeline);
     } else {
       this.pipeline._id = undefined;
@@ -124,6 +130,7 @@ export class SavePipelineComponent implements OnInit {
         .subscribe(statusMessage => {
           if (statusMessage.success) {
             let pipelineId: string = statusMessage.notifications[1].description;
+            this.storePipelineCanvasMetadata(pipelineId, updateMode);
             this.afterStorage(statusMessage, switchTab, pipelineId);
           } else {
             this.displayErrors(statusMessage.notifications[0]);
@@ -133,6 +140,21 @@ export class SavePipelineComponent implements OnInit {
         });
   };
 
+  storePipelineCanvasMetadata(pipelineId: string,
+                              updateMode: boolean) {
+    let request;
+    this.pipelineCanvasMetadata.pipelineId = pipelineId;
+    if (updateMode) {
+      request = this.pipelineCanvasService.updatePipelineCanvasMetadata(this.pipelineCanvasMetadata);
+    } else {
+      this.pipelineCanvasMetadata._id = undefined;
+      this.pipelineCanvasMetadata._rev = undefined;
+      request = this.pipelineCanvasService.addPipelineCanvasMetadata(this.pipelineCanvasMetadata);
+    }
+
+    request.subscribe();
+  }
+
   afterStorage(statusMessage: Message, switchTab, pipelineId?: string) {
     this.hide();
     this.editorService.makePipelineAssemblyEmpty(true);
diff --git a/ui/src/app/editor/services/editor.service.ts b/ui/src/app/editor/services/editor.service.ts
index 18b115d..a3f244a 100644
--- a/ui/src/app/editor/services/editor.service.ts
+++ b/ui/src/app/editor/services/editor.service.ts
@@ -21,9 +21,12 @@ import {HttpClient} from "@angular/common/http";
 import {
   DataProcessorInvocation,
   DataSetModificationMessage,
-  DataSinkInvocation, Pipeline,
+  DataSinkInvocation,
+  Pipeline,
+  PipelineCanvasMetadata,
   PipelineElementRecommendationMessage,
-  PipelineModificationMessage, PipelinePreviewModel,
+  PipelineModificationMessage,
+  PipelinePreviewModel,
   SpDataSet,
   SpDataStream
 } from "../../core-model/gen/streampipes-model";
@@ -79,6 +82,13 @@ export class EditorService {
             }));
     }
 
+    getCachedPipelineCanvasMetadata(): Observable<PipelineCanvasMetadata> {
+      return this.http.get(this.platformServicesCommons.authUserBasePath() + "/pipeline-canvas-cache")
+          .pipe(map(response => {
+            return PipelineCanvasMetadata.fromData(response as any);
+      }));
+    }
+
     convert(payload: any) {
       if (payload['@class'] === "org.apache.streampipes.model.SpDataSet") {
         return SpDataSet.fromData(payload as SpDataSet);
@@ -115,10 +125,19 @@ export class EditorService {
         return this.http.post(this.platformServicesCommons.authUserBasePath() + "/pipeline-cache", rawPipelineModel);
     }
 
+    updateCachedCanvasMetadata(pipelineCanvasMetadata: PipelineCanvasMetadata) {
+      return this.http.post(this.platformServicesCommons.authUserBasePath()
+          + "/pipeline-canvas-cache", pipelineCanvasMetadata)
+    }
+
     removePipelineFromCache() {
         return this.http.delete(this.platformServicesCommons.authUserBasePath() + "/pipeline-cache");
     }
 
+    removeCanvasMetadataFromCache() {
+      return this.http.delete(this.platformServicesCommons.authUserBasePath() + "/pipeline-canvas-cache");
+    }
+
     private get pipelinesResourceUrl() {
         return this.platformServicesCommons.authUserBasePath() + '/pipelines'
     }
diff --git a/ui/src/app/editor/services/pipeline-positioning.service.ts b/ui/src/app/editor/services/pipeline-positioning.service.ts
index ef03614..bf41d28 100644
--- a/ui/src/app/editor/services/pipeline-positioning.service.ts
+++ b/ui/src/app/editor/services/pipeline-positioning.service.ts
@@ -25,6 +25,8 @@ import {PipelineElementConfig} from "../model/editor.model";
 import {
     DataProcessorInvocation,
     DataSinkInvocation,
+    PipelineCanvasMetadata,
+    PipelineElementMetadata,
     SpDataStream
 } from "../../core-model/gen/streampipes-model";
 import {JsplumbFactoryService} from "./jsplumb-factory.service";
@@ -39,10 +41,38 @@ export class PipelinePositioningService {
                 private ObjectProvider: ObjectProvider) {
     }
 
+    collectPipelineElementPositions(pipelineCanvasMetadata: PipelineCanvasMetadata,
+                                    rawPipelineModel: PipelineElementConfig[]): PipelineCanvasMetadata {
+        rawPipelineModel.forEach(pe => {
+           this.collectPipelineElementPosition(pe.payload.dom, pipelineCanvasMetadata);
+        });
+        return pipelineCanvasMetadata;
+    }
+
+    collectPipelineElementPosition(domId: string,
+                                   pipelineCanvasMetadata: PipelineCanvasMetadata) {
+        let elementRef = $(`#${domId}`);
+        if (elementRef && elementRef.position()) {
+            let leftPos = elementRef.position().left;
+            let topPos = elementRef.position().top;
+            if (!pipelineCanvasMetadata.pipelineElementMetadata) {
+                pipelineCanvasMetadata.pipelineElementMetadata = {};
+            }
+            if (!pipelineCanvasMetadata.pipelineElementMetadata[domId]) {
+                pipelineCanvasMetadata.pipelineElementMetadata[domId] = new PipelineElementMetadata();
+            }
+            pipelineCanvasMetadata.pipelineElementMetadata[domId].position = {
+                x: leftPos,
+                y: topPos
+            };
+        }
+    }
+
     displayPipeline(rawPipelineModel: PipelineElementConfig[],
                     targetCanvas,
                     previewConfig: boolean,
-                    autoLayout: boolean) {
+                    autoLayout: boolean,
+                    pipelineCanvasMetadata?: PipelineCanvasMetadata) {
         let jsPlumbBridge = this.JsplumbFactoryService.getJsplumbBridge(previewConfig);
         let jsplumbConfig = previewConfig ? this.JsplumbConfigService.getPreviewConfig() : this.JsplumbConfigService.getEditorConfig();
         rawPipelineModel.forEach(currentPe => {
@@ -65,44 +95,60 @@ export class PipelinePositioningService {
         this.connectPipelineElements(rawPipelineModel, previewConfig, jsplumbConfig, jsPlumbBridge);
         if (autoLayout) {
             this.layoutGraph(targetCanvas, "span[id^='jsplumb']", previewConfig ? 75 : 110, previewConfig);
+        } else if (pipelineCanvasMetadata)  {
+            this.layoutGraphFromCanvasMetadata(pipelineCanvasMetadata);
         }
         jsPlumbBridge.repaintEverything();
     }
 
-    layoutGraph(canvas, nodeIdentifier, dimension, isPreview) {
-        let jsPlumbBridge = this.JsplumbFactoryService.getJsplumbBridge(isPreview);
-        var g = new dagre.graphlib.Graph();
-        g.setGraph({rankdir: "LR", ranksep: isPreview ? "50" : "100"});
+    layoutGraph(canvasId: string,
+                nodeIdentifier: string,
+                dimension: number,
+                previewConfig: boolean) {
+        let jsPlumbBridge = this.JsplumbFactoryService.getJsplumbBridge(previewConfig);
+        let g = new dagre.graphlib.Graph();
+        g.setGraph({rankdir: "LR", ranksep: previewConfig ? "50" : "100"});
         g.setDefaultEdgeLabel(function () {
             return {};
         });
 
-        var nodes = $(canvas).find(nodeIdentifier).get();
-        nodes.forEach((n, index) => {
+        let nodes = $(canvasId).find(nodeIdentifier).get();
+        nodes.forEach((n) => {
             g.setNode(n.id, {label: n.id, width: dimension, height: dimension});
         });
 
-        var edges = jsPlumbBridge.getAllConnections();
+        let edges = jsPlumbBridge.getAllConnections();
         edges.forEach(edge => {
             g.setEdge(edge.source.id, edge.target.id);
         });
 
         dagre.layout(g);
         g.nodes().forEach(v => {
-            $(`#${v}`).css("left", g.node(v).x + "px");
-            $(`#${v}`).css("top", g.node(v).y + "px");
+            let elementRef = $(`#${v}`);
+            elementRef.css("left", g.node(v).x + "px");
+            elementRef.css("top", g.node(v).y + "px");
         });
     }
 
+    layoutGraphFromCanvasMetadata(pipelineCanvasMetadata: PipelineCanvasMetadata) {
+        Object.entries(pipelineCanvasMetadata.pipelineElementMetadata).forEach(
+            ([key, value]) => {
+                let elementRef = $(`#${key}`);
+                if (elementRef) {
+                    elementRef.css("left", value.position.x + "px");
+                    elementRef.css("top", value.position.y + "px");
+                }
+            }
+        );
+    }
+
     connectPipelineElements(rawPipelineModel: PipelineElementConfig[],
                             previewConfig: boolean,
                             jsplumbConfig: any,
                             jsPlumbBridge: JsplumbBridge) {
-        var source, target;
+        let source, target;
         jsPlumbBridge.setSuspendDrawing(true);
-        for (var i = 0; i < rawPipelineModel.length; i++) {
-            var pe = rawPipelineModel[i];
-
+        rawPipelineModel.forEach(pe => {
             if (pe.type == "sepa" || pe.type == "action") {
                 if (!(pe.settings.disabled) && pe.payload.connectedTo) {
                     pe.payload.connectedTo.forEach((connection, index) => {
@@ -127,7 +173,7 @@ export class PipelinePositioningService {
                     });
                 }
             }
-        }
+        });
         jsPlumbBridge.setSuspendDrawing(false, true);
     }
 }
diff --git a/ui/src/app/platform-services/apis/pipeline-canvas-metadata.service.ts b/ui/src/app/platform-services/apis/pipeline-canvas-metadata.service.ts
new file mode 100644
index 0000000..09d1d5c
--- /dev/null
+++ b/ui/src/app/platform-services/apis/pipeline-canvas-metadata.service.ts
@@ -0,0 +1,65 @@
+/*
+ * 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 {Injectable} from "@angular/core";
+import {HttpClient} from "@angular/common/http";
+import {Observable} from "rxjs";
+import {
+  DataProcessorInvocation,
+  DataSinkInvocation,
+  DataSourceDescription, PipelineCanvasMetadata, SpDataSet, SpDataStream
+} from "../../core-model/gen/streampipes-model";
+import {PlatformServicesCommons} from "./commons.service";
+import {map} from "rxjs/operators";
+
+@Injectable()
+export class PipelineCanvasMetadataService {
+
+  constructor(private http: HttpClient,
+              private platformServicesCommons: PlatformServicesCommons) {
+
+  }
+
+  addPipelineCanvasMetadata(pipelineCanvasMetadata: PipelineCanvasMetadata) {
+    return this.http.post(this.pipelineCanvasMetadataBasePath, pipelineCanvasMetadata);
+  }
+
+  getPipelineCanvasMetadata(pipelineId: string): Observable<PipelineCanvasMetadata> {
+    return this.http.get(this.pipelineCanvasMetadataPipelinePath
+        + pipelineId).pipe(map(response => PipelineCanvasMetadata.fromData(response as any)));
+  }
+
+  updatePipelineCanvasMetadata(pipelineCanvasMetadata: PipelineCanvasMetadata) {
+    return this.http.put(this.pipelineCanvasMetadataBasePath
+        + "/"
+        + pipelineCanvasMetadata.pipelineId, pipelineCanvasMetadata);
+  }
+
+  deletePipelineCanvasMetadata(pipelineId: string) {
+    return this.http.delete(this.pipelineCanvasMetadataPipelinePath
+        + pipelineId);
+  }
+
+  private get pipelineCanvasMetadataBasePath() {
+    return this.platformServicesCommons.authUserBasePath() + "/pipeline-canvas-metadata";
+  }
+
+  private get pipelineCanvasMetadataPipelinePath() {
+    return this.pipelineCanvasMetadataBasePath + "/pipeline/";
+  }
+}
diff --git a/ui/src/app/platform-services/platform.module.ts b/ui/src/app/platform-services/platform.module.ts
index 44c8bea..85800cd 100644
--- a/ui/src/app/platform-services/platform.module.ts
+++ b/ui/src/app/platform-services/platform.module.ts
@@ -26,6 +26,7 @@ import {MeasurementUnitsService} from "./apis/measurement-units.service";
 import {PipelineElementTemplateService} from "./apis/pipeline-element-template.service";
 import {PipelineMonitoringService} from "./apis/pipeline-monitoring.service";
 import {SemanticTypesService} from "./apis/semantic-types.service";
+import {PipelineCanvasMetadataService} from "./apis/pipeline-canvas-metadata.service";
 
 @NgModule({
   imports: [],
@@ -34,6 +35,7 @@ import {SemanticTypesService} from "./apis/semantic-types.service";
     FilesService,
     MeasurementUnitsService,
     PlatformServicesCommons,
+    PipelineCanvasMetadataService,
     PipelineElementEndpointService,
     PipelineElementTemplateService,
     //PipelineTemplateService,

[incubator-streampipes] 01/02: [hotfix] Do not change original pipeline name in save dialog

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5dcd9ef2d8f752349c2a47995e9fc85d373e3c52
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Mon May 24 12:13:48 2021 +0200

    [hotfix] Do not change original pipeline name in save dialog
---
 .../editor/dialog/save-pipeline/save-pipeline.component.html  |  4 ++--
 .../editor/dialog/save-pipeline/save-pipeline.component.ts    | 11 ++++++-----
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
index 5065423..4599345 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.html
@@ -23,7 +23,7 @@
                 <div id="overwriteCheckbox" class="checkbox" *ngIf="currentModifiedPipelineId">
                     <mat-radio-group [(ngModel)]="updateMode" fxLayout="column" color="primary" class="pipeline-radio-group">
                         <mat-radio-button [value]="'update'" class="mb-10" style="padding-left: 0;">
-                            Update pipeline <b>{{pipeline.name}}</b>
+                            Update pipeline <b>{{currentPipelineName}}</b>
                         </mat-radio-button>
                         <mat-radio-button [value]="'clone'" class="mb-10">
                             Create new pipeline
@@ -74,4 +74,4 @@
             {{saved ? 'Close' : 'Cancel'}}
         </button>
     </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
index b9cef48..5660ba1 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
@@ -54,6 +54,8 @@ export class SavePipelineComponent implements OnInit {
   storageError: boolean = false;
   errorMessage: string = '';
 
+ currentPipelineName: string;
+
   constructor(private editorService: EditorService,
               private dialogRef: DialogRef<SavePipelineComponent>,
               private objectProvider: ObjectProvider,
@@ -66,6 +68,10 @@ export class SavePipelineComponent implements OnInit {
 
   ngOnInit() {
     this.getPipelineCategories();
+    if (this.currentModifiedPipelineId) {
+      this.currentPipelineName = this.pipeline.name;
+    }
+
     this.submitPipelineForm.addControl("pipelineName", new FormControl(this.pipeline.name,
         [Validators.required,
           Validators.maxLength(40)]))
@@ -105,11 +111,6 @@ export class SavePipelineComponent implements OnInit {
 
 
   savePipeline(switchTab) {
-    if (this.pipeline.name == "") {
-      //this.showToast("error", "Please enter a name for your pipeline");
-      return false;
-    }
-
     let storageRequest;
 
     if (this.currentModifiedPipelineId && this.updateMode === 'update') {