Repository: apache/streampipes
Pull request #2883 (2024-05-21 08:27:31)

fix: Fix python client Flake8 errors
<!--
  ~ 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.
-->

```
tests/client/test_client.py:141:121: E501 line too long (134 > 120 characters)
```

This issue also causes pre-commit checks to fail.

### 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>
no
PR introduces (a) deprecation(s): <yes/no>
no


Patch:
diff --git a/pom.xml b/pom.xml
index 6e1f33d663..f6b8e02d64 100644
--- a/pom.xml
+++ b/pom.xml
@@ -74,10 +74,9 @@
         <jakarta-activation-api.version>2.1.3</jakarta-activation-api.version>
         <jakarta-inject-api.version>2.0.1</jakarta-inject-api.version>
         <jakarta-servlet-api.version>6.0.0</jakarta-servlet-api.version>
-        <jakarta-validation.version>3.0.2</jakarta-validation.version>
         <jakarta-xml-bind-api.version>4.0.0</jakarta-xml-bind-api.version>
         <javax.xml.bind.version>2.4.0-b180725.0427</javax.xml.bind.version>
-        <java-websocket.version>1.5.0</java-websocket.version>
+        <java-websocket.version>1.5.6</java-websocket.version>
         <jaxb-runtime.version>2.3.2</jaxb-runtime.version>
         <javax-websocket-api.version>1.1</javax-websocket-api.version>
         <jakarta-persistence-api.version>3.0.1</jakarta-persistence-api.version>
@@ -328,11 +327,6 @@
                 <artifactId>jakarta.inject-api</artifactId>
                 <version>${jakarta-inject-api.version}</version>
             </dependency>
-            <dependency>
-                <groupId>jakarta.validation</groupId>
-                <artifactId>jakarta.validation-api</artifactId>
-                <version>${jakarta-validation.version}</version>
-            </dependency>
             <dependency>
                 <groupId>jakarta.servlet</groupId>
                 <artifactId>jakarta.servlet-api</artifactId>
diff --git a/streampipes-client-python/Makefile b/streampipes-client-python/Makefile
index 2ac4ef1576..dc5510ad7b 100644
--- a/streampipes-client-python/Makefile
+++ b/streampipes-client-python/Makefile
@@ -24,7 +24,7 @@ doc:
 
 .PHONY: lint
 lint:
-	poetry run flake8 streampipes --max-line-length 120
+	poetry run flake8 streampipes tests --max-line-length 120
 
 .PHONY: livedoc
 livedoc: doc
@@ -32,7 +32,7 @@ livedoc: doc
 
 .PHONY: mypy
 mypy:
-	poetry run mypy streampipes --config-file pyproject.toml
+	poetry run mypy streampipes tests --config-file pyproject.toml
 
 .PHONY: rebase
 rebase:
@@ -47,7 +47,12 @@ reformat-all:
 
 .PHONY: pre-commit
 pre-commit:
-	git ls-files -- 'streampipes/**/*' | xargs poetry run pre-commit run --verbose --files
+	@CHANGED_FILES_PYTHON=$$(git diff --name-only HEAD~1 HEAD -- 'streampipes/**/*' 'tests/**/*'); \
+	if [ -n "$$CHANGED_FILES_PYTHON" ]; then \
+		poetry run pre-commit run --verbose --files $$CHANGED_FILES_PYTHON; \
+	else \
+		echo "No files changed in 'streampipes' or 'tests' directories."; \
+	fi
 
 .PHONY: unit-tests
 unit-tests:
diff --git a/streampipes-client-python/streampipes/endpoint/api/version.py b/streampipes-client-python/streampipes/endpoint/api/version.py
index 1a319a6de5..fb06583e3a 100644
--- a/streampipes-client-python/streampipes/endpoint/api/version.py
+++ b/streampipes-client-python/streampipes/endpoint/api/version.py
@@ -28,6 +28,7 @@ from typing import Tuple, Type
 from streampipes.endpoint import APIEndpoint
 from streampipes.model.container import Versions
 from streampipes.model.container.resource_container import ResourceContainer
+from streampipes.model.resource import Version
 from streampipes.model.resource.resource import Resource
 
 
@@ -73,6 +74,16 @@ class VersionEndpoint(APIEndpoint):
 
         return Versions
 
+    @property
+    def _resource_cls(cls) -> Type[Version]:
+        """Returns the class of the resource that are bundled.
+
+        Returns
+        -------
+        [Version][streampipes.model.resource.Version]
+        """
+        return Version
+
     @property
     def _relative_api_path(self) -> Tuple[str, ...]:
         """Defines the relative api path to the DataStream endpoint.
@@ -99,7 +110,7 @@ class VersionEndpoint(APIEndpoint):
         """
         raise NotImplementedError("The `all()` method is not supported by this endpoint.")
 
-    def get(self, identifier: str, **kwargs) -> Resource:
+    def get(self, identifier: str, **kwargs) -> Version:
         """Queries the resource from the API endpoint.
 
         For this endpoint only one resource is available.
@@ -125,7 +136,9 @@ class VersionEndpoint(APIEndpoint):
                 "Please set 'identifier' to an empty string or 'None'."
             )
 
-        return super().get(identifier="")
+        response = self._make_request(request_method=self._parent_client.request_session.get, url=self.build_url())
+
+        return self._resource_cls(**response.json())
 
     def post(self, resource: Resource) -> None:
         """Usually, this method allows to create via this endpoint.
diff --git a/streampipes-client-python/streampipes/py.typed b/streampipes-client-python/streampipes/py.typed
index 17216e19b3..4d9d4b2ef6 100644
--- a/streampipes-client-python/streampipes/py.typed
+++ b/streampipes-client-python/streampipes/py.typed
@@ -14,4 +14,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-#
\ No newline at end of file
+#
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/SupportsNestedTransformationRule.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/SupportsNestedTransformationRule.java
index d332a79e42..86a36b7d6c 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/SupportsNestedTransformationRule.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/SupportsNestedTransformationRule.java
@@ -42,7 +42,10 @@ public abstract class SupportsNestedTransformationRule implements Transformation
           applyNested((Map<String, Object>) event.get(eventKey.get(0)), newKeysTmpList);
 
       event.remove(key);
