Repository: apache/streampipes
Pull request #1489 (2023-04-06 07:31:13)

Add integration test for MQTT (#1485)
<!--
  ~ 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.
  ~
  -->
  
  <!--
Thanks for contributing! Here are some tips you can follow to help us incorporate your contribution quickly and easily:
1. If this is your first time, please read our contributor guidelines:
    - https://streampipes.apache.org/getinvolved.html
    - https://cwiki.apache.org/confluence/display/STREAMPIPES/Getting+Started
2. Make sure the PR title is formatted like: `[#<GitHub issue id>] PR title ...`
3. If the PR is unfinished, add '[WIP]' in your PR title, e.g., `[WIP][#<GitHub issue id>] PR title ...`.
4. Please write your PR title to summarize what this PR proposes/fixes.
5. Link the PR to the corresponding GitHub issue (if present) in the `Development` section in the right menu bar. 
6. Be sure to keep the PR description updated to reflect all changes.
7. If possible, provide a concise example to reproduce the issue for a faster review.
8. Make sure tests pass via `mvn clean install`.
9. (Optional) If the contribution is large, please file an Apache ICLA
    - http://apache.org/licenses/icla.pdf
-->

### Purpose
<!--
Please clarify what changes you are proposing and describe how those changes will address the issue.
Furthermore, describe potential consequences the changes might have.
-->

Add integration test for MQTT adapter

### Remarks
<!--
Is there anything left we need to pay attention on?
Are there some references that might be important? E.g. links to Confluence, or discussions
on the mailing list or GitHub.
-->
PR introduces (a) breaking change(s): <yes/no>

PR introduces (a) deprecation(s): <yes/no>


github-actions[bot]: Hello there :wave:<br> <br> We noticed that it's been some time since activity occurred on your pull request :thinking:. In order to keep things moving forward, we're marking this PR as _stale_ and giving you 7 days to respond before it's automatically closed :alarm_clock:.<br> <br> Please take a moment to review your pull request and make any necessary updates or changes :man_technologist:. If you need more time or have any questions, please don't hesitate to let us know :speech_balloon:.<br> <br> Thank you for your contributions to our project, and we look forward to hearing back from you soon :pray:.

Patch:
diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml
index abf5e3a157..8d4645b863 100644
--- a/.github/workflows/osv-scanner.yml
+++ b/.github/workflows/osv-scanner.yml
@@ -35,7 +35,8 @@ jobs:
 
       - name: Scan for vulnerabilities
         run: |
-          docker run --rm -v $PWD:/repo -w /repo ghcr.io/google/osv-scanner --format markdown -r . 2>&1 | grep -vE '^((Scanning|Scanned|Failed).*)$' > VULNERABILITY.md
+          echo '<!--\n  ~ Licensed to the Apache Software Foundation (ASF) under one or more\n  ~ contributor license agreements.  See the NOTICE file distributed with\n  ~ this work for additional information regarding copyright ownership.\n  ~ The ASF licenses this file to You under the Apache License, Version 2.0\n  ~ (the "License"); you may not use this file except in compliance with\n  ~ the License.  You may obtain a copy of the License at\n  ~\n  ~    http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an "AS IS" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  ~\n  -->' > VULNERABILITY.md
+          docker run --rm -v $PWD:/repo -w /repo ghcr.io/google/osv-scanner --format markdown -r . 2>&1 | grep -vE '^((Scanning|Scanned|Failed).*)$' >> VULNERABILITY.md
         continue-on-error: true
 
       - name: Create Pull Request
@@ -46,13 +47,9 @@ jobs:
           committer: ${{ github.actor }} <noreply@gmail.com>
           author: ${{ github.actor }} <${{ github.actor }}@github.com>
           signoff: true
-          title: Add vulnerability report
-          commit-message: Added vulnerability report
+          title: Monthly update of vulnerability report
+          commit-message: monthly update of vulnerability report
           body: |
             Update *Vulnerablity* report
           delete-branch: true
-          labels: |
-            documentation
-            gh-action
-          team-reviewers: apache/streampipes-committers
-
+          reviewers: 'dominikriemer,tenthe,svenO3,smlabt,grainier,RobertIndie,bossenti'
diff --git a/.github/workflows/python-docs.yml b/.github/workflows/python-docs.yml
index 6581ee030e..38ef259fe1 100644
--- a/.github/workflows/python-docs.yml
+++ b/.github/workflows/python-docs.yml
@@ -63,5 +63,7 @@ jobs:
         uses: actions/upload-artifact@v3
         with:
           name: streampipes-python-docs
-          path: streampipes-client-python/docs-tmp/**/*
+          path: |
+            streampipes-client-python/docs-tmp/**/*
+            !streampipes-client-python/docs-tmp/**/versions.json  # exclude versions file because this would overwrite existing versions and aliases
           retention-days: 5
diff --git a/VULNERABILITY.md b/VULNERABILITY.md
new file mode 100644
index 0000000000..94c0bceaab
--- /dev/null
+++ b/VULNERABILITY.md
@@ -0,0 +1,32 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+| OSV URL | Ecosystem | Package | Version | Source |
+| --- | --- | --- | --- | --- |
+| https://osv.dev/GHSA-599f-7c49-w659 | Maven | org.apache.commons:commons-text | 1.8 | pom.xml |
+| https://osv.dev/GHSA-wxqc-pxw9-g2p8 | Maven | org.springframework:spring-core | 6.0.7 | pom.xml |
+| https://osv.dev/GHSA-mjmj-j48q-9wg2 | Maven | org.yaml:snakeyaml | 1.33 | pom.xml |
+| https://osv.dev/GHSA-px8h-6qxv-m22q | PyPI | werkzeug | 1.0.1 | streampipes-wrapper-python/requirements.txt |
+| https://osv.dev/GHSA-xg9f-g7g7-2323 | PyPI | werkzeug | 1.0.1 | streampipes-wrapper-python/requirements.txt |
+| https://osv.dev/PYSEC-2022-203 | PyPI | werkzeug | 1.0.1 | streampipes-wrapper-python/requirements.txt |
+| https://osv.dev/GHSA-36jr-mh4h-2g58 | npm | d3-color | 1.4.1 | ui/package-lock.json |
+| https://osv.dev/GHSA-36jr-mh4h-2g58 | npm | d3-color | 2.0.0 | ui/package-lock.json |
+| https://osv.dev/GHSA-ww39-953v-wcq6 | npm | glob-parent | 3.1.0 | ui/package-lock.json |
+| https://osv.dev/GHSA-4943-9vgg-gr5r | npm | quill | 1.3.7 | ui/package-lock.json |
+| https://osv.dev/GHSA-hc6q-2mpp-qw7j | npm | webpack | 5.74.0 | ui/package-lock.json |
+| https://osv.dev/GHSA-f9xv-q969-pqx4 | npm | yaml | 2.2.1 | ui/package-lock.json |
diff --git a/pom.xml b/pom.xml
index 356d900616..d34da2e216 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,7 +63,7 @@
 	<immutable-value.version>2.9.3</immutable-value.version>
 	<influxdb.version>2.23</influxdb.version>
 	<j2html.version>1.6.0</j2html.version>
-	<jackson.version>2.14.1</jackson.version>
+	<jackson.version>2.15.0</jackson.version>
 	<jackson.databind.version>2.14.1</jackson.databind.version>
 	<jakarta-annotation.version>2.1.1</jakarta-annotation.version>
 	<jakarta-activation-api.version>1.2.2</jakarta-activation-api.version>
@@ -84,7 +84,7 @@
 	<jboss-logging-processor.version>2.2.1.Final</jboss-logging-processor.version>
 	<jersey.version>3.1.0</jersey.version>
 	<jetbrains-annotations.version>16.0.2</jetbrains-annotations.version>
-	<jetty.version>10.0.10</jetty.version>
+	<jetty.version>10.0.14</jetty.version>
 	<jgrapht.version>1.5.1</jgrapht.version>
 	<jjwt.version>0.11.2</jjwt.version>
 	<jna.version>5.13.0</jna.version>
@@ -117,11 +117,11 @@
 	<slf4j.version>2.0.6</slf4j.version>
 	<snakeyaml.version>1.33</snakeyaml.version>
 	<snappy-java.version>1.1.7.7</snappy-java.version>
-	<spring.version>6.0.7</spring.version>
-	<spring-boot.version>3.0.5</spring-boot.version>
-	<spring-security.version>6.0.2</spring-security.version>
+	<spring.version>6.0.8</spring.version>
+	<spring-boot.version>3.0.6</spring-boot.version>
+	<spring-security.version>6.0.3</spring-security.version>
 	<swagger.version>2.2.7</swagger.version>
-	<type-parser.version>0.7.0</type-parser.version>
+	<type-parser.version>0.8.1</type-parser.version>
 	<wildfly-common.version>1.6.0.Final</wildfly-common.version>
 	<hawtbuf.version>1.11</hawtbuf.version>
 	<netty-tc-native.version>2.0.52.Final</netty-tc-native.version>
@@ -189,7 +189,6 @@
 	<antlr4-runtime.version>4.11.1</antlr4-runtime.version>
 	<checker-qual.version>2.5.2</checker-qual.version>
 	<commons-collections4.version>4.4</commons-collections4.version>
-	<commons-text.version>1.8</commons-text.version>
 	<guice.version>5.1.0</guice.version>
 	<eclipse.milo.version>0.6.3</eclipse.milo.version>
 	<netty.version>4.1.72.Final</netty.version>
diff --git a/streampipes-client-python/setup.py b/streampipes-client-python/setup.py
index 5602360afe..bb7a20c330 100644
--- a/streampipes-client-python/setup.py
+++ b/streampipes-client-python/setup.py
@@ -53,12 +53,12 @@ dev_packages = base_packages + [
     "isort==5.12.0",
     "mypy==1.2.0",
     "pandas-stubs==2.0.0.230412",
-    "pre-commit==3.2.0",
+    "pre-commit==3.3.0",
     "pytest==7.3.0",
     "pytest-cov==4.0.0",
     "pyupgrade==3.3.1",
     "types-Jinja2==2.11.9",
-    "types-requests==2.28.11.7",
+    "types-requests==2.29.0.0",
 ]
 
 docs_packages = [
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/api/AbstractTypedClientApi.java b/streampipes-client/src/main/java/org/apache/streampipes/client/api/AbstractTypedClientApi.java
index abd60c439a..ff3964bd8e 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/api/AbstractTypedClientApi.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/api/AbstractTypedClientApi.java
@@ -22,9 +22,11 @@ import org.apache.streampipes.client.model.StreamPipesClientConfig;
 import org.apache.streampipes.client.serializer.ListSerializer;
 import org.apache.streampipes.client.serializer.ObjectSerializer;
 import org.apache.streampipes.client.util.StreamPipesApiPath;
+import org.apache.streampipes.commons.exceptions.SpHttpErrorStatusCode;
 import org.apache.streampipes.commons.exceptions.SpRuntimeException;
 
 import java.util.List;
+import java.util.Optional;
 
 public abstract class AbstractTypedClientApi<T> extends AbstractClientApi {
 
@@ -40,9 +42,17 @@ public abstract class AbstractTypedClientApi<T> extends AbstractClientApi {
     return new GetRequest<>(clientConfig, apiPath, targetClass, serializer).executeRequest();
   }
 
-  protected T getSingle(StreamPipesApiPath apiPath) throws SpRuntimeException {
+  protected Optional<T> getSingle(StreamPipesApiPath apiPath) throws SpRuntimeException {
     ObjectSerializer<Void, T> serializer = new ObjectSerializer<>();
-    return new GetRequest<>(clientConfig, apiPath, targetClass, serializer).executeRequest();
+    try {
+      return Optional.of(new GetRequest<>(clientConfig, apiPath, targetClass, serializer).executeRequest());
+    } catch (SpHttpErrorStatusCode e) {
+      if (e.getHttpStatusCode() == 404) {
+        return Optional.empty();
+      } else {
+        throw e;
+      }
+    }
   }
 
   protected abstract StreamPipesApiPath getBaseResourcePath();
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/api/CRUDApi.java b/streampipes-client/src/main/java/org/apache/streampipes/client/api/CRUDApi.java
index 6cabcc09ab..cbe4221850 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/api/CRUDApi.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/api/CRUDApi.java
@@ -18,10 +18,11 @@
 package org.apache.streampipes.client.api;
 
 import java.util.List;
+import java.util.Optional;
 
 public interface CRUDApi<K, V> {
 
-  V get(K id);
+  Optional<V> get(K id);
 
   List<V> all();
 
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataLakeMeasureApi.java b/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataLakeMeasureApi.java
index d59b4898ea..702723ad44 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataLakeMeasureApi.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataLakeMeasureApi.java
@@ -23,6 +23,7 @@ import org.apache.streampipes.client.util.StreamPipesApiPath;
 import org.apache.streampipes.model.datalake.DataLakeMeasure;
 
 import java.util.List;
+import java.util.Optional;
 
 public class DataLakeMeasureApi extends AbstractTypedClientApi<DataLakeMeasure>
     implements CRUDApi<String, DataLakeMeasure> {
@@ -31,7 +32,7 @@ public class DataLakeMeasureApi extends AbstractTypedClientApi<DataLakeMeasure>
     super(clientConfig, DataLakeMeasure.class);
   }
 
-  public DataLakeMeasure get(String id) {
+  public Optional<DataLakeMeasure> get(String id) {
     return getSingle(getBaseResourcePath().addToPath(id));
   }
 
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataProcessorApi.java b/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataProcessorApi.java
index 656d5cf185..64f7ea7ee4 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataProcessorApi.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataProcessorApi.java
@@ -28,6 +28,7 @@ import org.apache.streampipes.messaging.kafka.SpKafkaConsumer;
 import org.apache.streampipes.model.graph.DataProcessorInvocation;
 
 import java.util.List;
+import java.util.Optional;
 
 public class DataProcessorApi extends AbstractTypedClientApi<DataProcessorInvocation>
     implements CRUDApi<String, DataProcessorInvocation> {
@@ -43,7 +44,7 @@ public class DataProcessorApi extends AbstractTypedClientApi<DataProcessorInvoca
   }
 
   @Override
-  public DataProcessorInvocation get(String s) {
+  public Optional<DataProcessorInvocation> get(String s) {
     return getSingle(getBaseResourcePath().addToPath(s));
   }
 
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataSinkApi.java b/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataSinkApi.java
index d53d21047a..694d458d62 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataSinkApi.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataSinkApi.java
@@ -27,6 +27,7 @@ import org.apache.streampipes.messaging.kafka.SpKafkaConsumer;
 import org.apache.streampipes.model.graph.DataSinkInvocation;
 
 import java.util.List;
+import java.util.Optional;
 
 public class DataSinkApi extends AbstractTypedClientApi<DataSinkInvocation>
     implements CRUDApi<String, DataSinkInvocation> {
@@ -36,7 +37,7 @@ public class DataSinkApi extends AbstractTypedClientApi<DataSinkInvocation>
   }
 
   @Override
-  public DataSinkInvocation get(String s) {
+  public Optional<DataSinkInvocation> get(String s) {
     return getSingle(getBaseResourcePath().addToPath(s));
   }
 
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataStreamApi.java b/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataStreamApi.java
index d3b8d28428..dd0c3e8247 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataStreamApi.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/api/DataStreamApi.java
@@ -28,6 +28,7 @@ import org.apache.streampipes.model.message.Message;
 
 import java.net.URLEncoder;
 import java.util.List;
+import java.util.Optional;
 
 public class DataStreamApi extends AbstractTypedClientApi<SpDataStream> implements CRUDApi<String, SpDataStream> {
 
@@ -36,7 +37,7 @@ public class DataStreamApi extends AbstractTypedClientApi<SpDataStream> implemen
   }
 
   @Override
-  public SpDataStream get(String streamId) {
+  public Optional<SpDataStream> get(String streamId) {
     return getSingle(StreamPipesApiPath.fromBaseApiPath()
         .addToPath("streams").addToPath(streamId));
   }
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/api/PipelineApi.java b/streampipes-client/src/main/java/org/apache/streampipes/client/api/PipelineApi.java
index 888c15df79..8cc180057e 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/api/PipelineApi.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/api/PipelineApi.java
@@ -26,6 +26,7 @@ import org.apache.streampipes.model.pipeline.Pipeline;
 import org.apache.streampipes.model.pipeline.PipelineOperationStatus;
 
 import java.util.List;
+import java.util.Optional;
 
 public class PipelineApi extends AbstractTypedClientApi<Pipeline> implements CRUDApi<String, Pipeline> {
 
@@ -34,7 +35,7 @@ public class PipelineApi extends AbstractTypedClientApi<Pipeline> implements CRU
   }
 
   @Override
-  public Pipeline get(String pipelineId) {
+  public Optional<Pipeline> get(String pipelineId) {
     return getSingle(getBaseResourcePath().addToPath(pipelineId));
   }
 
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/api/PipelineElementTemplateApi.java b/streampipes-client/src/main/java/org/apache/streampipes/client/api/PipelineElementTemplateApi.java
index 3038fefdde..33c9c22c53 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/api/PipelineElementTemplateApi.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/api/PipelineElementTemplateApi.java
@@ -22,6 +22,7 @@ import org.apache.streampipes.client.util.StreamPipesApiPath;
 import org.apache.streampipes.model.template.PipelineElementTemplate;
 
 import java.util.List;
+import java.util.Optional;
 
 public class PipelineElementTemplateApi extends AbstractTypedClientApi<PipelineElementTemplate>
     implements CRUDApi<String, PipelineElementTemplate> {
@@ -31,7 +32,7 @@ public class PipelineElementTemplateApi extends AbstractTypedClientApi<PipelineE
   }
 
   @Override
-  public PipelineElementTemplate get(String id) {
+  public Optional<PipelineElementTemplate> get(String id) {
     return getSingle(getBaseResourcePath().addToPath(id));
   }
 
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/http/HttpRequest.java b/streampipes-client/src/main/java/org/apache/streampipes/client/http/HttpRequest.java
index b0ffc92ad5..e34a1225ba 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/http/HttpRequest.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/http/HttpRequest.java
@@ -22,6 +22,7 @@ import org.apache.streampipes.client.model.ClientConnectionUrlResolver;
 import org.apache.streampipes.client.model.StreamPipesClientConfig;
 import org.apache.streampipes.client.serializer.Serializer;
 import org.apache.streampipes.client.util.StreamPipesApiPath;
+import org.apache.streampipes.commons.exceptions.SpHttpErrorStatusCode;
 import org.apache.streampipes.commons.exceptions.SpRuntimeException;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -100,14 +101,19 @@ public abstract class HttpRequest<K, V, T> {
       if (status.getStatusCode() == HttpStatus.SC_OK) {
         return afterRequest(serializer, response.getEntity());
       } else {
-        if (status.getStatusCode() == 401) {
-          throw new SpRuntimeException(
-              " 401 - Access to this resource is forbidden - did you provide a poper API key or client secret?");
-        } else {
-          throw new SpRuntimeException(status.getStatusCode() + " - " + status.getReasonPhrase());
+        switch (status.getStatusCode()) {
+          case HttpStatus.SC_UNAUTHORIZED:
+            throw new SpHttpErrorStatusCode(
+                " 401 - Access to this resource is forbidden - did you provide a poper API key or client secret?",
+                401);
+          case HttpStatus.SC_NOT_FOUND:
+            throw new SpHttpErrorStatusCode(" 404 - The requested resource could not be found.", 404);
+          default:
+            throw new SpHttpErrorStatusCode(status.getStatusCode() + " - " + status.getReasonPhrase(),
+                status.getStatusCode());
         }
       }
-    } catch (IOException | SpRuntimeException e) {
+    } catch (IOException e) {
       throw new SpRuntimeException(
           "Could not connect to the StreamPipes API - please check that StreamPipes is available", e);
     }
diff --git a/streampipes-commons/src/main/java/org/apache/streampipes/commons/exceptions/SpHttpErrorStatusCode.java b/streampipes-commons/src/main/java/org/apache/streampipes/commons/exceptions/SpHttpErrorStatusCode.java
new file mode 100644
index 0000000000..a4188e9664
--- /dev/null
+++ b/streampipes-commons/src/main/java/org/apache/streampipes/commons/exceptions/SpHttpErrorStatusCode.java
@@ -0,0 +1,39 @@
+/*
+ * 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.commons.exceptions;
+
+public class SpHttpErrorStatusCode extends SpRuntimeException {
+
+  private static final long serialVersionUID = 2289470758226670270L;
+  private Integer httpStatusCode;
+
+  /**
+   * Creates a new Exception with the given message and null as the cause.
+   *
+   * @param message The exception message
+   */
+  public SpHttpErrorStatusCode(String message, Integer httpStatusCode) {
+    super(message);
+    this.httpStatusCode = httpStatusCode;
+  }
+
+  public Integer getHttpStatusCode() {
+    return httpStatusCode;
+  }
+}
diff --git a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/adapter/format/util/JsonEventProperty.java b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/adapter/format/util/JsonEventProperty.java
index cc4c4b3604..b4343fb579 100644
--- a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/adapter/format/util/JsonEventProperty.java
+++ b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/adapter/format/util/JsonEventProperty.java
@@ -22,6 +22,7 @@ import org.apache.streampipes.model.schema.EventProperty;
 import org.apache.streampipes.model.schema.EventPropertyList;
 import org.apache.streampipes.model.schema.EventPropertyNested;
 import org.apache.streampipes.model.schema.EventPropertyPrimitive;
+import org.apache.streampipes.model.schema.PropertyScope;
 import org.apache.streampipes.vocabulary.XSD;
 
 import org.slf4j.Logger;
@@ -39,27 +40,15 @@ public class JsonEventProperty {
   public static EventProperty getEventProperty(String key, Object o) {
     EventProperty resultProperty = null;
 
-    LOG.info("Key: " + key);
-    LOG.info("Class: " + o.getClass());
-    LOG.info("Primitive: " + o.getClass().isPrimitive());
-    LOG.info("Array: " + o.getClass().isArray());
-    LOG.info("TypeName: " + o.getClass().getTypeName());
-
-
-
-    if (o.getClass().equals(Boolean.class)) {
-      resultProperty = new EventPropertyPrimitive();
-      resultProperty.setRuntimeName(key);
-      ((EventPropertyPrimitive) resultProperty).setRuntimeType(XSD.BOOLEAN.toString());
+    if (o == null) {
+      resultProperty = makePrimitiveProperty(key, XSD.STRING.toString());
+    } else if (o.getClass().equals(Boolean.class)) {
+      resultProperty = makePrimitiveProperty(key, XSD.BOOLEAN.toString());
     } else if (o.getClass().equals(String.class)) {
-      resultProperty = new EventPropertyPrimitive();
-      resultProperty.setRuntimeName(key);
-      ((EventPropertyPrimitive) resultProperty).setRuntimeType(XSD.STRING.toString());
+      resultProperty = makePrimitiveProperty(key, XSD.STRING.toString());
     } else if (o.getClass().equals(Integer.class) || o.getClass().equals(Double.class)
         || o.getClass().equals(Float.class) || o.getClass().equals(Long.class)) {
-      resultProperty = new EventPropertyPrimitive();
-      resultProperty.setRuntimeName(key);
-      ((EventPropertyPrimitive) resultProperty).setRuntimeType(XSD.FLOAT.toString());
+      resultProperty = makePrimitiveProperty(key, XSD.FLOAT.toString());
     } else if (o.getClass().equals(LinkedHashMap.class)) {
       resultProperty = new EventPropertyNested();
       resultProperty.setRuntimeName(key);
@@ -97,4 +86,14 @@ public class JsonEventProperty {
 
     return resultProperty;
   }
+
+  private static EventProperty makePrimitiveProperty(String key,
+                                                     String runtimeType) {
+    var property = new EventPropertyPrimitive();
+    property.setRuntimeName(key);
+    property.setRuntimeType(runtimeType);
+    property.setPropertyScope(PropertyScope.MEASUREMENT_PROPERTY.name());
+
+    return property;
+  }
 }
diff --git a/streampipes-extensions/streampipes-pipeline-elements-shared/src/main/java/org/apache/streampipes/pe/shared/config/mqtt/MqttConnectUtils.java b/streampipes-extensions/streampipes-pipeline-elements-shared/src/main/java/org/apache/streampipes/pe/shared/config/mqtt/MqttConnectUtils.java
index 4b6d833674..e1b82116a0 100644
--- a/streampipes-extensions/streampipes-pipeline-elements-shared/src/main/java/org/apache/streampipes/pe/shared/config/mqtt/MqttConnectUtils.java
+++ b/streampipes-extensions/streampipes-pipeline-elements-shared/src/main/java/org/apache/streampipes/pe/shared/config/mqtt/MqttConnectUtils.java
@@ -52,11 +52,15 @@ public class MqttConnectUtils {
   }
 
   public static StaticPropertyAlternative getAlternativesOne() {
-    //return Alternatives.from(Labels.from(ANONYMOUS_ACCESS, "Unauthenticated", ""));
     return Alternatives.from(Labels.withId(ANONYMOUS_ACCESS));
 
   }
 
+  public static StaticPropertyAlternative getAlternativesOne(boolean selected) {
+    return Alternatives.from(Labels.withId(ANONYMOUS_ACCESS), selected);
+
+  }
+
   public static StaticPropertyAlternative getAlternativesTwo() {
     return Alternatives.from(Labels.withId(USERNAME_ACCESS),
         StaticProperties.group(Labels.withId(USERNAME_GROUP),
diff --git a/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/mqtt/MqttClient.java b/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/mqtt/MqttClient.java
index 280d980dd2..29c88dfc43 100644
--- a/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/mqtt/MqttClient.java
+++ b/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/mqtt/MqttClient.java
@@ -155,7 +155,9 @@ public class MqttClient {
    */
   public void disconnect() {
     try {
-      this.conn.disconnect();
+      if (this.conn.isConnected()) {
+        this.conn.disconnect();
+      }
     } catch (Exception e) {
       throw new SpRuntimeException("Could not disconnect from MQTT broker: "
           + uri.toString() + ", " + e.getMessage(), e);
diff --git a/streampipes-rest-shared/src/main/java/org/apache/streampipes/rest/shared/impl/AbstractSharedRestInterface.java b/streampipes-rest-shared/src/main/java/org/apache/streampipes/rest/shared/impl/AbstractSharedRestInterface.java
index 4d5b78a9c1..74897e29b0 100644
--- a/streampipes-rest-shared/src/main/java/org/apache/streampipes/rest/shared/impl/AbstractSharedRestInterface.java
+++ b/streampipes-rest-shared/src/main/java/org/apache/streampipes/rest/shared/impl/AbstractSharedRestInterface.java
@@ -31,12 +31,15 @@ public abstract class AbstractSharedRestInterface {
     return error(entity, 400);
   }
 
+  protected <T> Response notFound(T entity) {
+    return error(entity, 404);
+  }
+
   protected <T> Response serverError(T entity) {
     return error(entity, 500);
   }
 
-  protected <T> Response error(T entity,
-                               Integer statusCode) {
+  protected <T> Response error(T entity, Integer statusCode) {
     return Response
         .status(statusCode)
         .entity(entity)
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
index eafa7179d3..b71064f3c7 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
@@ -18,7 +18,6 @@
 
 package org.apache.streampipes.rest.impl;
 
-
 import org.apache.streampipes.commons.exceptions.NoMatchingFormatException;
 import org.apache.streampipes.commons.exceptions.NoMatchingJsonSchemaException;
 import org.apache.streampipes.commons.exceptions.NoMatchingProtocolException;
@@ -76,14 +75,12 @@ public class PipelineResource extends AbstractAuthGuardedRestResource {
   @GET
   @Produces(MediaType.APPLICATION_JSON)
   @JacksonSerialized
-  @Operation(summary = "Get all pipelines of the current user",
-      tags = {"Pipeline"},
-      responses = {
-          @ApiResponse(content = {
-              @Content(
+  @Operation(summary = "Get all pipelines of the current user", tags = {"Pipeline"}, responses = {
+      @ApiResponse(content = {
+          @Content(
                   mediaType = "application/json",
-                  array = @ArraySchema(schema = @Schema(implementation = Pipeline.class)))
-          })})
+                  array = @ArraySchema(schema = @Schema(implementation = Pipeline.class))
+          )})})
   @PreAuthorize(AuthConstants.HAS_READ_PIPELINE_PRIVILEGE)
   @PostFilter("hasPermission(filterObject.pipelineId, 'READ')")
   public List<Pipeline> get() {
@@ -94,8 +91,7 @@ public class PipelineResource extends AbstractAuthGuardedRestResource {
   @Produces(MediaType.APPLICATION_JSON)
   @Path("/{pipelineId}/status")
   @JacksonSerialized
-  @Operation(summary = "Get the pipeline status of a given pipeline",
-      tags = {"Pipeline"})
+  @Operation(summary = "Get the pipeline status of a given pipeline", tags = {"Pipeline"})
   @PreAuthorize(AuthConstants.HAS_READ_PIPELINE_PRIVILEGE)
   public Response getPipelineStatus(@PathParam("pipelineId") String pipelineId) {
     return ok(PipelineStatusManager.getPipelineStatus(pipelineId, 5));
@@ -105,8 +101,7 @@ public class PipelineResource extends AbstractAuthGuardedRestResource {
   @Path("/{pipelineId}")
   @Produces(MediaType.APPLICATION_JSON)
   @JacksonSerialized
-  @Operation(summary = "Delete a pipeline with a given id",
-      tags = {"Pipeline"})
+  @Operation(summary = "Delete a pipeline with a given id", tags = {"Pipeline"})
   @PreAuthorize(AuthConstants.HAS_DELETE_PIPELINE_PRIVILEGE)
   public Response removeOwn(@PathParam("pipelineId") String pipelineId) {
     PipelineManager.deletePipeline(pipelineId);
@@ -117,19 +112,23 @@ public class PipelineResource extends AbstractAuthGuardedRestResource {
   @Path("/{pipelineId}")
   @Produces(MediaType.APPLICATION_JSON)
   @JacksonSerialized
-  @Operation(summary = "Get a specific pipeline with the given id",
-      tags = {"Pipeline"})
+  @Operation(summary = "Get a specific pipeline with the given id", tags = {"Pipeline"})
   @PreAuthorize(AuthConstants.HAS_READ_PIPELINE_PRIVILEGE)
   public Response getElement(@PathParam("pipelineId") String pipelineId) {
-    return ok(PipelineManager.getPipeline(pipelineId));
+    Pipeline foundPipeline = PipelineManager.getPipeline(pipelineId);
+
+    if (foundPipeline == null) {
+      return notFound("Pipeline with " + pipelineId + " not found.");
+    } else {
+      return ok(PipelineManager.getPipeline(pipelineId));
+    }
   }
 
   @Path("/{pipelineId}/start")
   @GET
   @Produces(MediaType.APPLICATION_JSON)
   @JacksonSerialized
-  @Operation(summary = "Start the pipeline with the given id",
-      tags = {"Pipeline"})
+  @Operation(summary = "Start the pipeline with the given id", tags = {"Pipeline"})
   @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
   public Response start(@PathParam("pipelineId") String pipelineId) {
     try {
@@ -146,28 +145,24 @@ public class PipelineResource extends AbstractAuthGuardedRestResource {
   @GET
   @Produces(MediaType.APPLICATION_JSON)
   @JacksonSerialized
-  @Operation(summary = "Stop the pipeline with the given id",
-      tags = {"Pipeline"})
+  @Operation(summary = "Stop the pipeline with the given id", tags = {"Pipeline"})
   @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
   public Response stop(@PathParam("pipelineId") String pipelineId,
-                       @QueryParam("forceStop") @DefaultValue("false") boolean forceStop) {
+             @QueryParam("forceStop") @DefaultValue("false") boolean forceStop) {
     try {
       PipelineOperationStatus status = PipelineManager.stopPipeline(pipelineId, forceStop);
       return ok(status);
-    } catch
-    (Exception e) {
+    } catch (Exception e) {
       e.printStackTrace();
-      return constructErrorMessage(
-          new Notification(NotificationType.UNKNOWN_ERROR.title(), NotificationType.UNKNOWN_ERROR.description(),
-              e.getMessage()));
+      return constructErrorMessage(new Notification(NotificationType.UNKNOWN_ERROR.title(),
+          NotificationType.UNKNOWN_ERROR.description(), e.getMessage()));
     }
   }
 
   @POST
   @Produces(MediaType.APPLICATION_JSON)
   @JacksonSerialized
-  @Operation(summary = "Store a new pipeline",
-      tags = {"Pipeline"})
+  @Operation(summary = "Store a new pipeline", tags = {"Pipeline"})
   @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
   public Response addPipeline(Pipeline pipeline) {
 
@@ -184,21 +179,17 @@ public class PipelineResource extends AbstractAuthGuardedRestResource {
   @Hidden
   @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
   @PostAuthorize("hasPermission(returnObject, 'READ')")
-  public PipelineElementRecommendationMessage recommend(Pipeline pipeline,
-                                                        @PathParam("recId") String baseRecElement) {
+  public PipelineElementRecommendationMessage recommend(Pipeline pipeline, @PathParam("recId") String baseRecElement) {
     try {
       return Operations.findRecommendedElements(pipeline, baseRecElement);
     } catch (JsonSyntaxException e) {
-      throw new WebApplicationException(badRequest(new Notification(NotificationType.UNKNOWN_ERROR,
-          e.getMessage())));
+      throw new WebApplicationException(badRequest(new Notification(NotificationType.UNKNOWN_ERROR, e.getMessage())));
     } catch (NoSuitableSepasAvailableException e) {
-      throw new WebApplicationException(badRequest(new Notification(NotificationType.NO_SEPA_FOUND,
-          e.getMessage())));
+      throw new WebApplicationException(badRequest(new Notification(NotificationType.NO_SEPA_FOUND, e.getMessage())));
     } catch (Exception e) {
       e.printStackTrace();
       throw new WebApplicationException(
-          serverError(constructErrorMessage(new Notification(NotificationType.UNKNOWN_ERROR,
-              e.getMessage()))));
+          serverError(constructErrorMessage(new Notification(NotificationType.UNKNOWN_ERROR, e.getMessage()))));
     }
   }
 
@@ -224,26 +215,20 @@ public class PipelineResource extends AbstractAuthGuardedRestResource {
     try {
       return ok(Operations.validatePipeline(pipeline));
     } catch (JsonSyntaxException e) {
-      return badRequest(new Notification(NotificationType.UNKNOWN_ERROR,
-          e.getMessage()));
+      return badRequest(new Notification(NotificationType.UNKNOWN_ERROR, e.getMessage()));
     } catch (NoMatchingSchemaException e) {
-      return badRequest(new Notification(NotificationType.NO_VALID_CONNECTION,
-          e.getMessage()));
+      return badRequest(new Notification(NotificationType.NO_VALID_CONNECTION, e.getMessage()));
     } catch (NoMatchingFormatException e) {
-      return badRequest(new Notification(NotificationType.NO_MATCHING_FORMAT_CONNECTION,
-          e.getMessage()));
+      return badRequest(new Notification(NotificationType.NO_MATCHING_FORMAT_CONNECTION, e.getMessage()));
     } catch (NoMatchingProtocolException e) {
-      return badRequest(new Notification(NotificationType.NO_MATCHING_PROTOCOL_CONNECTION,
-          e.getMessage()));
+      return badRequest(new Notification(NotificationType.NO_MATCHING_PROTOCOL_CONNECTION, e.getMessage()));
     } catch (RemoteServerNotAccessibleException | NoMatchingJsonSchemaException e) {
-      return serverError(new Notification(NotificationType.REMOTE_SERVER_NOT_ACCESSIBLE
-          , e.getMessage()));
+      return serverError(new Notification(NotificationType.REMOTE_SERVER_NOT_ACCESSIBLE, e.getMessage()));
     } catch (InvalidConnectionException e) {
       return badRequest(e.getErrorLog());
     } catch (Exception e) {
       e.printStackTrace();
-      return serverError(new Notification(NotificationType.UNKNOWN_ERROR,
-          e.getMessage()));
+      return serverError(new Notification(NotificationType.UNKNOWN_ERROR, e.getMessage()));
     }
   }
 
@@ -251,11 +236,9 @@ public class PipelineResource extends AbstractAuthGuardedRestResource {
   @Path("/{pipelineId}")
   @Produces(MediaType.APPLICATION_JSON)
   @JacksonSerialized
-  @Operation(summary = "Update an existing pipeline",
-      tags = {"Pipeline"})
+  @Operation(summary = "Update an existing pipeline", tags = {"Pipeline"})
   @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
-  public Response overwritePipeline(@PathParam("pipelineId") String pipelineId,
-                                    Pipeline pipeline) {
+  public Response overwritePipeline(@PathParam("pipelineId") String pipelineId, Pipeline pipeline) {
     Pipeline storedPipeline = getPipelineStorage().getPipeline(pipelineId);
     if (!storedPipeline.isRunning()) {
       storedPipeline.setStreams(pipeline.getStreams());
@@ -275,14 +258,10 @@ public class PipelineResource extends AbstractAuthGuardedRestResource {
   @Produces(MediaType.APPLICATION_JSON)
   @Path("/contains/{elementId}")
   @JacksonSerialized
-  @Operation(summary = "Returns all pipelines that contain the element with the elementId",
-      tags = {"Pipeline"},
-      responses = {
-          @ApiResponse(content = {
-              @Content(
-                  mediaType = "application/json",
-                  array = @ArraySchema(schema = @Schema(implementation = Pipeline.class)))
-          })})
+  @Operation(summary = "Returns all pipelines that contain the element with the elementId", tags = {
+      "Pipeline"}, responses = {@ApiResponse(content = {
+        @Content(mediaType = "application/json",
+          array = @ArraySchema(schema = @Schema(implementation = Pipeline.class)))})})
   @PreAuthorize(AuthConstants.HAS_READ_PIPELINE_PRIVILEGE)
   @PostFilter("hasPermission(filterObject.pipelineId, 'READ')")
   public List<Pipeline> getPipelinesContainingElement(@PathParam("elementId") String elementId) {
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/ConnectionStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/ConnectionStorageImpl.java
index de2110da94..6402d4b30b 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/ConnectionStorageImpl.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/ConnectionStorageImpl.java
@@ -98,7 +98,8 @@ public class ConnectionStorageImpl extends AbstractDao<Connection> implements
 
   private Optional<JsonObject> getFrequentConnections(String query) {
     try {
-      return Optional.of((JsonObject) new JsonParser().parse(Request.Get(query).execute().returnContent().asString()));
+      var request = Utils.append(Request.Get(query));
+      return Optional.of((JsonObject) new JsonParser().parse(request.execute().returnContent().asString()));
     } catch (IOException e) {
       e.printStackTrace();
       return Optional.empty();
diff --git a/streampipes-wrapper-python/requirements.txt b/streampipes-wrapper-python/requirements.txt
index d029896e1f..3686fb9ffd 100644
--- a/streampipes-wrapper-python/requirements.txt
+++ b/streampipes-wrapper-python/requirements.txt
@@ -1,7 +1,7 @@
 waitress==2.1.2
 click==7.1.2
 confluent-kafka==1.4.2
-Flask==1.1.2
+Flask==2.3.2
 Flask-Classful==0.14.2
 Flask-Negotiate==0.1.0
 idna==2.10
@@ -12,4 +12,4 @@ python-consul==1.1.0
 requests==2.24.0
 six==1.15.0
 urllib3==1.26.5
-Werkzeug==1.0.1
+Werkzeug==2.2.3
diff --git a/streampipes-wrapper-python/setup.py b/streampipes-wrapper-python/setup.py
index ba40ffdf1b..fd4ecf3ee5 100644
--- a/streampipes-wrapper-python/setup.py
+++ b/streampipes-wrapper-python/setup.py
@@ -40,7 +40,7 @@ setup(
     long_description_content_type='text/markdown',
     install_requires=[
         'confluent-kafka==1.4.2',
-        'Flask==1.1.2',
+        'Flask==2.3.2',
         'flask-classful==0.14.2',
         'Flask-Negotiate==0.1.0',
         'waitress==2.1.2',
diff --git a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/function/FunctionContextGenerator.java b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/function/FunctionContextGenerator.java
index c3465b4138..9b56d0d078 100644
--- a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/function/FunctionContextGenerator.java
+++ b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/function/FunctionContextGenerator.java
@@ -25,7 +25,7 @@ import org.apache.streampipes.wrapper.routing.SpOutputCollector;
 
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
+import java.util.Optional;
 
 public class FunctionContextGenerator {
 
@@ -60,6 +60,7 @@ public class FunctionContextGenerator {
     return this.streamIds
         .stream()
         .map(streamId -> client.streams().get(streamId))
-        .collect(Collectors.toList());
+        .flatMap(Optional::stream)
+        .toList();
   }
 }
diff --git a/ui/package-lock.json b/ui/package-lock.json
index e3597d7011..153cae49fb 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -59,7 +59,7 @@
         "ngx-echarts": "^14.0.0",
         "ngx-quill": "^19.0.1",
         "ngx-showdown": "6.0.0",
-        "plotly.js": "^2.18.0",
+        "plotly.js": "^2.20.0",
         "quill": "1.3.7",
         "roboto-fontface": "0.10.0",
         "rxjs": "^7.8.0",
@@ -80,7 +80,7 @@
         "@angular-eslint/template-parser": "^15.2.0",
         "@angular/cli": "^14.2.10",
         "@angular/compiler-cli": "^14.2.12",
-        "@cypress/webpack-batteries-included-preprocessor": "2.3.0",
+        "@cypress/webpack-batteries-included-preprocessor": "2.4.1",
         "@cypress/webpack-preprocessor": "5.17.0",
         "@ngtools/webpack": "^14.2.10",
         "@types/angular": "^1.8.4",
@@ -198,6 +198,427 @@
         "@angular/compiler-cli": "^14.0.0"
       }
     },
+    "node_modules/@angular-builders/custom-webpack/node_modules/@angular-devkit/architect": {
+      "version": "0.1402.11",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.11.tgz",
+      "integrity": "sha512-RuSZrBQ+QbipAESZ4aXCyAMQHaEaDyyV/FDS9J2HJWfEFbRD5oxlEt/tBC8XjmJQsktaUOh07GT8MNJjPKVAQw==",
+      "dev": true,
+      "dependencies": {
+        "@angular-devkit/core": "14.2.11",
+        "rxjs": "6.6.7"
+      },
+      "engines": {
+        "node": "^14.15.0 || >=16.10.0",
+        "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+        "yarn": ">= 1.13.0"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/@angular-devkit/build-angular": {
+      "version": "14.2.11",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.2.11.tgz",
+      "integrity": "sha512-O3X7GXcCBCGceVSHT+GIJ2JrRCg2YcO7HtNavpmPrraNr1o+aCdTkmT5WTS2cqWkZBm/z0wqKR8PsX/ZoD2r1A==",
+      "dev": true,
+      "dependencies": {
+        "@ampproject/remapping": "2.2.0",
+        "@angular-devkit/architect": "0.1402.11",
+        "@angular-devkit/build-webpack": "0.1402.11",
+        "@angular-devkit/core": "14.2.11",
+        "@babel/core": "7.18.10",
+        "@babel/generator": "7.18.12",
+        "@babel/helper-annotate-as-pure": "7.18.6",
+        "@babel/plugin-proposal-async-generator-functions": "7.18.10",
+        "@babel/plugin-transform-async-to-generator": "7.18.6",
+        "@babel/plugin-transform-runtime": "7.18.10",
+        "@babel/preset-env": "7.18.10",
+        "@babel/runtime": "7.18.9",
+        "@babel/template": "7.18.10",
+        "@discoveryjs/json-ext": "0.5.7",
+        "@ngtools/webpack": "14.2.11",
+        "ansi-colors": "4.1.3",
+        "babel-loader": "8.2.5",
+        "babel-plugin-istanbul": "6.1.1",
+        "browserslist": "^4.9.1",
+        "cacache": "16.1.2",
+        "copy-webpack-plugin": "11.0.0",
+        "critters": "0.0.16",
+        "css-loader": "6.7.1",
+        "esbuild-wasm": "0.15.5",
+        "glob": "8.0.3",
+        "https-proxy-agent": "5.0.1",
+        "inquirer": "8.2.4",
+        "jsonc-parser": "3.1.0",
+        "karma-source-map-support": "1.4.0",
+        "less": "4.1.3",
+        "less-loader": "11.0.0",
+        "license-webpack-plugin": "4.0.2",
+        "loader-utils": "3.2.1",
+        "mini-css-extract-plugin": "2.6.1",
+        "minimatch": "5.1.0",
+        "open": "8.4.0",
+        "ora": "5.4.1",
+        "parse5-html-rewriting-stream": "6.0.1",
+        "piscina": "3.2.0",
+        "postcss": "8.4.16",
+        "postcss-import": "15.0.0",
+        "postcss-loader": "7.0.1",
+        "postcss-preset-env": "7.8.0",
+        "regenerator-runtime": "0.13.9",
+        "resolve-url-loader": "5.0.0",
+        "rxjs": "6.6.7",
+        "sass": "1.54.4",
+        "sass-loader": "13.0.2",
+        "semver": "7.3.7",
+        "source-map-loader": "4.0.0",
+        "source-map-support": "0.5.21",
+        "stylus": "0.59.0",
+        "stylus-loader": "7.0.0",
+        "terser": "5.14.2",
+        "text-table": "0.2.0",
+        "tree-kill": "1.2.2",
+        "tslib": "2.4.0",
+        "webpack": "5.76.1",
+        "webpack-dev-middleware": "5.3.3",
+        "webpack-dev-server": "4.11.0",
+        "webpack-merge": "5.8.0",
+        "webpack-subresource-integrity": "5.1.0"
+      },
+      "engines": {
+        "node": "^14.15.0 || >=16.10.0",
+        "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+        "yarn": ">= 1.13.0"
+      },
+      "optionalDependencies": {
+        "esbuild": "0.15.5"
+      },
+      "peerDependencies": {
+        "@angular/compiler-cli": "^14.0.0",
+        "@angular/localize": "^14.0.0",
+        "@angular/service-worker": "^14.0.0",
+        "karma": "^6.3.0",
+        "ng-packagr": "^14.0.0",
+        "protractor": "^7.0.0",
+        "tailwindcss": "^2.0.0 || ^3.0.0",
+        "typescript": ">=4.6.2 <4.9"
+      },
+      "peerDependenciesMeta": {
+        "@angular/localize": {
+          "optional": true
+        },
+        "@angular/service-worker": {
+          "optional": true
+        },
+        "karma": {
+          "optional": true
+        },
+        "ng-packagr": {
+          "optional": true
+        },
+        "protractor": {
+          "optional": true
+        },
+        "tailwindcss": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/@angular-devkit/build-webpack": {
+      "version": "0.1402.11",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.11.tgz",
+      "integrity": "sha512-Ajyg1O6B6JSHsDlPdh165uy3glW4IiUlRXu8VVAOSA88WIT1Dl17f4Oun0/t27ip0/CNceiVY9MzOqIwGL1E6g==",
+      "dev": true,
+      "dependencies": {
+        "@angular-devkit/architect": "0.1402.11",
+        "rxjs": "6.6.7"
+      },
+      "engines": {
+        "node": "^14.15.0 || >=16.10.0",
+        "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+        "yarn": ">= 1.13.0"
+      },
+      "peerDependencies": {
+        "webpack": "^5.30.0",
+        "webpack-dev-server": "^4.0.0"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/@angular-devkit/core": {
+      "version": "14.2.11",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.11.tgz",
+      "integrity": "sha512-cBIGs6y9rykOQqnuAQOB1DgIRyBFYtvKRJb7QNUfIJ0qUfARKkuV/yikv3lrb95ePGkmoRzmjkFqcFZiYU+r7A==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "8.11.0",
+        "ajv-formats": "2.1.1",
+        "jsonc-parser": "3.1.0",
+        "rxjs": "6.6.7",
+        "source-map": "0.7.4"
+      },
+      "engines": {
+        "node": "^14.15.0 || >=16.10.0",
+        "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+        "yarn": ">= 1.13.0"
+      },
+      "peerDependencies": {
+        "chokidar": "^3.5.2"
+      },
+      "peerDependenciesMeta": {
+        "chokidar": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/@babel/core": {
+      "version": "7.18.10",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz",
+      "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==",
+      "dev": true,
+      "dependencies": {
+        "@ampproject/remapping": "^2.1.0",
+        "@babel/code-frame": "^7.18.6",
+        "@babel/generator": "^7.18.10",
+        "@babel/helper-compilation-targets": "^7.18.9",
+        "@babel/helper-module-transforms": "^7.18.9",
+        "@babel/helpers": "^7.18.9",
+        "@babel/parser": "^7.18.10",
+        "@babel/template": "^7.18.10",
+        "@babel/traverse": "^7.18.10",
+        "@babel/types": "^7.18.10",
+        "convert-source-map": "^1.7.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.2",
+        "json5": "^2.2.1",
+        "semver": "^6.3.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/babel"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/@babel/core/node_modules/semver": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/@babel/preset-env": {
+      "version": "7.18.10",
+      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz",
+      "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/compat-data": "^7.18.8",
+        "@babel/helper-compilation-targets": "^7.18.9",
+        "@babel/helper-plugin-utils": "^7.18.9",
+        "@babel/helper-validator-option": "^7.18.6",
+        "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6",
+        "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9",
+        "@babel/plugin-proposal-async-generator-functions": "^7.18.10",
+        "@babel/plugin-proposal-class-properties": "^7.18.6",
+        "@babel/plugin-proposal-class-static-block": "^7.18.6",
+        "@babel/plugin-proposal-dynamic-import": "^7.18.6",
+        "@babel/plugin-proposal-export-namespace-from": "^7.18.9",
+        "@babel/plugin-proposal-json-strings": "^7.18.6",
+        "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9",
+        "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
+        "@babel/plugin-proposal-numeric-separator": "^7.18.6",
+        "@babel/plugin-proposal-object-rest-spread": "^7.18.9",
+        "@babel/plugin-proposal-optional-catch-binding": "^7.18.6",
+        "@babel/plugin-proposal-optional-chaining": "^7.18.9",
+        "@babel/plugin-proposal-private-methods": "^7.18.6",
+        "@babel/plugin-proposal-private-property-in-object": "^7.18.6",
+        "@babel/plugin-proposal-unicode-property-regex": "^7.18.6",
+        "@babel/plugin-syntax-async-generators": "^7.8.4",
+        "@babel/plugin-syntax-class-properties": "^7.12.13",
+        "@babel/plugin-syntax-class-static-block": "^7.14.5",
+        "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+        "@babel/plugin-syntax-export-namespace-from": "^7.8.3",
+        "@babel/plugin-syntax-import-assertions": "^7.18.6",
+        "@babel/plugin-syntax-json-strings": "^7.8.3",
+        "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+        "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+        "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+        "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+        "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+        "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+        "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+        "@babel/plugin-syntax-top-level-await": "^7.14.5",
+        "@babel/plugin-transform-arrow-functions": "^7.18.6",
+        "@babel/plugin-transform-async-to-generator": "^7.18.6",
+        "@babel/plugin-transform-block-scoped-functions": "^7.18.6",
+        "@babel/plugin-transform-block-scoping": "^7.18.9",
+        "@babel/plugin-transform-classes": "^7.18.9",
+        "@babel/plugin-transform-computed-properties": "^7.18.9",
+        "@babel/plugin-transform-destructuring": "^7.18.9",
+        "@babel/plugin-transform-dotall-regex": "^7.18.6",
+        "@babel/plugin-transform-duplicate-keys": "^7.18.9",
+        "@babel/plugin-transform-exponentiation-operator": "^7.18.6",
+        "@babel/plugin-transform-for-of": "^7.18.8",
+        "@babel/plugin-transform-function-name": "^7.18.9",
+        "@babel/plugin-transform-literals": "^7.18.9",
+        "@babel/plugin-transform-member-expression-literals": "^7.18.6",
+        "@babel/plugin-transform-modules-amd": "^7.18.6",
+        "@babel/plugin-transform-modules-commonjs": "^7.18.6",
+        "@babel/plugin-transform-modules-systemjs": "^7.18.9",
+        "@babel/plugin-transform-modules-umd": "^7.18.6",
+        "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6",
+        "@babel/plugin-transform-new-target": "^7.18.6",
+        "@babel/plugin-transform-object-super": "^7.18.6",
+        "@babel/plugin-transform-parameters": "^7.18.8",
+        "@babel/plugin-transform-property-literals": "^7.18.6",
+        "@babel/plugin-transform-regenerator": "^7.18.6",
+        "@babel/plugin-transform-reserved-words": "^7.18.6",
+        "@babel/plugin-transform-shorthand-properties": "^7.18.6",
+        "@babel/plugin-transform-spread": "^7.18.9",
+        "@babel/plugin-transform-sticky-regex": "^7.18.6",
+        "@babel/plugin-transform-template-literals": "^7.18.9",
+        "@babel/plugin-transform-typeof-symbol": "^7.18.9",
+        "@babel/plugin-transform-unicode-escapes": "^7.18.10",
+        "@babel/plugin-transform-unicode-regex": "^7.18.6",
+        "@babel/preset-modules": "^0.1.5",
+        "@babel/types": "^7.18.10",
+        "babel-plugin-polyfill-corejs2": "^0.3.2",
+        "babel-plugin-polyfill-corejs3": "^0.5.3",
+        "babel-plugin-polyfill-regenerator": "^0.4.0",
+        "core-js-compat": "^3.22.1",
+        "semver": "^6.3.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/@babel/preset-env/node_modules/semver": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/@ngtools/webpack": {
+      "version": "14.2.11",
+      "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.2.11.tgz",
+      "integrity": "sha512-4enbLFAp98uTgWYF6OFceQqLcfv2/0brIrNN4iWT9xe/Mh3zdCt+eH42zvNRsqo9WXNWRSLvnx8I924p83LNlw==",
+      "dev": true,
+      "engines": {
+        "node": "^14.15.0 || >=16.10.0",
+        "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+        "yarn": ">= 1.13.0"
+      },
+      "peerDependencies": {
+        "@angular/compiler-cli": "^14.0.0",
+        "typescript": ">=4.6.2 <4.9",
+        "webpack": "^5.54.0"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/ajv": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
+      "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "json-schema-traverse": "^1.0.0",
+        "require-from-string": "^2.0.2",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/babel-loader": {
+      "version": "8.2.5",
+      "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz",
+      "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==",
+      "dev": true,
+      "dependencies": {
+        "find-cache-dir": "^3.3.1",
+        "loader-utils": "^2.0.0",
+        "make-dir": "^3.1.0",
+        "schema-utils": "^2.6.5"
+      },
+      "engines": {
+        "node": ">= 8.9"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0",
+        "webpack": ">=2"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/babel-loader/node_modules/loader-utils": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+      "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+      "dev": true,
+      "dependencies": {
+        "big.js": "^5.2.2",
+        "emojis-list": "^3.0.0",
+        "json5": "^2.1.2"
+      },
+      "engines": {
+        "node": ">=8.9.0"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/rxjs": {
+      "version": "6.6.7",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+      "dev": true,
+      "dependencies": {
+        "tslib": "^1.9.0"
+      },
+      "engines": {
+        "npm": ">=2.0.0"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/rxjs/node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+      "dev": true
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/semver": {
+      "version": "7.3.7",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
+      "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@angular-builders/custom-webpack/node_modules/tslib": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+      "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+      "dev": true
+    },
     "node_modules/@angular-devkit/architect": {
       "version": "0.1402.10",
       "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.10.tgz",
@@ -3876,9 +4297,9 @@
       }
     },
     "node_modules/@cypress/webpack-batteries-included-preprocessor": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/@cypress/webpack-batteries-included-preprocessor/-/webpack-batteries-included-preprocessor-2.3.0.tgz",
-      "integrity": "sha512-gpZc4f35ll6sWARwqLOZ7i1hlAV7eF+qYIPfe5KOJJbHRBh6WGtL8T9DCnuqO5+wf/HTRGEiKFE2YIe3HhkR8g==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@cypress/webpack-batteries-included-preprocessor/-/webpack-batteries-included-preprocessor-2.4.1.tgz",
+      "integrity": "sha512-As6vP+a/BC5cOINyi7s1XfIcgBlXrXwIqriLi3OSoWQXm9BONpM24tGJjFIXZ2KHWTCyAzjFMig8ifc/VWUm/g==",
       "dev": true,
       "dependencies": {
         "@babel/core": "^7.11.1",
@@ -5397,9 +5818,9 @@
       "integrity": "sha512-QOr/t7314XTwgAUDazR+RDcTAWSbkpjDnZJddd9f56jSUA8ptUsyDblAb+sp/O5O1o5Fiu9KpWxVHKuhGUgp5w=="
     },
     "node_modules/@plotly/d3": {
-      "version": "3.8.0",
-      "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.0.tgz",
-      "integrity": "sha512-L10iHgzvw3uSic/nQpYehlNzxUQvImwms5U7S95pJAEhrllzkrdQNy1Mc5DW9ab881Yr4fh300gJztKXWZDfkQ=="
+      "version": "3.8.1",
+      "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.1.tgz",
+      "integrity": "sha512-x49ThEu1FRA00kTso4Jdfyf2byaCPLBGmLjAYQz5OzaPyLUhHesX3/Nfv2OHEhynhdy2UB39DLXq6thYe2L2kg=="
     },
     "node_modules/@plotly/d3-sankey": {
       "version": "0.7.2",
@@ -15562,9 +15983,9 @@
       }
     },
     "node_modules/is-mobile": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-3.1.1.tgz",
-      "integrity": "sha512-RRoXXR2HNFxNkUnxtaBdGBXtFlUMFa06S0NUKf/LCF+MuGLu13gi9iBCkoEmc6+rpXuwi5Mso5V8Zf7mNynMBQ=="
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-4.0.0.tgz",
+      "integrity": "sha512-mlcHZA84t1qLSuWkt2v0I2l61PYdyQDt4aG1mLIXF5FDMm4+haBCxCPYSr/uwqQNRk1MiTizn0ypEuRAOLRAew=="
     },
     "node_modules/is-module": {
       "version": "1.0.0",
@@ -17176,9 +17597,9 @@
       }
     },
     "node_modules/lint-staged/node_modules/yaml": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz",
-      "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==",
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
+      "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
       "dev": true,
       "engines": {
         "node": ">= 14"
@@ -20220,11 +20641,11 @@
       }
     },
     "node_modules/plotly.js": {
-      "version": "2.18.0",
-      "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-2.18.0.tgz",
-      "integrity": "sha512-EauUKlr03yI7v2K2BA0NobvJb1Na+uid0ZFKbLehpiR/WSnGO7tf5/cKnXl4TEqGYKnytj07Poek6JlXZC3jbg==",
+      "version": "2.22.0",
+      "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-2.22.0.tgz",
+      "integrity": "sha512-g+YTMnChFDx/fNUV2rqkrf87wKwKuOhqv9LIxhmPyF94RGM4v4blP0mmgwQ278qv3kXmfTMJecIUusr1gzscng==",
       "dependencies": {
-        "@plotly/d3": "3.8.0",
+        "@plotly/d3": "3.8.1",
         "@plotly/d3-sankey": "0.7.2",
         "@plotly/d3-sankey-circular": "0.33.1",
         "@turf/area": "^6.4.0",
@@ -20241,7 +20662,7 @@
         "d3-geo": "^1.12.1",
         "d3-geo-projection": "^2.9.0",
         "d3-hierarchy": "^1.1.9",
-        "d3-interpolate": "^1.4.0",
+        "d3-interpolate": "^3.0.1",
         "d3-time": "^1.1.0",
         "d3-time-format": "^2.2.3",
         "fast-isnumeric": "^1.1.4",
@@ -20250,7 +20671,7 @@
         "glslify": "^7.1.1",
         "has-hover": "^1.0.1",
         "has-passive-events": "^1.0.0",
-        "is-mobile": "^3.1.1",
+        "is-mobile": "^4.0.0",
         "mapbox-gl": "1.10.1",
         "mouse-change": "^1.4.0",
         "mouse-event-offset": "^3.0.2",
@@ -20275,11 +20696,6 @@
         "world-calendars": "^1.0.3"
       }
     },
-    "node_modules/plotly.js/node_modules/d3-color": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz",
-      "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q=="
-    },
     "node_modules/plotly.js/node_modules/d3-format": {
       "version": "1.4.5",
       "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
@@ -20291,11 +20707,14 @@
       "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ=="
     },
     "node_modules/plotly.js/node_modules/d3-interpolate": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz",
-      "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==",
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
       "dependencies": {
-        "d3-color": "1"
+        "d3-color": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
       }
     },
     "node_modules/plotly.js/node_modules/d3-time": {
@@ -26678,9 +27097,9 @@
       "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
     },
     "node_modules/webpack": {
-      "version": "5.76.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz",
-      "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==",
+      "version": "5.76.1",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz",
+      "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==",
       "dev": true,
       "dependencies": {
         "@types/eslint-scope": "^3.7.3",
@@ -27454,6 +27873,319 @@
         "ts-node": "^10.0.0",
         "tsconfig-paths": "^3.9.0",
         "webpack-merge": "^5.7.3"
+      },
+      "dependencies": {
+        "@angular-devkit/architect": {
+          "version": "0.1402.11",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1402.11.tgz",
+          "integrity": "sha512-RuSZrBQ+QbipAESZ4aXCyAMQHaEaDyyV/FDS9J2HJWfEFbRD5oxlEt/tBC8XjmJQsktaUOh07GT8MNJjPKVAQw==",
+          "dev": true,
+          "requires": {
+            "@angular-devkit/core": "14.2.11",
+            "rxjs": "6.6.7"
+          }
+        },
+        "@angular-devkit/build-angular": {
+          "version": "14.2.11",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-14.2.11.tgz",
+          "integrity": "sha512-O3X7GXcCBCGceVSHT+GIJ2JrRCg2YcO7HtNavpmPrraNr1o+aCdTkmT5WTS2cqWkZBm/z0wqKR8PsX/ZoD2r1A==",
+          "dev": true,
+          "requires": {
+            "@ampproject/remapping": "2.2.0",
+            "@angular-devkit/architect": "0.1402.11",
+            "@angular-devkit/build-webpack": "0.1402.11",
+            "@angular-devkit/core": "14.2.11",
+            "@babel/core": "7.18.10",
+            "@babel/generator": "7.18.12",
+            "@babel/helper-annotate-as-pure": "7.18.6",
+            "@babel/plugin-proposal-async-generator-functions": "7.18.10",
+            "@babel/plugin-transform-async-to-generator": "7.18.6",
+            "@babel/plugin-transform-runtime": "7.18.10",
+            "@babel/preset-env": "7.18.10",
+            "@babel/runtime": "7.18.9",
+            "@babel/template": "7.18.10",
+            "@discoveryjs/json-ext": "0.5.7",
+            "@ngtools/webpack": "14.2.11",
+            "ansi-colors": "4.1.3",
+            "babel-loader": "8.2.5",
+            "babel-plugin-istanbul": "6.1.1",
+            "browserslist": "^4.9.1",
+            "cacache": "16.1.2",
+            "copy-webpack-plugin": "11.0.0",
+            "critters": "0.0.16",
+            "css-loader": "6.7.1",
+            "esbuild": "0.15.5",
+            "esbuild-wasm": "0.15.5",
+            "glob": "8.0.3",
+            "https-proxy-agent": "5.0.1",
+            "inquirer": "8.2.4",
+            "jsonc-parser": "3.1.0",
+            "karma-source-map-support": "1.4.0",
+            "less": "4.1.3",
+            "less-loader": "11.0.0",
+            "license-webpack-plugin": "4.0.2",
+            "loader-utils": "3.2.1",
+            "mini-css-extract-plugin": "2.6.1",
+            "minimatch": "5.1.0",
+            "open": "8.4.0",
+            "ora": "5.4.1",
+            "parse5-html-rewriting-stream": "6.0.1",
+            "piscina": "3.2.0",
+            "postcss": "8.4.16",
+            "postcss-import": "15.0.0",
+            "postcss-loader": "7.0.1",
+            "postcss-preset-env": "7.8.0",
+            "regenerator-runtime": "0.13.9",
+            "resolve-url-loader": "5.0.0",
+            "rxjs": "6.6.7",
+            "sass": "1.54.4",
+            "sass-loader": "13.0.2",
+            "semver": "7.3.7",
+            "source-map-loader": "4.0.0",
+            "source-map-support": "0.5.21",
+            "stylus": "0.59.0",
+            "stylus-loader": "7.0.0",
+            "terser": "5.14.2",
+            "text-table": "0.2.0",
+            "tree-kill": "1.2.2",
+            "tslib": "2.4.0",
+            "webpack": "5.76.1",
+            "webpack-dev-middleware": "5.3.3",
+            "webpack-dev-server": "4.11.0",
+            "webpack-merge": "5.8.0",
+            "webpack-subresource-integrity": "5.1.0"
+          }
+        },
+        "@angular-devkit/build-webpack": {
+          "version": "0.1402.11",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.11.tgz",
+          "integrity": "sha512-Ajyg1O6B6JSHsDlPdh165uy3glW4IiUlRXu8VVAOSA88WIT1Dl17f4Oun0/t27ip0/CNceiVY9MzOqIwGL1E6g==",
+          "dev": true,
+          "requires": {
+            "@angular-devkit/architect": "0.1402.11",
+            "rxjs": "6.6.7"
+          }
+        },
+        "@angular-devkit/core": {
+          "version": "14.2.11",
+          "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.11.tgz",
+          "integrity": "sha512-cBIGs6y9rykOQqnuAQOB1DgIRyBFYtvKRJb7QNUfIJ0qUfARKkuV/yikv3lrb95ePGkmoRzmjkFqcFZiYU+r7A==",
+          "dev": true,
+          "requires": {
+            "ajv": "8.11.0",
+            "ajv-formats": "2.1.1",
+            "jsonc-parser": "3.1.0",
+            "rxjs": "6.6.7",
+            "source-map": "0.7.4"
+          }
+        },
+        "@babel/core": {
+          "version": "7.18.10",
+          "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz",
+          "integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==",
+          "dev": true,
+          "requires": {
+            "@ampproject/remapping": "^2.1.0",
+            "@babel/code-frame": "^7.18.6",
+            "@babel/generator": "^7.18.10",
+            "@babel/helper-compilation-targets": "^7.18.9",
+            "@babel/helper-module-transforms": "^7.18.9",
+            "@babel/helpers": "^7.18.9",
+            "@babel/parser": "^7.18.10",
+            "@babel/template": "^7.18.10",
+            "@babel/traverse": "^7.18.10",
+            "@babel/types": "^7.18.10",
+            "convert-source-map": "^1.7.0",
+            "debug": "^4.1.0",
+            "gensync": "^1.0.0-beta.2",
+            "json5": "^2.2.1",
+            "semver": "^6.3.0"
+          },
+          "dependencies": {
+            "semver": {
+              "version": "6.3.0",
+              "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+              "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+              "dev": true
+            }
+          }
+        },
+        "@babel/preset-env": {
+          "version": "7.18.10",
+          "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.18.10.tgz",
+          "integrity": "sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==",
+          "dev": true,
+          "requires": {
+            "@babel/compat-data": "^7.18.8",
+            "@babel/helper-compilation-targets": "^7.18.9",
+            "@babel/helper-plugin-utils": "^7.18.9",
+            "@babel/helper-validator-option": "^7.18.6",
+            "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6",
+            "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9",
+            "@babel/plugin-proposal-async-generator-functions": "^7.18.10",
+            "@babel/plugin-proposal-class-properties": "^7.18.6",
+            "@babel/plugin-proposal-class-static-block": "^7.18.6",
+            "@babel/plugin-proposal-dynamic-import": "^7.18.6",
+            "@babel/plugin-proposal-export-namespace-from": "^7.18.9",
+            "@babel/plugin-proposal-json-strings": "^7.18.6",
+            "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9",
+            "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
+            "@babel/plugin-proposal-numeric-separator": "^7.18.6",
+            "@babel/plugin-proposal-object-rest-spread": "^7.18.9",
+            "@babel/plugin-proposal-optional-catch-binding": "^7.18.6",
+            "@babel/plugin-proposal-optional-chaining": "^7.18.9",
+            "@babel/plugin-proposal-private-methods": "^7.18.6",
+            "@babel/plugin-proposal-private-property-in-object": "^7.18.6",
+            "@babel/plugin-proposal-unicode-property-regex": "^7.18.6",
+            "@babel/plugin-syntax-async-generators": "^7.8.4",
+            "@babel/plugin-syntax-class-properties": "^7.12.13",
+            "@babel/plugin-syntax-class-static-block": "^7.14.5",
+            "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+            "@babel/plugin-syntax-export-namespace-from": "^7.8.3",
+            "@babel/plugin-syntax-import-assertions": "^7.18.6",
+            "@babel/plugin-syntax-json-strings": "^7.8.3",
+            "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+            "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+            "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+            "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+            "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+            "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+            "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+            "@babel/plugin-syntax-top-level-await": "^7.14.5",
+            "@babel/plugin-transform-arrow-functions": "^7.18.6",
+            "@babel/plugin-transform-async-to-generator": "^7.18.6",
+            "@babel/plugin-transform-block-scoped-functions": "^7.18.6",
+            "@babel/plugin-transform-block-scoping": "^7.18.9",
+            "@babel/plugin-transform-classes": "^7.18.9",
+            "@babel/plugin-transform-computed-properties": "^7.18.9",
+            "@babel/plugin-transform-destructuring": "^7.18.9",
+            "@babel/plugin-transform-dotall-regex": "^7.18.6",
+            "@babel/plugin-transform-duplicate-keys": "^7.18.9",
+            "@babel/plugin-transform-exponentiation-operator": "^7.18.6",
+            "@babel/plugin-transform-for-of": "^7.18.8",
+            "@babel/plugin-transform-function-name": "^7.18.9",
+            "@babel/plugin-transform-literals": "^7.18.9",
+            "@babel/plugin-transform-member-expression-literals": "^7.18.6",
+            "@babel/plugin-transform-modules-amd": "^7.18.6",
+            "@babel/plugin-transform-modules-commonjs": "^7.18.6",
+            "@babel/plugin-transform-modules-systemjs": "^7.18.9",
+            "@babel/plugin-transform-modules-umd": "^7.18.6",
+            "@babel/plugin-transform-named-capturing-groups-regex": "^7.18.6",
+            "@babel/plugin-transform-new-target": "^7.18.6",
+            "@babel/plugin-transform-object-super": "^7.18.6",
+            "@babel/plugin-transform-parameters": "^7.18.8",
+            "@babel/plugin-transform-property-literals": "^7.18.6",
+            "@babel/plugin-transform-regenerator": "^7.18.6",
+            "@babel/plugin-transform-reserved-words": "^7.18.6",
+            "@babel/plugin-transform-shorthand-properties": "^7.18.6",
+            "@babel/plugin-transform-spread": "^7.18.9",
+            "@babel/plugin-transform-sticky-regex": "^7.18.6",
+            "@babel/plugin-transform-template-literals": "^7.18.9",
+            "@babel/plugin-transform-typeof-symbol": "^7.18.9",
+            "@babel/plugin-transform-unicode-escapes": "^7.18.10",
+            "@babel/plugin-transform-unicode-regex": "^7.18.6",
+            "@babel/preset-modules": "^0.1.5",
+            "@babel/types": "^7.18.10",
+            "babel-plugin-polyfill-corejs2": "^0.3.2",
+            "babel-plugin-polyfill-corejs3": "^0.5.3",
+            "babel-plugin-polyfill-regenerator": "^0.4.0",
+            "core-js-compat": "^3.22.1",
+            "semver": "^6.3.0"
+          },
+          "dependencies": {
+            "semver": {
+              "version": "6.3.0",
+              "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+              "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+              "dev": true
+            }
+          }
+        },
+        "@ngtools/webpack": {
+          "version": "14.2.11",
+          "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.2.11.tgz",
+          "integrity": "sha512-4enbLFAp98uTgWYF6OFceQqLcfv2/0brIrNN4iWT9xe/Mh3zdCt+eH42zvNRsqo9WXNWRSLvnx8I924p83LNlw==",
+          "dev": true,
+          "requires": {}
+        },
+        "ajv": {
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
+          "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
+          "dev": true,
+          "requires": {
+            "fast-deep-equal": "^3.1.1",
+            "json-schema-traverse": "^1.0.0",
+            "require-from-string": "^2.0.2",
+            "uri-js": "^4.2.2"
+          }
+        },
+        "babel-loader": {
+          "version": "8.2.5",
+          "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz",
+          "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==",
+          "dev": true,
+          "requires": {
+            "find-cache-dir": "^3.3.1",
+            "loader-utils": "^2.0.0",
+            "make-dir": "^3.1.0",
+            "schema-utils": "^2.6.5"
+          },
+          "dependencies": {
+            "loader-utils": {
+              "version": "2.0.4",
+              "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+              "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+              "dev": true,
+              "requires": {
+                "big.js": "^5.2.2",
+                "emojis-list": "^3.0.0",
+                "json5": "^2.1.2"
+              }
+            }
+          }
+        },
+        "lru-cache": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+          "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+          "dev": true,
+          "requires": {
+            "yallist": "^4.0.0"
+          }
+        },
+        "rxjs": {
+          "version": "6.6.7",
+          "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+          "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.0"
+          },
+          "dependencies": {
+            "tslib": {
+              "version": "1.14.1",
+              "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+              "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+              "dev": true
+            }
+          }
+        },
+        "semver": {
+          "version": "7.3.7",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
+          "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^6.0.0"
+          }
+        },
+        "tslib": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+          "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+          "dev": true
+        }
       }
     },
     "@angular-devkit/architect": {
@@ -29978,9 +30710,9 @@
       }
     },
     "@cypress/webpack-batteries-included-preprocessor": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/@cypress/webpack-batteries-included-preprocessor/-/webpack-batteries-included-preprocessor-2.3.0.tgz",
-      "integrity": "sha512-gpZc4f35ll6sWARwqLOZ7i1hlAV7eF+qYIPfe5KOJJbHRBh6WGtL8T9DCnuqO5+wf/HTRGEiKFE2YIe3HhkR8g==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/@cypress/webpack-batteries-included-preprocessor/-/webpack-batteries-included-preprocessor-2.4.1.tgz",
+      "integrity": "sha512-As6vP+a/BC5cOINyi7s1XfIcgBlXrXwIqriLi3OSoWQXm9BONpM24tGJjFIXZ2KHWTCyAzjFMig8ifc/VWUm/g==",
       "dev": true,
       "requires": {
         "@babel/core": "^7.11.1",
@@ -31211,9 +31943,9 @@
       "integrity": "sha512-QOr/t7314XTwgAUDazR+RDcTAWSbkpjDnZJddd9f56jSUA8ptUsyDblAb+sp/O5O1o5Fiu9KpWxVHKuhGUgp5w=="
     },
     "@plotly/d3": {
-      "version": "3.8.0",
-      "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.0.tgz",
-      "integrity": "sha512-L10iHgzvw3uSic/nQpYehlNzxUQvImwms5U7S95pJAEhrllzkrdQNy1Mc5DW9ab881Yr4fh300gJztKXWZDfkQ=="
+      "version": "3.8.1",
+      "resolved": "https://registry.npmjs.org/@plotly/d3/-/d3-3.8.1.tgz",
+      "integrity": "sha512-x49ThEu1FRA00kTso4Jdfyf2byaCPLBGmLjAYQz5OzaPyLUhHesX3/Nfv2OHEhynhdy2UB39DLXq6thYe2L2kg=="
     },
     "@plotly/d3-sankey": {
       "version": "0.7.2",
@@ -39327,9 +40059,9 @@
       "dev": true
     },
     "is-mobile": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-3.1.1.tgz",
-      "integrity": "sha512-RRoXXR2HNFxNkUnxtaBdGBXtFlUMFa06S0NUKf/LCF+MuGLu13gi9iBCkoEmc6+rpXuwi5Mso5V8Zf7mNynMBQ=="
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-4.0.0.tgz",
+      "integrity": "sha512-mlcHZA84t1qLSuWkt2v0I2l61PYdyQDt4aG1mLIXF5FDMm4+haBCxCPYSr/uwqQNRk1MiTizn0ypEuRAOLRAew=="
     },
     "is-module": {
       "version": "1.0.0",
@@ -40499,9 +41231,9 @@
           "dev": true
         },
         "yaml": {
-          "version": "2.2.1",
-          "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz",
-          "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==",
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
+          "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
           "dev": true
         }
       }
@@ -42913,11 +43645,11 @@
       }
     },
     "plotly.js": {
-      "version": "2.18.0",
-      "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-2.18.0.tgz",
-      "integrity": "sha512-EauUKlr03yI7v2K2BA0NobvJb1Na+uid0ZFKbLehpiR/WSnGO7tf5/cKnXl4TEqGYKnytj07Poek6JlXZC3jbg==",
+      "version": "2.22.0",
+      "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-2.22.0.tgz",
+      "integrity": "sha512-g+YTMnChFDx/fNUV2rqkrf87wKwKuOhqv9LIxhmPyF94RGM4v4blP0mmgwQ278qv3kXmfTMJecIUusr1gzscng==",
       "requires": {
-        "@plotly/d3": "3.8.0",
+        "@plotly/d3": "3.8.1",
         "@plotly/d3-sankey": "0.7.2",
         "@plotly/d3-sankey-circular": "0.33.1",
         "@turf/area": "^6.4.0",
@@ -42934,7 +43666,7 @@
         "d3-geo": "^1.12.1",
         "d3-geo-projection": "^2.9.0",
         "d3-hierarchy": "^1.1.9",
-        "d3-interpolate": "^1.4.0",
+        "d3-interpolate": "^3.0.1",
         "d3-time": "^1.1.0",
         "d3-time-format": "^2.2.3",
         "fast-isnumeric": "^1.1.4",
@@ -42943,7 +43675,7 @@
         "glslify": "^7.1.1",
         "has-hover": "^1.0.1",
         "has-passive-events": "^1.0.0",
-        "is-mobile": "^3.1.1",
+        "is-mobile": "^4.0.0",
         "mapbox-gl": "1.10.1",
         "mouse-change": "^1.4.0",
         "mouse-event-offset": "^3.0.2",
@@ -42968,11 +43700,6 @@
         "world-calendars": "^1.0.3"
       },
       "dependencies": {
-        "d3-color": {
-          "version": "1.4.1",
-          "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz",
-          "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q=="
-        },
         "d3-format": {
           "version": "1.4.5",
           "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
@@ -42984,11 +43711,11 @@
           "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ=="
         },
         "d3-interpolate": {
-          "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz",
-          "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==",
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+          "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
           "requires": {
-            "d3-color": "1"
+            "d3-color": "1 - 3"
           }
         },
         "d3-time": {
@@ -47995,9 +48722,9 @@
       "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
     },
     "webpack": {
-      "version": "5.76.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz",
-      "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==",
+      "version": "5.76.1",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz",
+      "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==",
       "dev": true,
       "requires": {
         "@types/eslint-scope": "^3.7.3",
diff --git a/ui/package.json b/ui/package.json
index 785a4cefd1..a6fb2e131a 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -81,7 +81,7 @@
     "ngx-echarts": "^14.0.0",
     "ngx-quill": "^19.0.1",
     "ngx-showdown": "6.0.0",
-    "plotly.js": "^2.18.0",
+    "plotly.js": "^2.20.0",
     "quill": "1.3.7",
     "roboto-fontface": "0.10.0",
     "rxjs": "^7.8.0",
@@ -102,7 +102,7 @@
     "@angular-eslint/template-parser": "^15.2.0",
     "@angular/cli": "^14.2.10",
     "@angular/compiler-cli": "^14.2.12",
-    "@cypress/webpack-batteries-included-preprocessor": "2.3.0",
+    "@cypress/webpack-batteries-included-preprocessor": "2.4.1",
     "@cypress/webpack-preprocessor": "5.17.0",
     "@ngtools/webpack": "^14.2.10",
     "@types/angular": "^1.8.4",
diff --git a/ui/projects/streampipes/platform-services/package.json b/ui/projects/streampipes/platform-services/package.json
index 9cb36e4211..3ababad319 100644
--- a/ui/projects/streampipes/platform-services/package.json
+++ b/ui/projects/streampipes/platform-services/package.json
@@ -12,7 +12,7 @@
   },
   "devDependencies": {
     "karma": "~6.4.1",
-    "karma-chrome-launcher": "~3.1.0",
+    "karma-chrome-launcher": "~3.2.0",
     "karma-coverage-istanbul-reporter": "~3.0.2",
     "karma-firefox-launcher": "^2.1.2",
     "karma-jasmine": "~5.1.0",
diff --git a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-property-row/event-property-row.component.ts b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-property-row/event-property-row.component.ts
index 04f37751f7..5b0261a16d 100644
--- a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-property-row/event-property-row.component.ts
+++ b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-property-row/event-property-row.component.ts
@@ -86,7 +86,10 @@ export class EventPropertyRowComponent implements OnInit {
         this.timestampProperty = this.isTimestampProperty(this.node.data);
 
         if (this.node.data instanceof EventProperty) {
-            this.originalRuntimeName = this.findOriginalProperty().runtimeName;
+            const originalProperty = this.findOriginalProperty(
+                this.originalEventSchema.eventProperties,
+            );
+            this.originalRuntimeName = originalProperty.runtimeName;
             this.showFieldStatus =
                 this.fieldStatusInfo &&
                 this.fieldStatusInfo[this.originalRuntimeName] !== undefined;
@@ -96,7 +99,7 @@ export class EventPropertyRowComponent implements OnInit {
                 this.eventPreview[0][this.originalRuntimeName] !== undefined;
             if (this.isPrimitive) {
                 this.originalRuntimeType = this.parseType(
-                    this.findOriginalProperty().runtimeType,
+                    originalProperty.runtimeType,
                 );
                 this.runtimeType = this.parseType(
                     (this.node.data as EventPropertyPrimitive).runtimeType,
@@ -109,10 +112,21 @@ export class EventPropertyRowComponent implements OnInit {
         }
     }
 
-    private findOriginalProperty(): any {
-        return this.originalEventSchema.eventProperties.find(
-            ep => ep.elementId === this.node.data.elementId,
-        );
+    private findOriginalProperty(properties: EventPropertyUnion[]): any {
+        let result: EventPropertyUnion | undefined;
+
+        for (const property of properties) {
+            if (property.elementId === this.node.data.elementId) {
+                result = property;
+                break;
+            } else if (property instanceof EventPropertyNested) {
+                result = this.findOriginalProperty(property.eventProperties);
+                if (result) {
+                    break;
+                }
+            }
+        }
+        return result;
     }
 
     private parseType(runtimeType: string) {
diff --git a/ui/src/app/connect/dialog/edit-event-property/components/edit-schema-transformation/edit-schema-transformation.component.html b/ui/src/app/connect/dialog/edit-event-property/components/edit-schema-transformation/edit-schema-transformation.component.html
index ceb0765434..8ca237642b 100644
--- a/ui/src/app/connect/dialog/edit-event-property/components/edit-schema-transformation/edit-schema-transformation.component.html
+++ b/ui/src/app/connect/dialog/edit-event-property/components/edit-schema-transformation/edit-schema-transformation.component.html
@@ -72,11 +72,12 @@
                     [matAutocompleteDisabled]="isTimestampProperty"
                     [formControl]="domainPropertyControl"
                 />
-                <mat-autocomplete #st="matAutocomplete" [panelWidth]="'300px'">
+                <mat-autocomplete #st="matAutocomplete" [panelWidth]="'400px'">
                     <mat-option
                         *ngFor="let semanticType of semanticTypes | async"
                         [value]="semanticType"
                         style="font-size: 10pt"
+                        [matTooltip]="semanticType"
                     >
                         {{ semanticType }}
                     </mat-option>


Test patch:
diff --git a/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/adapter/TestUtils.java b/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/adapter/TestUtils.java
index a6deadc935..a25b6f5f5c 100644
--- a/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/adapter/TestUtils.java
+++ b/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/adapter/TestUtils.java
@@ -40,6 +40,15 @@ public class TestUtils {
     return jsonObject;
   }
 
+  public static String makeNestedJsonObject() {
+    return "{\"device\": "
+        + "{"
+        + "\"uuid\": \"uuid\", "
+        + "\"name\": null, "
+        + "\"version\": \"1.2.3\"} "
+        + "}";
+  }
+
   public static JsonArray makeJsonArray(JsonObject... jsonObjects) {
     JsonArray jsonArray = new JsonArray();
     Arrays.stream(jsonObjects).forEach(jsonArray::add);
diff --git a/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/adapter/format/json/object/JsonObjectParserTest.java b/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/adapter/format/json/object/JsonObjectParserTest.java
index df0b4faeb9..d70c122c65 100644
--- a/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/adapter/format/json/object/JsonObjectParserTest.java
+++ b/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/adapter/format/json/object/JsonObjectParserTest.java
@@ -18,12 +18,15 @@
 
 package org.apache.streampipes.extensions.management.connect.adapter.format.json.object;
 
+import org.apache.streampipes.extensions.management.connect.adapter.TestUtils;
+
 import com.google.gson.JsonObject;
 import org.apache.commons.io.IOUtils;
 import org.junit.Test;
 
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.Collections;
 import java.util.List;
 
 import static org.apache.streampipes.extensions.management.connect.adapter.TestUtils.makeJsonObject;
@@ -61,6 +64,15 @@ public class JsonObjectParserTest {
     assertEquals("{\"one\":1}", parsedStringEventOne);
   }
 
+  @Test
+  public void parseNestedSchemaWithNullValues() {
+    JsonObjectParser parser = new JsonObjectParser();
+
+    var guessInfo = parser.getSchemaAndSample(Collections.singletonList(TestUtils.makeNestedJsonObject().getBytes()));
+
+    assertEquals(guessInfo.getEventSchema().getEventProperties().size(), 1);
+  }
+
   private InputStream getInputStream(String s) {
     return IOUtils.toInputStream(s, "UTF-8");
   }
diff --git a/streampipes-integration-tests/pom.xml b/streampipes-integration-tests/pom.xml
index 93d8e2ea7d..60309278e3 100644
--- a/streampipes-integration-tests/pom.xml
+++ b/streampipes-integration-tests/pom.xml
@@ -49,6 +49,11 @@
             <artifactId>pulsar-client</artifactId>
             <version>${pulsar.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.streampipes</groupId>
+            <artifactId>streampipes-messaging-mqtt</artifactId>
+            <version>0.92.0-SNAPSHOT</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.streampipes</groupId>
             <artifactId>streampipes-connect-adapters-iiot</artifactId>
@@ -56,6 +61,11 @@
             <classifier>embed</classifier>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.streampipes</groupId>
+            <artifactId>streampipes-pipeline-elements-shared</artifactId>
+            <version>0.92.0-SNAPSHOT</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.streampipes</groupId>
             <artifactId>streampipes-service-discovery</artifactId>
diff --git a/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/AdapterTesterBase.java b/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/AdapterTesterBase.java
index e2134bf352..1140dc5163 100644
--- a/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/AdapterTesterBase.java
+++ b/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/AdapterTesterBase.java
@@ -17,6 +17,7 @@
  */
 package org.apache.streampipes.integration.adapters;
 
+import org.apache.streampipes.connect.iiot.protocol.stream.MqttProtocol;
 import org.apache.streampipes.connect.iiot.protocol.stream.pulsar.PulsarProtocol;
 import org.apache.streampipes.extensions.api.connect.exception.AdapterException;
 import org.apache.streampipes.extensions.management.connect.AdapterUtils;
@@ -32,7 +33,7 @@ public abstract class AdapterTesterBase implements AutoCloseable {
   Adapter adapter;
 
   public Adapter startAdapter(AdapterDescription adapterDescription) throws AdapterException {
-    DeclarersSingleton.getInstance().add(new PulsarProtocol());
+    DeclarersSingleton.getInstance().add(new PulsarProtocol()).add(new MqttProtocol());
     Adapter adapter = (Adapter) AdapterUtils.setAdapter(adapterDescription);
     adapter.startAdapter();
     this.adapter = adapter;
diff --git a/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/AdaptersTest.java b/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/AdaptersTest.java
index 9d1801294b..8b64157e43 100644
--- a/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/AdaptersTest.java
+++ b/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/AdaptersTest.java
@@ -39,4 +39,11 @@ public class AdaptersTest {
       testAdapter(pulsarAdapterTester);
     }
   }
+
+  @Test
+  public void testMqttAdapter() throws Exception {
+    try (MqttAdapterTester mqttAdapterTester = new MqttAdapterTester()) {
+      testAdapter(mqttAdapterTester);
+    }
+  }
 }
diff --git a/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/MqttAdapterTester.java b/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/MqttAdapterTester.java
new file mode 100644
index 0000000000..0d915ed996
--- /dev/null
+++ b/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/adapters/MqttAdapterTester.java
@@ -0,0 +1,129 @@
+/*
+ * 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.integration.adapters;
+
+import org.apache.streampipes.connect.iiot.protocol.stream.MqttProtocol;
+import org.apache.streampipes.extensions.api.connect.exception.AdapterException;
+import org.apache.streampipes.extensions.management.connect.adapter.format.json.object.JsonObjectFormat;
+import org.apache.streampipes.integration.containers.MosquittoContainer;
+import org.apache.streampipes.integration.containers.MosquittoDevContainer;
+import org.apache.streampipes.messaging.mqtt.MqttPublisher;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.model.connect.rules.DebugSinkRuleDescription;
+import org.apache.streampipes.model.grounding.MqttTransportProtocol;
+import org.apache.streampipes.pe.shared.config.mqtt.MqttConnectUtils;
+import org.apache.streampipes.sdk.builder.adapter.GenericDataStreamAdapterBuilder;
+import org.apache.streampipes.sdk.builder.adapter.ProtocolDescriptionBuilder;
+import org.apache.streampipes.sdk.helpers.Labels;
+
+import com.google.common.collect.Maps;
+import org.junit.Assert;
+import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+public class MqttAdapterTester extends AdapterTesterBase {
+
+  MosquittoContainer mosquittoContainer;
+
+  private static final String TOPIC = "testtopic";
+
+  @Override
+  public void startAdapterService() throws Exception {
+    if (Objects.equals(System.getenv("TEST_MODE"), "dev")) {
+      mosquittoContainer = new MosquittoDevContainer();
+    } else {
+      mosquittoContainer = new MosquittoContainer();
+    }
+    mosquittoContainer.start();
+  }
+
+  @Override
+  public AdapterDescription prepareAdapter() throws Exception {
+    return GenericDataStreamAdapterBuilder
+        .create(MqttProtocol.ID)
+        .format(new JsonObjectFormat()
+            .declareModel())
+        .protocol(ProtocolDescriptionBuilder.create(MqttProtocol.ID)
+            .requiredTextParameter(
+                Labels.withId(MqttConnectUtils.BROKER_URL),
+                mosquittoContainer.getBrokerUrl())
+            .requiredTextParameter(
+                Labels.withId(MqttConnectUtils.TOPIC), TOPIC)
+            .requiredAlternatives(
+                MqttConnectUtils.getAccessModeLabel(),
+                MqttConnectUtils.getAlternativesOne(true),
+                MqttConnectUtils.getAlternativesTwo())
+            .build())
+        .addRules(List.of(new DebugSinkRuleDescription()))
+        .build();
+  }
+
+  @Override
+  public List<Map<String, Object>> generateData() throws Exception {
+    TimeUnit.SECONDS.sleep(2);
+    List<Map<String, Object>> result = new ArrayList<>();
+    MqttTransportProtocol mqttSettings = makeMqttSettings();
+    MqttPublisher publisher = new MqttPublisher();
+    publisher.connect(mqttSettings);
+
+    ObjectMapper objectMapper = new ObjectMapper();
+
+    for (int i = 0; i < 3; i++) {
+      var dataMap = new HashMap<String, Object>();
+      dataMap.put("timestamp", i);
+      dataMap.put("value", "test-data");
+      byte[] data = objectMapper.writeValueAsBytes(dataMap);
+      publisher.publish(data);
+      result.add(dataMap);
+    }
+
+    publisher.disconnect();
+    return result;
+  }
+
+  private MqttTransportProtocol makeMqttSettings() {
+    return new MqttTransportProtocol(mosquittoContainer.getBrokerHost(), mosquittoContainer.getBrokerPort(), TOPIC);
+  }
+
+  @Override
+  public void validateData(List<Map<String, Object>> expectedData) throws Exception {
+    for (Map<String, Object> expected : expectedData) {
+      Map<String, Object> actual = takeEvent();
+      Assert.assertTrue(Maps.difference(expected, actual).areEqual());
+    }
+  }
+
+  @Override
+  public void close() throws Exception {
+    if (mosquittoContainer != null) {
+      mosquittoContainer.stop();
+    }
+    try {
+      stopAdapter();
+    } catch (AdapterException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/containers/MosquittoContainer.java b/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/containers/MosquittoContainer.java
new file mode 100644
index 0000000000..a22de39ec1
--- /dev/null
+++ b/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/containers/MosquittoContainer.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.integration.containers;
+
+import org.testcontainers.containers.BindMode;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+public class MosquittoContainer extends GenericContainer<MosquittoContainer> {
+
+  protected static final int MOSQUITTO_PORT = 1883;
+
+  public MosquittoContainer() {
+    super("eclipse-mosquitto:latest");
+  }
+
+  public void start() {
+    this.waitStrategy = Wait.forLogMessage(".*listen socket on port 1883.*", 1);
+    this.withExposedPorts(MOSQUITTO_PORT);
+    this.withClasspathResourceMapping(
+        "mosquitto.conf",
+        "/mosquitto/config/mosquitto.conf",
+        BindMode.READ_ONLY);
+    super.start();
+  }
+
+  public String getBrokerHost() {
+    return getHost();
+  }
+
+  public Integer getBrokerPort() {
+    return getMappedPort(MOSQUITTO_PORT);
+  }
+
+  public String getBrokerUrl() {
+    return "tcp://" + getBrokerHost() + ":" + getBrokerPort();
+  }
+}
diff --git a/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/containers/MosquittoDevContainer.java b/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/containers/MosquittoDevContainer.java
new file mode 100644
index 0000000000..40e84fed8f
--- /dev/null
+++ b/streampipes-integration-tests/src/test/java/org/apache/streampipes/integration/containers/MosquittoDevContainer.java
@@ -0,0 +1,26 @@
+/*
+ * 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.integration.containers;
+
+public class MosquittoDevContainer extends MosquittoContainer {
+  public String getBrokerHost() {
+    return "localhost";
+  }
+
+}
diff --git a/streampipes-integration-tests/src/test/resources/mosquitto.conf b/streampipes-integration-tests/src/test/resources/mosquitto.conf
new file mode 100644
index 0000000000..3c4318c1ad
--- /dev/null
+++ b/streampipes-integration-tests/src/test/resources/mosquitto.conf
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+#
+
+allow_anonymous true
+listener 1883
+log_type all