-      event.put(key, newSubEvent);
+
+      if (newSubEvent != null) {
+        event.put(key, newSubEvent);
+      }
     }
     return event;
   }
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/convert/ToTransformedSchemaConverter.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/convert/ToTransformedSchemaConverter.java
index 60f282abd4..92b1674d0d 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/convert/ToTransformedSchemaConverter.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/convert/ToTransformedSchemaConverter.java
@@ -109,6 +109,10 @@ public class ToTransformedSchemaConverter implements ITransformationRuleVisitor,
   public void visit(AddTimestampRuleDescription rule) {
     var timestampProperty = EpProperties.timestampProperty(rule.getRuntimeKey());
     timestampProperty.setElementId(TIMESTAMP_ID_PREFIX + UUIDGenerator.generateUuid());
+    // null check required for backwards compatibility
+    if (rule.getPropertyScope() != null) {
+      timestampProperty.setPropertyScope(rule.getPropertyScope().name());
+    }
     this.properties.add(timestampProperty);
   }
 
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/DeleteTransformationRule.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/DeleteTransformationRule.java
index 11fb3abe4c..7fe7805baa 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/DeleteTransformationRule.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/DeleteTransformationRule.java
@@ -38,6 +38,8 @@ public class DeleteTransformationRule extends SupportsNestedTransformationRule {
 
   @Override
   protected void applyTransformation(Map<String, Object> event, List<String> eventKeys) {
-    event.remove(eventKeys.get(0));
+    if (event != null) {
+      event.remove(eventKeys.get(0));
+    }
   }
 }
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.simulator.machine/documentation.md b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.simulator.machine/documentation.md
index ec3ed056bb..b27aa491f6 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.simulator.machine/documentation.md
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/resources/org.apache.streampipes.connect.iiot.adapters.simulator.machine/documentation.md
@@ -26,9 +26,61 @@
 
 ## Description
 
-Publishes various simulated machine sensor data in a configurable time interval (in milliseconds).
-Sensors are:
-* flowrate
-* pressure
-* waterlevel
+This adapter publishes simulated machine sensor data at a configurable time interval. It is ideal for exploring the
+capabilities of StreamPipes without needing your own data or for testing purposes. Three different sensor scenarios are
+available:
+
+* Flowrate
+* Pressure
+* Water Level
+
+All scenarios include an error or anomaly condition, making them suitable for trend detection, anomaly detection, and
+similar applications.
+
+### Flowrate Sensor
+
+This scenario simulates a flowrate sensor in a piping system, including a sensor defect situation. The generated data
+stream includes:
+
+- **Sensor ID**: The identifier or name of the sensor, such as `sensor01`.
+- **Mass Flow**: Numeric value denoting the current mass flow in the sensor, ranging from 0 to 10.
+- **Volume Flow**: Numeric value denoting the current volume flow, ranging from 0 to 10.
+- **Temperature**: Numeric value denoting the current temperature in degrees Celsius, ranging from 40 to 100.
+- **Density**: Numeric value denoting the current density of the fluid, ranging from 40 to 50.
+- **Sensor Fault Flags**: Boolean indicator of sensor issues.
+
+The sensor defect scenario is as follows: Normally, temperature values range between 40 and 50 degrees Celsius. After
+thirty seconds, the simulation switches to defect mode for another thirty seconds, with temperatures ranging from 80 to
+100 degrees Celsius and `Sensor Fault Flags` set to `true`.
+
+### Pressure Sensor
+
+This scenario simulates a pressure sensor in a gas tank, including an anomaly situation. The generated data stream
+includes:
+
+- **Sensor ID**: The identifier or name of the sensor, such as `sensor01`.
+- **Pressure**: Numeric value denoting the current pressure in the tank, ranging from 10 to 70.
+
+The anomaly scenario is as follows: Normally, pressure values range between 10 and 40. After thirty seconds, the
+simulation switches to anomaly mode for another thirty seconds, with pressure values ranging from 40 to 70.
+
+### Water Level Sensor
+
+This scenario simulates a sensor in a water tank, including an overflow situation. The generated data stream includes:
+
+- **Sensor ID**: The identifier or name of the sensor, such as `sensor01`.
+- **Level**: Numeric value denoting the current water level in the tank, ranging from 20 to 80.
+- **Overflow**: Boolean indicator of tank overflow.
+
+The overflow scenario is as follows: Normally, level values range between 20 and 30. After thirty seconds, the
+simulation switches to overflow mode for another thirty seconds, with level values ranging from 60 to 80 and `Overflow`
+set to `true`.
+
+## Configuration
+
+When creating the adapter, the following parameters can be configured:
+
+- **Wait Time**: The time in milliseconds between two sensor events. Defaults to 1000 (1 second).
+- **Sensor**: Select one of the sensor scenarios described above: `flowrate`, `pressure`, `waterlevel`.
+
 ***
\ No newline at end of file
diff --git a/streampipes-extensions/streampipes-processors-filters-jvm/src/main/java/org/apache/streampipes/processors/filters/jvm/processor/projection/ProjectionProcessor.java b/streampipes-extensions/streampipes-processors-filters-jvm/src/main/java/org/apache/streampipes/processors/filters/jvm/processor/projection/ProjectionProcessor.java
index a8642a7e9a..c933da7bf5 100644
--- a/streampipes-extensions/streampipes-processors-filters-jvm/src/main/java/org/apache/streampipes/processors/filters/jvm/processor/projection/ProjectionProcessor.java
+++ b/streampipes-extensions/streampipes-processors-filters-jvm/src/main/java/org/apache/streampipes/processors/filters/jvm/processor/projection/ProjectionProcessor.java
@@ -22,6 +22,7 @@ import org.apache.streampipes.commons.exceptions.SpRuntimeException;
 import org.apache.streampipes.extensions.api.pe.context.EventProcessorRuntimeContext;
 import org.apache.streampipes.extensions.api.pe.routing.SpOutputCollector;
 import org.apache.streampipes.model.DataProcessorType;
+import org.apache.streampipes.model.extensions.ExtensionAssetType;
 import org.apache.streampipes.model.graph.DataProcessorDescription;
 import org.apache.streampipes.model.runtime.Event;
 import org.apache.streampipes.sdk.builder.ProcessingElementBuilder;
@@ -29,7 +30,6 @@ import org.apache.streampipes.sdk.builder.StreamRequirementsBuilder;
 import org.apache.streampipes.sdk.helpers.EpRequirements;
 import org.apache.streampipes.sdk.helpers.Locales;
 import org.apache.streampipes.sdk.helpers.OutputStrategies;
-import org.apache.streampipes.sdk.utils.Assets;
 import org.apache.streampipes.wrapper.params.compat.ProcessorParams;
 import org.apache.streampipes.wrapper.standalone.StreamPipesDataProcessor;
 
@@ -43,7 +43,7 @@ public class ProjectionProcessor extends StreamPipesDataProcessor {
   public DataProcessorDescription declareModel() {
     return ProcessingElementBuilder.create("org.apache.streampipes.processors.filters.jvm.project")
         .category(DataProcessorType.TRANSFORM)
-        .withAssets(Assets.DOCUMENTATION, Assets.ICON)
+        .withAssets(ExtensionAssetType.DOCUMENTATION, ExtensionAssetType.ICON)
         .withLocales(Locales.EN)
         .requiredStream(StreamRequirementsBuilder
             .create()
diff --git a/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/websocket/SocketServer.java b/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/websocket/SocketServer.java
index b42cbad301..babd6f8a3f 100644
--- a/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/websocket/SocketServer.java
+++ b/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/websocket/SocketServer.java
@@ -24,16 +24,21 @@ import org.apache.streampipes.model.runtime.Event;
 import org.java_websocket.WebSocket;
 import org.java_websocket.handshake.ClientHandshake;
 import org.java_websocket.server.WebSocketServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.net.InetSocketAddress;
 import java.util.Map;
 
 public class SocketServer extends WebSocketServer {
 
-  private JsonDataFormatDefinition dataFormatDefinition;
+  private static final Logger LOG = LoggerFactory.getLogger(SocketServer.class);
+
+  private final JsonDataFormatDefinition dataFormatDefinition;
 
   public SocketServer(int port) {
     super(new InetSocketAddress(port));
+    setReuseAddr(true);
     dataFormatDefinition = new JsonDataFormatDefinition();
   }
 
@@ -42,27 +47,27 @@ public class SocketServer extends WebSocketServer {
     conn.send("Welcome!"); //This method sends a message to the new client
     broadcast(
         "New connection: " + handshake.getResourceDescriptor()); //This method sends a message to all clients connected
-    System.out.println(conn.getRemoteSocketAddress().getAddress().getHostAddress() + " connected.");
+    LOG.info("{} connected.", conn.getRemoteSocketAddress().getAddress().getHostAddress());
   }
 
   @Override
   public void onClose(WebSocket conn, int code, String reason, boolean remote) {
-    // do nothing special
+    LOG.info("{} closed.", conn.getRemoteSocketAddress().getAddress().getHostAddress());
   }
 
   @Override
   public void onMessage(WebSocket conn, String message) {
-    System.out.println(conn + ": " + message);
+    LOG.debug(" {}: {}", conn, message);
   }
 
   @Override
   public void onError(WebSocket conn, Exception ex) {
-    ex.printStackTrace();
+    LOG.error("Error in websocket connection", ex);
   }
 
   @Override
   public void onStart() {
-    System.out.println("Server started!");
+    LOG.info("Server started at port {}", getPort());
     setConnectionLostTimeout(0);
     setConnectionLostTimeout(100);
   }
diff --git a/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/websocket/WebsocketServerSink.java b/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/websocket/WebsocketServerSink.java
index d4b4aaae3f..2cbba45c9c 100644
--- a/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/websocket/WebsocketServerSink.java
+++ b/streampipes-extensions/streampipes-sinks-brokers-jvm/src/main/java/org/apache/streampipes/sinks/brokers/jvm/websocket/WebsocketServerSink.java
@@ -19,46 +19,46 @@
 package org.apache.streampipes.sinks.brokers.jvm.websocket;
 
 import org.apache.streampipes.commons.exceptions.SpRuntimeException;
+import org.apache.streampipes.extensions.api.pe.IStreamPipesDataSink;
+import org.apache.streampipes.extensions.api.pe.config.IDataSinkConfiguration;
 import org.apache.streampipes.extensions.api.pe.context.EventSinkRuntimeContext;
+import org.apache.streampipes.extensions.api.pe.param.IDataSinkParameters;
 import org.apache.streampipes.model.DataSinkType;
-import org.apache.streampipes.model.graph.DataSinkDescription;
+import org.apache.streampipes.model.extensions.ExtensionAssetType;
 import org.apache.streampipes.model.runtime.Event;
 import org.apache.streampipes.sdk.builder.DataSinkBuilder;
 import org.apache.streampipes.sdk.builder.StreamRequirementsBuilder;
+import org.apache.streampipes.sdk.builder.sink.DataSinkConfiguration;
 import org.apache.streampipes.sdk.helpers.EpRequirements;
 import org.apache.streampipes.sdk.helpers.Labels;
 import org.apache.streampipes.sdk.helpers.Locales;
-import org.apache.streampipes.sdk.utils.Assets;
-import org.apache.streampipes.wrapper.params.compat.SinkParams;
-import org.apache.streampipes.wrapper.standalone.StreamPipesDataSink;
 
-import java.io.IOException;
-
-
-public class WebsocketServerSink extends StreamPipesDataSink {
+public class WebsocketServerSink implements IStreamPipesDataSink {
 
   private static final String PORT_KEY = "port";
-  private Integer port;
 
   private SocketServer server;
 
   @Override
-  public DataSinkDescription declareModel() {
-    return DataSinkBuilder.create("org.apache.streampipes.sinks.brokers.jvm.websocket", 0)
+  public IDataSinkConfiguration declareConfig() {
+    return DataSinkConfiguration.create(
+        WebsocketServerSink::new,
+        DataSinkBuilder.create("org.apache.streampipes.sinks.brokers.jvm.websocket", 0)
         .category(DataSinkType.MESSAGING)
         .withLocales(Locales.EN)
-        .withAssets(Assets.DOCUMENTATION, Assets.ICON)
+        .withAssets(ExtensionAssetType.DOCUMENTATION, ExtensionAssetType.ICON)
         .requiredStream(StreamRequirementsBuilder
             .create()
             .requiredProperty(EpRequirements.anyProperty())
             .build())
         .requiredIntegerParameter(Labels.withId(PORT_KEY))
-        .build();
+        .build());
   }
 
   @Override
-  public void onInvocation(SinkParams parameters, EventSinkRuntimeContext runtimeContext) throws SpRuntimeException {
-    port = parameters.extractor().singleValueParameter(PORT_KEY, Integer.class);
+  public void onPipelineStarted(IDataSinkParameters params,
+                                EventSinkRuntimeContext runtimeContext) {
+    var port = params.extractor().singleValueParameter(PORT_KEY, Integer.class);
     server = new SocketServer(port);
     server.setReuseAddr(true);
     server.start();
@@ -70,11 +70,10 @@ public class WebsocketServerSink extends StreamPipesDataSink {
   }
 
   @Override
-  public void onDetach() throws SpRuntimeException {
+  public void onPipelineStopped() {
     try {
-      server.stop();
-      server = null;
-    } catch (IOException | InterruptedException e) {
+      server.stop(0);
+    } catch (InterruptedException e) {
       throw new SpRuntimeException(e.getMessage());
     }
   }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/connect/rules/value/AddTimestampRuleDescription.java b/streampipes-model/src/main/java/org/apache/streampipes/model/connect/rules/value/AddTimestampRuleDescription.java
index 379ac3b90b..1df522dbd0 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/connect/rules/value/AddTimestampRuleDescription.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/connect/rules/value/AddTimestampRuleDescription.java
@@ -20,10 +20,12 @@ package org.apache.streampipes.model.connect.rules.value;
 
 import org.apache.streampipes.model.connect.rules.ITransformationRuleVisitor;
 import org.apache.streampipes.model.connect.rules.TransformationRulePriority;
+import org.apache.streampipes.model.schema.PropertyScope;
 
 public class AddTimestampRuleDescription extends ValueTransformationRuleDescription {
 
   private String runtimeKey;
+  private PropertyScope propertyScope;
 
   public AddTimestampRuleDescription() {
     super();
@@ -36,6 +38,7 @@ public class AddTimestampRuleDescription extends ValueTransformationRuleDescript
   public AddTimestampRuleDescription(AddTimestampRuleDescription other) {
     super(other);
     this.runtimeKey = other.getRuntimeKey();
+    this.propertyScope = other.getPropertyScope();
   }
 
   public String getRuntimeKey() {
@@ -46,6 +49,14 @@ public class AddTimestampRuleDescription extends ValueTransformationRuleDescript
     this.runtimeKey = runtimeKey;
   }
 
+  public PropertyScope getPropertyScope() {
+    return propertyScope;
+  }
+
+  public void setPropertyScope(PropertyScope propertyScope) {
+    this.propertyScope = propertyScope;
+  }
+
   @Override
   public void accept(ITransformationRuleVisitor visitor) {
     visitor.visit(this);
diff --git a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java
index ad29407fe6..bfefe341ba 100644
--- a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java
+++ b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java
@@ -248,22 +248,22 @@ public abstract class AbstractParameterExtractor<T extends InvocableStreamPipesE
     List<TreeInputNode> allNodes = new ArrayList<>();
     RuntimeResolvableTreeInputStaticProperty sp =
         getStaticPropertyByName(internalName, RuntimeResolvableTreeInputStaticProperty.class);
-    if (sp.getNodes().size() > 0) {
+    if (!sp.getNodes().isEmpty()) {
       sp.getNodes().forEach(node -> buildFlatTree(node, allNodes));
     }
 
-    if (allNodes.size() > 0) {
-      return allNodes
+    if (!allNodes.isEmpty()) {
+      return sp.getSelectedNodesInternalNames()
           .stream()
           .filter(node -> {
             if (!onlyDataNodes) {
               return true;
             } else {
-              return node.isDataNode();
+              var existingNode = allNodes.stream().filter(n -> n.getInternalNodeName().equals(node)).findFirst();
+              return existingNode.map(TreeInputNode::isDataNode).orElse(false);
             }
           })
-          .filter(TreeInputNode::isSelected)
-          .map(node -> typeParser.parse(node.getInternalNodeName(), targetClass))
+          .map(node -> typeParser.parse(node, targetClass))
           .collect(Collectors.toList());
     } else {
       return new ArrayList<>();
diff --git a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventProcessorRuntime.java b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventProcessorRuntime.java
index c2b70c5401..356f57b8c7 100644
--- a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventProcessorRuntime.java
+++ b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventProcessorRuntime.java
@@ -96,6 +96,7 @@ public class StandaloneEventProcessorRuntime extends StandalonePipelineElementRu
   @Override
   protected void afterStop() {
     disconnectInputCollectors();
+    pipelineElement.onPipelineStopped();
     outputCollector.disconnect();
   }
 
diff --git a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventSinkRuntime.java b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventSinkRuntime.java
index 7c7f407c14..8dc453924b 100644
--- a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventSinkRuntime.java
+++ b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventSinkRuntime.java
@@ -81,6 +81,7 @@ public class StandaloneEventSinkRuntime extends StandalonePipelineElementRuntime
   @Override
   protected void afterStop() {
     inputCollectors.forEach(is -> is.unregisterConsumer(instanceId));
+    pipelineElement.onPipelineStopped();
     postDiscard();
   }
 }
diff --git a/ui/cypress/fixtures/connect/deleteTransformationRule/nestedInput.json b/ui/cypress/fixtures/connect/deleteTransformationRule/nestedInput.json
new file mode 100644
index 0000000000..5456e65cbb
--- /dev/null
+++ b/ui/cypress/fixtures/connect/deleteTransformationRule/nestedInput.json
@@ -0,0 +1,7 @@
+{
+  "timestamp": 1667904471000,
+  "parent": {
+    "child": "text",
+    "child_two": "textTwo"
+  }
+}
diff --git a/ui/cypress/fixtures/connect/deleteTransformationRule/prefixInput.csv b/ui/cypress/fixtures/connect/deleteTransformationRule/prefixInput.csv
new file mode 100644
index 0000000000..5280fe3838
--- /dev/null
+++ b/ui/cypress/fixtures/connect/deleteTransformationRule/prefixInput.csv
@@ -0,0 +1,2 @@
+timestamp;reserved bit;reserved bit_1;reserved bit_2
+1715356080000;true;true;true
diff --git a/ui/cypress/support/utils/ConnectEventSchemaUtils.ts b/ui/cypress/support/utils/ConnectEventSchemaUtils.ts
index 1d933399ff..663a42edbf 100644
--- a/ui/cypress/support/utils/ConnectEventSchemaUtils.ts
+++ b/ui/cypress/support/utils/ConnectEventSchemaUtils.ts
@@ -178,12 +178,19 @@ export class ConnectEventSchemaUtils {
     }
 
     public static deleteProperty(propertyName: string) {
-        cy.dataCy('delete-property-' + propertyName, { timeout: 10000 })
+        cy.dataCy('"delete-property-' + propertyName + '"', { timeout: 10000 })
             .children()
             .click({ force: true });
         cy.dataCy('connect-schema-delete-properties-btn', {
             timeout: 10000,
-        }).click();
+        }).click({ force: true });
+
+        // The following two commands are required to fix flaky tests
+        // if another solution can be found, it can be removed
+        cy.wait(200);
+        cy.dataCy('connect-schema-update-preview-btn', {
+            timeout: 10000,
+        }).click({ force: true });
     }
 
     public static changePropertyDataType(
diff --git a/ui/cypress/support/utils/connect/ConnectUtils.ts b/ui/cypress/support/utils/connect/ConnectUtils.ts
index 407475c331..6fe6cfd708 100644
--- a/ui/cypress/support/utils/connect/ConnectUtils.ts
+++ b/ui/cypress/support/utils/connect/ConnectUtils.ts
@@ -278,29 +278,33 @@ export class ConnectUtils {
 
     public static setUpPreprocessingRuleTest(
         overwriteTimestamp: boolean,
+        adapterConfigurationBuilder?: AdapterBuilder,
     ): AdapterInput {
-        const adapterConfiguration = AdapterBuilder.create('File_Stream')
-            .setStoreInDataLake()
-            .setTimestampProperty('timestamp')
-            .addProtocolInput(
-                'radio',
-                'speed',
-                'fastest_\\(ignore_original_time\\)',
-            )
-            .addProtocolInput('radio', 'replayonce', 'yes')
-            .setName('Adapter to test rules')
-            .setFormat('csv')
-            .addFormatInput('input', ConnectBtns.csvDelimiter(), ';')
-            .addFormatInput('checkbox', ConnectBtns.csvHeader(), 'check');
+        if (!adapterConfigurationBuilder) {
+            adapterConfigurationBuilder = AdapterBuilder.create('File_Stream')
+                .setStoreInDataLake()
+                .setTimestampProperty('timestamp')
+                .addProtocolInput(
+                    'radio',
+                    'speed',
+                    'fastest_\\(ignore_original_time\\)',
+                )
+                .addProtocolInput('radio', 'replayonce', 'yes')
+                .setName('Adapter to test rules')
+                .setFormat('csv')
+                .addFormatInput('input', ConnectBtns.csvDelimiter(), ';')
+                .addFormatInput('checkbox', ConnectBtns.csvHeader(), 'check');
+        }
 
         if (overwriteTimestamp) {
-            adapterConfiguration.addProtocolInput(
+            adapterConfigurationBuilder.addProtocolInput(
                 'checkbox',
                 'replaceTimestamp',
                 'check',
             );
         }
-        adapterConfiguration = adapterConfiguration.build();
+
+        const adapterConfiguration = adapterConfigurationBuilder.build();
 
         ConnectUtils.goToConnect();
         ConnectUtils.goToNewAdapterPage();
diff --git a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
index b0de88590b..1cd4d5a729 100644
--- a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model.ts
@@ -20,7 +20,7 @@
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 3.2.1263 on 2024-04-30 20:29:03.
+// Generated using typescript-generator version 3.2.1263 on 2024-05-21 13:45:10.
 
 export class NamedStreamPipesEntity {
     '@class':
@@ -325,6 +325,7 @@ export class ValueTransformationRuleDescription extends TransformationRuleDescri
 
 export class AddTimestampRuleDescription extends ValueTransformationRuleDescription {
     '@class': 'org.apache.streampipes.model.connect.rules.value.AddTimestampRuleDescription';
+    'propertyScope': PropertyScope;
     'runtimeKey': string;
 
     static 'fromData'(
@@ -336,6 +337,7 @@ export class AddTimestampRuleDescription extends ValueTransformationRuleDescript
         }
         const instance = target || new AddTimestampRuleDescription();
         super.fromData(data, instance);
+        instance.propertyScope = data.propertyScope;
         instance.runtimeKey = data.runtimeKey;
         return instance;
     }
diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-exception-message/sp-exception-message.component.html b/ui/projects/streampipes/shared-ui/src/lib/components/sp-exception-message/sp-exception-message.component.html
index 5f9b8ab1e0..04493f10e3 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-exception-message/sp-exception-message.component.html
+++ b/ui/projects/streampipes/shared-ui/src/lib/components/sp-exception-message/sp-exception-message.component.html
@@ -30,13 +30,17 @@
                     <h5 *ngIf="messageTimestamp" class="mr-15">
                         {{ messageTimestamp | date: 'short' }}
                     </h5>
-                    <h5 fxFlex class="color-warn">
+                    <h5 fxFlex>
                         {{ message.title || 'Error' }}
                     </h5>
                 </div>
                 <span fxFlex></span>
                 <div fxLayoutAlign="end center" *ngIf="showDetails">
-                    <button mat-button (click)="openDetailsDialog()">
+                    <button
+                        mat-button
+                        color="accent"
+                        (click)="openDetailsDialog()"
+                    >
                         <i class="material-icons">visibility</i>&nbsp;Details
                     </button>
                 </div>
diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/sp-exception-message/sp-exception-message.component.scss b/ui/projects/streampipes/shared-ui/src/lib/components/sp-exception-message/sp-exception-message.component.scss
index 26170c62ba..165ce8fb0f 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/components/sp-exception-message/sp-exception-message.component.scss
+++ b/ui/projects/streampipes/shared-ui/src/lib/components/sp-exception-message/sp-exception-message.component.scss
@@ -18,7 +18,6 @@
 
 .error-panel {
     background: var(--color-bg-1);
-    border-radius: 5px;
 }
 
 .error {
diff --git a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema-preview/event-schema-preview.component.html b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema-preview/event-schema-preview.component.html
index 41d8e5446c..41ae59c67b 100644
--- a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema-preview/event-schema-preview.component.html
+++ b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema-preview/event-schema-preview.component.html
@@ -49,6 +49,7 @@
             <pre
                 [innerHTML]="desiredField | jsonpretty"
                 class="preview-text"
+                data-cy="schema-preview-result-event"
             ></pre>
         </sp-basic-inner-panel>
     </div>
diff --git a/ui/src/app/connect/services/transformation-rule.service.spec.ts b/ui/src/app/connect/services/transformation-rule.service.spec.ts
index 39c938b913..a0030bf34f 100644
--- a/ui/src/app/connect/services/transformation-rule.service.spec.ts
+++ b/ui/src/app/connect/services/transformation-rule.service.spec.ts
@@ -19,7 +19,6 @@
 import { TransformationRuleService } from './transformation-rule.service';
 import {
     CreateNestedRuleDescription,
-    DeleteRuleDescription,
     EventPropertyNested,
     EventPropertyPrimitive,
     EventPropertyUnion,
@@ -196,73 +195,6 @@ describe('TransformationRuleService', () => {
 
         expect(result.length).toBe(1);
         expect(result[0].oldRuntimeKey).toBe('a');
-        // expect(result[0].newRuntimeKey).toBe('b.a');
-    });
-
-    it('Delete simple', () => {
-        const oldEventSchema: EventSchema = new EventSchema();
-        const eventProperty: EventPropertyPrimitive =
-            new EventPropertyPrimitive();
-        eventProperty.runtimeName = 'a';
-        oldEventSchema.eventProperties = [];
-        oldEventSchema.eventProperties.push(eventProperty);
-
-        const newEventSchema: EventSchema = new EventSchema();
-        newEventSchema.eventProperties = [];
-
-        const result: DeleteRuleDescription[] = service.getDeleteRules(
-            newEventSchema.eventProperties,
-            oldEventSchema,
-            newEventSchema,
-        );
-
-        expect(result.length).toBe(1);
-        expect(result[0].runtimeKey).toBe('a');
-    });
-
-    it('Delete nested', () => {
-        const oldEventSchema: EventSchema = new EventSchema();
-        const eventProperty: EventPropertyPrimitive =
-            new EventPropertyPrimitive();
-        eventProperty.elementId = 'id_2';
-        eventProperty.runtimeName = 'a';
-        const eventPropertyNested: EventPropertyNested =
-            new EventPropertyNested();
-        eventPropertyNested.elementId = 'id_1';
-        eventPropertyNested.eventProperties = [];
-        eventPropertyNested.eventProperties.push(eventProperty);
-        eventPropertyNested.runtimeName = 'b';
-        oldEventSchema.eventProperties = [];
-        oldEventSchema.eventProperties.push(eventPropertyNested);
-
-        let newEventSchema: EventSchema = new EventSchema();
-        const newEventPropertyNested: EventPropertyNested =
-            new EventPropertyNested();
-        newEventPropertyNested.elementId = 'id_1';
-        newEventPropertyNested.runtimeName = 'b';
-        newEventPropertyNested.eventProperties = [];
-        newEventSchema.eventProperties = [];
-        newEventSchema.eventProperties.push(newEventPropertyNested);
-
-        let result: DeleteRuleDescription[] = service.getDeleteRules(
-            newEventSchema.eventProperties,
-            oldEventSchema,
-            newEventSchema,
-        );
-
-        expect(result.length).toBe(1);
-        expect(result[0].runtimeKey).toBe('b.a');
-
-        newEventSchema = new EventSchema();
-        newEventSchema.eventProperties = [];
-        result = service.getDeleteRules(
-            newEventSchema.eventProperties,
-            oldEventSchema,
-            newEventSchema,
-        );
-
-        expect(result.length).toBe(1);
-        expect(result[0].runtimeKey).toBe('b');
     });
 
     it('Rename simple', () => {
diff --git a/ui/src/app/connect/services/transformation-rule.service.ts b/ui/src/app/connect/services/transformation-rule.service.ts
index f1cca8565c..443abebab9 100644
--- a/ui/src/app/connect/services/transformation-rule.service.ts
+++ b/ui/src/app/connect/services/transformation-rule.service.ts
@@ -30,6 +30,7 @@ import {
     EventPropertyUnion,
     EventSchema,
     MoveRuleDescription,
+    PropertyScope,
     RenameRuleDescription,
     SemanticType,
     TimestampTranfsformationRuleDescription,
@@ -55,22 +56,23 @@ export class TransformationRuleService {
         if (originalSchema == null || targetSchema == null) {
             console.log('Old and new schema must be defined');
         } else {
-            const addedTimestampProperties = this.getTimestampProperty(
+            const addedTimestampProperty = this.getTimestampProperty(
                 targetSchema.eventProperties,
             );
-            if (addedTimestampProperties) {
+            if (addedTimestampProperty) {
                 // add to old event schema for the case users moved the property to a nested property
-                originalSchema.eventProperties.push(addedTimestampProperties);
+                originalSchema.eventProperties.push(addedTimestampProperty);
 
                 const timestampRuleDescription: AddTimestampRuleDescription =
                     new AddTimestampRuleDescription();
                 timestampRuleDescription['@class'] =
                     'org.apache.streampipes.model.connect.rules.value.AddTimestampRuleDescription';
                 timestampRuleDescription.runtimeKey =
-                    addedTimestampProperties.runtimeName;
+                    addedTimestampProperty.runtimeName;
+                timestampRuleDescription.propertyScope =
+                    addedTimestampProperty.propertyScope as PropertyScope;
                 transformationRuleDescriptions.push(timestampRuleDescription);
             }
-
             const staticValueProperties = this.getStaticValueProperties(
                 targetSchema.eventProperties,
             );
@@ -84,6 +86,7 @@ export class TransformationRuleService {
                 rule.datatype = ep.runtimeType;
                 rule.label = ep.label;
                 rule.description = ep.description;
+                rule.propertyScope = ep.propertyScope as PropertyScope;
                 if (ep.domainProperties.length > 0) {
                     rule.semanticType = ep.domainProperties[0];
                 }
@@ -125,13 +128,7 @@ export class TransformationRuleService {
                         targetSchema,
                     ),
                 )
-                .concat(
-                    this.getDeleteRules(
-                        targetSchema.eventProperties,
-                        originalSchema,
-                        targetSchema,
-                    ),
-                )
+                .concat(this.getDeleteRules(originalSchema, targetSchema))
                 .concat(
                     this.getUnitTransformRules(
                         targetSchema.eventProperties,
@@ -299,11 +296,10 @@ export class TransformationRuleService {
     }
 
     public getDeleteRules(
-        newEventProperties: EventProperty[],
         oldEventSchema: EventSchema,
         newEventSchema: EventSchema,
     ): DeleteRuleDescription[] {
-        let resultKeys: string[] = [];
+        const resultKeys: string[] = [];
 
         const allNewIds: string[] = this.getAllIds(
             newEventSchema.eventProperties,
@@ -323,21 +319,6 @@ export class TransformationRuleService {
             }
         }
 
-        const uniqEs6 = arrArg => {
-            return arrArg.filter((elem, pos, arr) => {
-                let r = true;
-                for (const a of arr) {
-                    if (elem.startsWith(a) && a !== elem) {
-                        r = false;
-                    }
-                }
-
-                return r;
-            });
-        };
-
-        resultKeys = uniqEs6(resultKeys);
-
         const resultRules: DeleteRuleDescription[] = [];
         for (const key of resultKeys) {
             const rule: DeleteRuleDescription = new DeleteRuleDescription();
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
index beabe6de63..b3bc952595 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
@@ -54,14 +54,30 @@
     <div fxLayout="row" fxLayoutGap="15px">
         <div fxFlex="30" class="tree-input-section" fxLayout="column">
             <div class="tree-input-header" fxLayoutAlign="start center">
-                <b>Browse</b>
+                <div fxLayout="row" fxFlex="100">
+                    <div fxFlex fxLayoutAlign="start center"><b>Browse</b></div>
+                    <div fxLayoutAlign="end center">
+                        <button
+                            mat-icon-button
+                            color="accent"
+                            (click)="largeView = !largeView"
+                        >
+                            <mat-icon *ngIf="!largeView">open_in_full</mat-icon>
+                            <mat-icon *ngIf="largeView"
+                                >close_fullscreen</mat-icon
+                            >
+                        </button>
+                    </div>
+                </div>
             </div>
             <mat-tree
                 [dataSource]="dataSource"
                 [treeControl]="treeControl"
                 #tree
                 class="sp-tree"
-                style="max-height: 300px; overflow-y: auto"
+                [ngClass]="
+                    largeView ? 'tree-large-height' : 'tree-normal-height'
+                "
             >
                 <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
                     <div
@@ -156,6 +172,7 @@
                                     hasDataChildren(node)
                                 "
                                 matTooltip="Add all direct children"
+                                (click)="addAllDirectChildren(node)"
                                 >add_circle</i
                             >
                             <i
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
index d8aa86627e..4cb8975c33 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
@@ -16,6 +16,19 @@
  *
  */
 
+.sp-tree {
+    overflow-y: auto;
+}
+
+.tree-normal-height {
+    min-height: 300px;
+    max-height: 300px;
+}
+
+.tree-large-height {
+    min-height: 800px;
+}
+
 .sp-tree-invisible {
     display: none;
 }
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
index f736e4a139..1f7dbf2010 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
@@ -27,6 +27,7 @@ import { RuntimeResolvableService } from '../static-runtime-resolvable-input/run
 import { NestedTreeControl } from '@angular/cdk/tree';
 import { MatTree, MatTreeNestedDataSource } from '@angular/material/tree';
 import { UntypedFormControl } from '@angular/forms';
+import Tree from 'echarts/types/src/data/Tree';
 
 @Component({
     selector: 'sp-runtime-resolvable-tree-input',
@@ -43,6 +44,8 @@ export class StaticRuntimeResolvableTreeInputComponent
     selectedNodeMetadata: Record<string, string>;
     selectedNodeId: string;
 
+    largeView = false;
+
     @ViewChild('tree') tree: MatTree<TreeInputNode>;
 
     constructor(runtimeResolvableService: RuntimeResolvableService) {
@@ -159,6 +162,25 @@ export class StaticRuntimeResolvableTreeInputComponent
         this.performValidation();
     }
 
+    addAllDirectChildren(node: TreeInputNode) {
+        node.children.forEach(child => {
+            if (child.dataNode && !this.existsSelectedNode(child)) {
+                this.staticProperty.selectedNodesInternalNames.push(
+                    child.internalNodeName,
+                );
+            }
+        });
+        this.performValidation();
+    }
+
+    existsSelectedNode(node: TreeInputNode) {
+        return (
+            this.staticProperty.selectedNodesInternalNames.find(
+                nodeName => nodeName === node.internalNodeName,
+            ) !== undefined
+        );
+    }
+
     removeNode(node: TreeInputNode) {
         node.selected = false;
         const index = this.getSelectedNodeIndex(node.internalNodeName);


Test patch:
diff --git a/streampipes-client-python/tests/client/test_client.py b/streampipes-client-python/tests/client/test_client.py
index 6e92deb880..241dab261f 100644
--- a/streampipes-client-python/tests/client/test_client.py
+++ b/streampipes-client-python/tests/client/test_client.py
@@ -119,7 +119,13 @@ class TestStreamPipesClient(TestCase):
             if "streams" in kwargs["url"]:
                 return MockResponse(
                     json.dumps(
-                        [{"elementId": "test-stream", "name": "test", "eventGrounding": {"transportProtocols": []}}]
+                        [
+                            {
+                                "elementId": "test-stream",
+                                "name": "test",
+                                "eventGrounding": {"transportProtocols": []},
+                            }
+                        ]
                     )
                 )
             if "versions" in kwargs["url"]:
@@ -138,7 +144,8 @@ class TestStreamPipesClient(TestCase):
         mocked_logger.info.assert_has_calls(
             calls=[
                 call(
-                    "\nHi there!\nYou are connected to a StreamPipes instance running at https://localhost:443 with version SP-dev.\n"
+                    "\nHi there!\nYou are connected to a StreamPipes instance running at "
+                    "https://localhost:443 with version SP-dev.\n"
                     "The following StreamPipes resources are available with this client:\n"
                     "1x DataLakeMeasures\n1x DataStreams"
                 ),
diff --git a/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/DeleteTransformationRuleTest.java b/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/DeleteTransformationRuleTest.java
index 0579efb627..28d8ffb20e 100644
--- a/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/DeleteTransformationRuleTest.java
+++ b/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/DeleteTransformationRuleTest.java
@@ -18,7 +18,8 @@
 
 package org.apache.streampipes.connect.shared.preprocessing.transform.schema;
 
-import org.junit.jupiter.api.Assertions;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Test;
 
 import java.util.Arrays;
@@ -26,33 +27,113 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
 public class DeleteTransformationRuleTest {
 
+
+  private ObjectMapper objectMapper = new ObjectMapper();
+
   @Test
   public void transformSimple() {
-    Map<String, Object> event = new HashMap<>();
+    var event = new HashMap<String, Object>();
     event.put("key", "value");
 
-    DeleteTransformationRule deleteRule = new DeleteTransformationRule(List.of("key"));
+    var deleteRule = new DeleteTransformationRule(List.of("key"));
+
+    var result = deleteRule.apply(event);
+
+    assertEquals(0,
+                 result.keySet()
+                       .size()
+    );
+  }
+
+  @Test
+  public void transformNested() throws JsonProcessingException {
+    var jsonString = """
+                     {
+                         "parent": {
+                             "child": "value",
+                             "child2": "value2"
+                         },
+                         "keepProperty": "test"
+                     }
+                     """;
+
+    var event = getEvent(jsonString);
+
+    var deleteRule = new DeleteTransformationRule(List.of("parent", "child"));
+
+    var result = deleteRule.apply(event);
+
+    assertEquals(2,
+                 result.keySet()
+                       .size()
+    );
+    assertEquals(1, ((Map) result.get("parent")).size());
+  }
+
+  @Test
+  public void testRemoveParent() throws JsonProcessingException {
+    var jsonString = """
+                     {
+                         "parent": {
+                             "child": "value",
+                             "child2": "value2"
+                         },
+                         "keepProperty": "test"
+                     }
+                     """;
+
+    var event = getEvent(jsonString);
+
+    var deleteRule = new DeleteTransformationRule(List.of("parent"));
 
-    Map<String, Object> result = deleteRule.apply(event);
+    var result = deleteRule.apply(event);
 
-    Assertions.assertEquals(0,
-                            result.keySet().size());
+    assertEquals(1,
+                 result.keySet()
+                       .size()
+    );
+    assertEquals("test", result.get("keepProperty"));
   }
 
+
   @Test
-  public void transformNested() {
-    Map<String, Object> child = new HashMap<>();
-    child.put("child", "value");
-    Map<String, Object> event = new HashMap<>();
-    event.put("parent", child);
+  // verifying that mathod applyTransformation method works when passed a null event.
+  public void applyTransformationWithNullParameter() {
+    new DeleteTransformationRule(List.of())
+        .applyTransformation(null, List.of());
+  }
 
-    DeleteTransformationRule deleteRule = new DeleteTransformationRule(Arrays.asList("parent", "child"));
+  @Test
+  public void deleteNestedChildWithParentProperty() throws JsonProcessingException {
 
-    Map<String, Object> result = deleteRule.apply(event);
+    var jsonString = """
+                     {
+                         "parent": {
+                             "child": "value"
+                         },
+                         "keepProperty": "test"
+                     }
+                     """;
 
-    Assertions.assertEquals(1,
-                            result.keySet().size());
+    var event = getEvent(jsonString);
+
+    var deleteRule = new DeleteTransformationRule(Arrays.asList("parent", "child"));
+
+    var result = deleteRule.apply(event);
+
+    assertEquals(2,
+                 result.keySet()
+                       .size()
+    );
+    assertEquals("test", result.get("keepProperty"));
   }
+
+  private Map<String, Object> getEvent(String eventJson) throws JsonProcessingException {
+    return objectMapper.readValue(eventJson, HashMap.class);
+  }
+
 }
diff --git a/ui/cypress/tests/adapter/rules/deleteTransformationRule.spec.ts b/ui/cypress/tests/adapter/rules/deleteTransformationRule.spec.ts
new file mode 100644
index 0000000000..6ecb38744b
--- /dev/null
+++ b/ui/cypress/tests/adapter/rules/deleteTransformationRule.spec.ts
@@ -0,0 +1,117 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+import { ConnectUtils } from '../../../support/utils/connect/ConnectUtils';
+import { FileManagementUtils } from '../../../support/utils/FileManagementUtils';
+import { ConnectEventSchemaUtils } from '../../../support/utils/ConnectEventSchemaUtils';
+import { AdapterBuilder } from '../../../support/builder/AdapterBuilder';
+import { ConnectBtns } from '../../../support/utils/connect/ConnectBtns';
+
+describe('Connect delete rule transformation', () => {
+    beforeEach('Setup Test', () => {
+        cy.initStreamPipesTest();
+    });
+
+    it('Test delete with same prefix', () => {
+        FileManagementUtils.addFile(
+            'connect/deleteTransformationRule/prefixInput.csv',
+        );
+
+        ConnectUtils.setUpPreprocessingRuleTest(false);
+
+        ConnectEventSchemaUtils.deleteProperty('reserved bit');
+        ConnectEventSchemaUtils.deleteProperty('reserved bit_1');
+        ConnectEventSchemaUtils.deleteProperty('reserved bit_2');
+
+        cy.dataCy('schema-preview-result-event').should(
+            'have.text',
+            '{\u00A0\u00A0\u00A0\u00A0"timestamp":\u00A01715356080000}',
+        );
+    });
+
+    it('Test delete nested properties', () => {
+        FileManagementUtils.addFile(
+            'connect/deleteTransformationRule/nestedInput.json',
+        );
+
+        const adapterConfigurationBuilder = AdapterBuilder.create('File_Stream')
+            .setStoreInDataLake()
+            .setTimestampProperty('timestamp')
+            .addProtocolInput(
+                'radio',
+                'speed',
+                'fastest_\\(ignore_original_time\\)',
+            )
+            .addProtocolInput('radio', 'replayonce', 'yes')
+            .setName('Adapter to test rules')
+            .setFormat('json');
+
+        ConnectUtils.setUpPreprocessingRuleTest(
+            false,
+            adapterConfigurationBuilder,
+        );
+
+        // Test to delete the child property
+        ConnectEventSchemaUtils.deleteProperty('child');
+
+        // The resulting string contains non-breaking spaces character (\u00A0)
+        cy.dataCy('schema-preview-result-event').should(
+            'have.text',
+            '{\u00A0\u00A0\u00A0\u00A0"parent":\u00A0{\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0"child_two":\u00A0"textTwo"\u00A0\u00A0\u00A0\u00A0},\u00A0\u00A0\u00A0\u00A0"timestamp":\u00A01667904471000}',
+        );
+
+        ConnectBtns.refreshSchema().click();
+
+        // Test to delete the parent property
+        ConnectEventSchemaUtils.deleteProperty('parent');
+        cy.dataCy('schema-preview-result-event').should(
+            'have.text',
+
+            '{\u00A0\u00A0\u00A0\u00A0"timestamp":\u00A01667904471000}',
+        );
+
+        ConnectBtns.refreshSchema().click();
+        ConnectEventSchemaUtils.deleteProperty('child');
+        ConnectEventSchemaUtils.deleteProperty('child_two');
+
+        cy.dataCy('schema-preview-result-event').should(
+            'have.text',
+            '{\u00A0\u00A0\u00A0\u00A0"parent":\u00A0{},\u00A0\u00A0\u00A0\u00A0"timestamp":\u00A01667904471000}',
+        );
+
+        ConnectBtns.refreshSchema().click();
+
+        // Test to delete the parent property
+        ConnectEventSchemaUtils.deleteProperty('parent');
+        cy.dataCy('schema-preview-result-event').should(
+            'have.text',
+
+            '{\u00A0\u00A0\u00A0\u00A0"timestamp":\u00A01667904471000}',
+        );
+
+        ConnectBtns.refreshSchema().click();
+
+        // Test to delete both child properties
+        ConnectEventSchemaUtils.deleteProperty('child');
+        ConnectEventSchemaUtils.deleteProperty('child_two');
+
+        cy.dataCy('schema-preview-result-event').should(
+            'have.text',
+            '{\u00A0\u00A0\u00A0\u00A0"parent":\u00A0{},\u00A0\u00A0\u00A0\u00A0"timestamp":\u00A01667904471000}',
+        );
+    });
+});