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

zehnder pushed a commit to branch 
4032-introducing-function-transformation-for-complex-data-handling-in-connect
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to 
refs/heads/4032-introducing-function-transformation-for-complex-data-handling-in-connect
 by this push:
     new cd57c4f1c2 refactor: Fix test testVariousUserRoles
     new 44df2117de Merge branch 'dev' into 
4032-introducing-function-transformation-for-complex-data-handling-in-connect
cd57c4f1c2 is described below

commit cd57c4f1c28846eda4902765e669300313470043
Author: Philipp Zehnder <[email protected]>
AuthorDate: Mon Dec 8 12:58:52 2025 +0100

    refactor: Fix test testVariousUserRoles
---
 .../management/management/GuessManagement.java     |  53 +++++--
 .../connect/management/util/WorkerPaths.java       |   7 +
 .../extensions/api/connect/StreamPipesAdapter.java |   7 +
 ...ment.java => AdapterWorkerGuessManagement.java} |  56 ++++++-
 ....java => AdapterWorkerGuessManagementTest.java} |   4 +-
 .../machine/MachineDataSimulatorAdapter.java       |  30 ++++
 .../model/connect/guess/SampleData.java            |  30 ++--
 ...source.java => AdapterWorkerGuessResource.java} |  37 +++--
 ...ionHandler.java => SpRestExceptionHandler.java} |  14 +-
 .../rest/impl/connect/GuessResource.java           |  18 +++
 .../StreamPipesExtensionsServiceBase.java          |   4 +-
 .../src/lib/model/gen/streampipes-model-client.ts  |   2 +-
 .../src/lib/model/gen/streampipes-model.ts         |  28 +++-
 .../adapter-configuration.component.html           |  27 +++-
 .../adapter-configuration.component.ts             |   6 +-
 .../adapter-settings.component.html                |   4 +-
 .../adapter-settings/adapter-settings.component.ts |  13 +-
 .../event-preview/event-preview.component.html     | 136 +++++++++++++++++
 .../event-preview/event-preview.component.scss     |  28 +---
 .../event-preview/event-preview.component.ts       | 169 +++++++++++++++++++++
 .../event-schema/event-schema.component.html       |   4 +-
 .../event-schema/event-schema.component.ts         |  12 +-
 .../start-adapter-configuration.component.html     |   2 +-
 .../start-adapter-configuration.component.ts       |   7 +-
 ui/src/app/connect/connect.module.ts               |   4 +
 ui/src/app/connect/services/rest.service.ts        |  14 ++
 26 files changed, 606 insertions(+), 110 deletions(-)

diff --git 
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/GuessManagement.java
 
b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/GuessManagement.java
index c43be660bb..d2b808ee01 100644
--- 
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/GuessManagement.java
+++ 
b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/GuessManagement.java
@@ -29,7 +29,7 @@ import 
org.apache.streampipes.manager.execution.endpoint.ExtensionsServiceEndpoi
 import org.apache.streampipes.model.connect.adapter.AdapterDescription;
 import org.apache.streampipes.model.connect.guess.AdapterEventPreview;
 import org.apache.streampipes.model.connect.guess.GuessSchema;
-import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTag;
+import org.apache.streampipes.model.connect.guess.SampleData;
 import org.apache.streampipes.model.monitoring.SpLogMessage;
 import org.apache.streampipes.resource.management.secret.SecretProvider;
 import org.apache.streampipes.serializers.json.JacksonSerializer;
@@ -44,7 +44,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.Set;
 
 public class GuessManagement {
 
@@ -57,13 +56,11 @@ public class GuessManagement {
     this.objectMapper = JacksonSerializer.getObjectMapper();
   }
 
+  @Deprecated
   public GuessSchema guessSchema(AdapterDescription adapterDescription)
       throws ParseException, WorkerAdapterException, 
NoServiceEndpointsAvailableException, IOException {
     SecretProvider.getDecryptionService().apply(adapterDescription);
-    var workerUrl = getWorkerUrl(
-        adapterDescription.getAppId(),
-        adapterDescription.getDeploymentConfiguration().getDesiredServiceTags()
-    );
+    var workerUrl = getWorkerUrl(adapterDescription, 
WorkerPaths.getGuessSchemaPath());
     var description = objectMapper.writeValueAsString(adapterDescription);
 
     LOG.debug("Calling guess schema at: {}", workerUrl);
@@ -82,13 +79,47 @@ public class GuessManagement {
     }
   }
 
-  private String getWorkerUrl(String appId,
-                              Set<SpServiceTag> customServiceTags) throws 
NoServiceEndpointsAvailableException {
-    var baseUrl = endpointGenerator.getEndpointBaseUrl(appId, 
SpServiceUrlProvider.ADAPTER, customServiceTags);
-    return baseUrl + WorkerPaths.getGuessSchemaPath();
-  }
 
+
+  @Deprecated
   public String performAdapterEventPreview(AdapterEventPreview previewRequest) 
throws JsonProcessingException {
     return new AdapterEventPreviewPipeline(previewRequest).makePreview();
   }
+
+  public SampleData getSampleData(AdapterDescription adapterDescription)
+      throws WorkerAdapterException, NoServiceEndpointsAvailableException, 
IOException {
+
+    SecretProvider.getDecryptionService().apply(adapterDescription);
+
+    var workerUrl = getWorkerUrl(adapterDescription, 
WorkerPaths.getSamplePath());
+
+    var adapterDescriptionString = 
objectMapper.writeValueAsString(adapterDescription);
+
+    LOG.debug("Calling get get sample data at: {}", workerUrl);
+
+    var httpResponse = ExtensionServiceExecutions
+        .extServicePostRequest(workerUrl, adapterDescriptionString)
+        .execute()
+        .returnResponse();
+
+    var responseString = EntityUtils.toString(httpResponse.getEntity());
+
+    if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+      return objectMapper.readValue(responseString, SampleData.class);
+    } else {
+      var exception = objectMapper.readValue(responseString, 
SpLogMessage.class);
+      throw new WorkerAdapterException(exception);
+    }
+  }
+
+  private String getWorkerUrl(AdapterDescription adapterDescription,
+                              String suffix) throws 
NoServiceEndpointsAvailableException {
+    var baseUrl = endpointGenerator.getEndpointBaseUrl(
+        adapterDescription.getAppId(),
+        SpServiceUrlProvider.ADAPTER,
+        
adapterDescription.getDeploymentConfiguration().getDesiredServiceTags());
+
+    return baseUrl + suffix;
+  }
+
 }
diff --git 
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/util/WorkerPaths.java
 
b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/util/WorkerPaths.java
index 7bd2b0c1ed..02e2b3e9ab 100644
--- 
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/util/WorkerPaths.java
+++ 
b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/util/WorkerPaths.java
@@ -41,4 +41,11 @@ public class WorkerPaths {
     return WorkerMainPath + "/guess/schema";
   }
 
+  // TODO naming
+  public static String getSamplePath() {
+    return WorkerMainPath + "/guess/sample";
+  }
+
+
+
 }
diff --git 
a/streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/StreamPipesAdapter.java
 
b/streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/StreamPipesAdapter.java
index 8651b05ee5..275ae20925 100644
--- 
a/streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/StreamPipesAdapter.java
+++ 
b/streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/StreamPipesAdapter.java
@@ -24,6 +24,7 @@ import 
org.apache.streampipes.extensions.api.connect.context.IAdapterRuntimeCont
 import 
org.apache.streampipes.extensions.api.extractor.IAdapterParameterExtractor;
 import org.apache.streampipes.model.connect.adapter.AdapterDescription;
 import org.apache.streampipes.model.connect.guess.GuessSchema;
+import org.apache.streampipes.model.connect.guess.SampleData;
 
 public interface StreamPipesAdapter {
   IAdapterConfiguration declareConfig();
@@ -51,4 +52,10 @@ public interface StreamPipesAdapter {
 
   GuessSchema onSchemaRequested(IAdapterParameterExtractor extractor,
                                 IAdapterGuessSchemaContext 
adapterGuessSchemaContext) throws AdapterException;
+
+  default SampleData onSampleDataRequested(
+      IAdapterParameterExtractor extractor,
+      IAdapterGuessSchemaContext adapterGuessSchemaContext) throws 
AdapterException {
+    throw new UnsupportedOperationException("Event preview is not supported by 
this adapter.");
+  }
 }
diff --git 
a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/GuessManagement.java
 
b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/AdapterWorkerGuessManagement.java
similarity index 61%
rename from 
streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/GuessManagement.java
rename to 
streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/AdapterWorkerGuessManagement.java
index 042a6e244a..e5c94dc378 100644
--- 
a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/GuessManagement.java
+++ 
b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/AdapterWorkerGuessManagement.java
@@ -20,11 +20,13 @@ package 
org.apache.streampipes.extensions.management.connect;
 
 import org.apache.streampipes.commons.exceptions.connect.AdapterException;
 import org.apache.streampipes.commons.exceptions.connect.ParseException;
+import org.apache.streampipes.extensions.api.connect.StreamPipesAdapter;
 import 
org.apache.streampipes.extensions.api.connect.context.IAdapterGuessSchemaContext;
 import org.apache.streampipes.extensions.management.init.DeclarersSingleton;
 import org.apache.streampipes.extensions.management.init.IDeclarersSingleton;
 import org.apache.streampipes.model.connect.adapter.AdapterDescription;
 import org.apache.streampipes.model.connect.guess.GuessSchema;
+import org.apache.streampipes.model.connect.guess.SampleData;
 import org.apache.streampipes.sdk.extractor.AdapterParameterExtractor;
 
 import org.slf4j.Logger;
@@ -33,16 +35,16 @@ import org.slf4j.LoggerFactory;
 import java.util.Arrays;
 import java.util.Optional;
 
-public class GuessManagement {
+public class AdapterWorkerGuessManagement {
 
-  private static final Logger LOG = 
LoggerFactory.getLogger(GuessManagement.class);
+  private static final Logger LOG = 
LoggerFactory.getLogger(AdapterWorkerGuessManagement.class);
 
   private IAdapterGuessSchemaContext guessSchemaContext;
 
-  public GuessManagement() {
+  public AdapterWorkerGuessManagement() {
   }
 
-  public GuessManagement(IAdapterGuessSchemaContext guessSchemaContext) {
+  public AdapterWorkerGuessManagement(IAdapterGuessSchemaContext 
guessSchemaContext) {
     this.guessSchemaContext = guessSchemaContext;
   }
 
@@ -56,7 +58,8 @@ public class GuessManagement {
       LOG.debug("Start guessing schema for: {}", 
adapterDescription.getAppId());
 
       // get registered parser of adapter
-      var registeredParsers = 
adapterInstance.declareConfig().getSupportedParsers();
+      var registeredParsers = adapterInstance.declareConfig()
+                                             .getSupportedParsers();
 
       var extractor = AdapterParameterExtractor.from(adapterDescription, 
registeredParsers);
 
@@ -66,7 +69,9 @@ public class GuessManagement {
         var guessedSchemaObj = adapterInstance
             .onSchemaRequested(extractor, guessSchemaContext);
 
-        if 
(!adapterDescription.getEventSchema().getEventProperties().isEmpty()) {
+        if (!adapterDescription.getEventSchema()
+                               .getEventProperties()
+                               .isEmpty()) {
           new 
SchemaUpdateManagement().computeSchemaChanges(adapterDescription, 
guessedSchemaObj);
         } else {
           guessedSchemaObj.setTargetSchema(guessedSchemaObj.getEventSchema());
@@ -77,9 +82,12 @@ public class GuessManagement {
         LOG.error(e.toString());
 
         String errorClass = "";
-        Optional<StackTraceElement> stackTraceElement = 
Arrays.stream(e.getStackTrace()).findFirst();
+        Optional<StackTraceElement> stackTraceElement = 
Arrays.stream(e.getStackTrace())
+                                                              .findFirst();
         if (stackTraceElement.isPresent()) {
-          String[] errorClassLong = 
stackTraceElement.get().getClassName().split("\\.");
+          String[] errorClassLong = stackTraceElement.get()
+                                                     .getClassName()
+                                                     .split("\\.");
           errorClass = errorClassLong[errorClassLong.length - 1] + ": ";
         }
         throw new ParseException(errorClass + e.getMessage());
@@ -93,6 +101,38 @@ public class GuessManagement {
 
   }
 
+  /**
+   * Collects sample data from an adapter instance
+   */
+  public SampleData getSampleData(AdapterDescription adapterDescription) 
throws AdapterException {
+    LOG.debug("Start receiving sample data for adapter: {}", 
adapterDescription.getAppId());
+
+    var adapter = 
getAdapterInstanceOrThrowException(adapterDescription.getAppId());
+
+    // get registered parser of adapter
+    var registeredParsers = adapter
+        .declareConfig()
+        .getSupportedParsers();
+
+    var extractor = AdapterParameterExtractor.from(adapterDescription, 
registeredParsers);
+
+    LOG.debug("Requesting the sample events for: {}", 
adapterDescription.getAppId());
+
+    return adapter.onSampleDataRequested(extractor, guessSchemaContext);
+
+  }
+
+  private StreamPipesAdapter getAdapterInstanceOrThrowException(String appId) 
throws AdapterException {
+    var adapter = getDeclarerSingleton()
+        .getAdapter(appId);
+
+    if (adapter.isPresent()) {
+      return adapter.get();
+    } else {
+      throw new AdapterException("Adapter with app id %s was not be 
found".formatted(appId));
+    }
+  }
+
   public IDeclarersSingleton getDeclarerSingleton() {
     return DeclarersSingleton.getInstance();
   }
diff --git 
a/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/GuessManagementTest.java
 
b/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/AdapterWorkerGuessManagementTest.java
similarity index 93%
rename from 
streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/GuessManagementTest.java
rename to 
streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/AdapterWorkerGuessManagementTest.java
index ee94b3e108..5be5a1ef7e 100644
--- 
a/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/GuessManagementTest.java
+++ 
b/streampipes-extensions-management/src/test/java/org/apache/streampipes/extensions/management/connect/AdapterWorkerGuessManagementTest.java
@@ -32,12 +32,12 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
-public class GuessManagementTest {
+public class AdapterWorkerGuessManagementTest {
 
   @Test
   public void getAdapterGuessInfoAdapterNotFound() {
     var mockDeclarersSingleton = Mockito.mock(IDeclarersSingleton.class);
-    var guessManagement = Mockito.spy(GuessManagement.class);
+    var guessManagement = Mockito.spy(AdapterWorkerGuessManagement.class);
 
     doReturn(mockDeclarersSingleton)
         .when(guessManagement)
diff --git 
a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/simulator/machine/MachineDataSimulatorAdapter.java
 
b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/simulator/machine/MachineDataSimulatorAdapter.java
index 5440d19e86..992581fa44 100644
--- 
a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/simulator/machine/MachineDataSimulatorAdapter.java
+++ 
b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/simulator/machine/MachineDataSimulatorAdapter.java
@@ -26,12 +26,20 @@ import 
org.apache.streampipes.extensions.api.connect.context.IAdapterGuessSchema
 import 
org.apache.streampipes.extensions.api.connect.context.IAdapterRuntimeContext;
 import 
org.apache.streampipes.extensions.api.extractor.IAdapterParameterExtractor;
 import org.apache.streampipes.model.connect.guess.GuessSchema;
+import org.apache.streampipes.model.connect.guess.SampleData;
 import org.apache.streampipes.model.extensions.ExtensionAssetType;
 import org.apache.streampipes.sdk.builder.adapter.AdapterConfigurationBuilder;
 import org.apache.streampipes.sdk.helpers.Labels;
 import org.apache.streampipes.sdk.helpers.Locales;
 import org.apache.streampipes.sdk.helpers.Options;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 public class MachineDataSimulatorAdapter implements StreamPipesAdapter {
 
   public static final String ID = 
"org.apache.streampipes.connect.iiot.adapters.simulator.machine";
@@ -84,4 +92,26 @@ public class MachineDataSimulatorAdapter implements 
StreamPipesAdapter {
     var selectedSimulatorOption = 
ex.selectedSingleValue(SELECTED_SIMULATOR_OPTION, String.class);
     return 
MachineDataSimulatorUtils.getSimulator(selectedSimulatorOption).getSchema();
   }
+
+  @Override
+  public SampleData onSampleDataRequested(IAdapterParameterExtractor extractor,
+                                          IAdapterGuessSchemaContext 
adapterGuessSchemaContext) throws AdapterException {
+    var ex = extractor.getStaticPropertyExtractor();
+    var selectedSimulatorOption = 
ex.selectedSingleValue(SELECTED_SIMULATOR_OPTION, String.class);
+
+    var guessSchema = 
MachineDataSimulatorUtils.getSimulator(selectedSimulatorOption).getSchema();
+
+    ObjectMapper objectMapper = new ObjectMapper();
+    Map<String, Object> event = null;
+    try {
+      event = objectMapper.readValue(guessSchema.getEventPreview().get(0), 
HashMap.class);
+    } catch (JsonProcessingException e) {
+      event = Map.of();
+    }
+
+    var eventPreview = new SampleData();
+    eventPreview.setSamples(List.of(event));
+
+    return eventPreview;
+  }
 }
diff --git 
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/util/WorkerPaths.java
 
b/streampipes-model/src/main/java/org/apache/streampipes/model/connect/guess/SampleData.java
similarity index 55%
copy from 
streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/util/WorkerPaths.java
copy to 
streampipes-model/src/main/java/org/apache/streampipes/model/connect/guess/SampleData.java
index 7bd2b0c1ed..7fdffdd31c 100644
--- 
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/util/WorkerPaths.java
+++ 
b/streampipes-model/src/main/java/org/apache/streampipes/model/connect/guess/SampleData.java
@@ -15,30 +15,26 @@
  * limitations under the License.
  *
  */
-package org.apache.streampipes.connect.management.util;
 
-public class WorkerPaths {
+package org.apache.streampipes.model.connect.guess;
 
-  private static final String WorkerMainPath = "/api/v1/worker";
+import org.apache.streampipes.model.shared.annotation.TsModel;
 
-  public static String getStreamInvokePath() {
-    return WorkerMainPath + "/stream/invoke";
-  }
+import java.util.List;
+import java.util.Map;
 
-  public static String getStreamStopPath() {
-    return WorkerMainPath + "/stream/stop";
-  }
+@TsModel
+public class SampleData {
+  // TODO add this  public Map<String, FieldStatusInfo> fieldStatusInfo
 
-  public static String getRunningAdaptersPath() {
-    return WorkerMainPath + "/running";
-  }
+  // A SampleEvent contains at least one sample
+  private List<Map<String, Object>> samples;
 
-  public static String getRuntimeResolvablePath(String elementId) {
-    return WorkerMainPath + "/resolvable/" + elementId + "/configurations";
+  public List<Map<String, Object>> getSamples() {
+    return samples;
   }
 
-  public static String getGuessSchemaPath() {
-    return WorkerMainPath + "/guess/schema";
+  public void setSamples(List<Map<String, Object>> samples) {
+    this.samples = samples;
   }
-
 }
diff --git 
a/streampipes-rest-extensions/src/main/java/org/apache/streampipes/rest/extensions/connect/GuessResource.java
 
b/streampipes-rest-extensions/src/main/java/org/apache/streampipes/rest/extensions/connect/AdapterWorkerGuessResource.java
similarity index 68%
rename from 
streampipes-rest-extensions/src/main/java/org/apache/streampipes/rest/extensions/connect/GuessResource.java
rename to 
streampipes-rest-extensions/src/main/java/org/apache/streampipes/rest/extensions/connect/AdapterWorkerGuessResource.java
index 42a177dfc5..aeef8fd12b 100644
--- 
a/streampipes-rest-extensions/src/main/java/org/apache/streampipes/rest/extensions/connect/GuessResource.java
+++ 
b/streampipes-rest-extensions/src/main/java/org/apache/streampipes/rest/extensions/connect/AdapterWorkerGuessResource.java
@@ -20,10 +20,11 @@ package org.apache.streampipes.rest.extensions.connect;
 
 import org.apache.streampipes.commons.exceptions.connect.AdapterException;
 import org.apache.streampipes.commons.exceptions.connect.ParseException;
-import org.apache.streampipes.extensions.management.connect.GuessManagement;
+import 
org.apache.streampipes.extensions.management.connect.AdapterWorkerGuessManagement;
 import 
org.apache.streampipes.extensions.management.context.AdapterContextGenerator;
 import org.apache.streampipes.model.connect.adapter.AdapterDescription;
 import org.apache.streampipes.model.connect.guess.GuessSchema;
+import org.apache.streampipes.model.connect.guess.SampleData;
 import org.apache.streampipes.model.monitoring.SpLogMessage;
 import org.apache.streampipes.rest.shared.exception.SpLogMessageException;
 import org.apache.streampipes.rest.shared.impl.AbstractSharedRestInterface;
@@ -40,17 +41,17 @@ import 
org.springframework.web.bind.annotation.RestController;
 
 @RestController
 @RequestMapping("/api/v1/worker/guess")
-public class GuessResource extends AbstractSharedRestInterface {
+public class AdapterWorkerGuessResource extends AbstractSharedRestInterface {
 
-  private static final Logger logger = 
LoggerFactory.getLogger(GuessResource.class);
+  private static final Logger LOG = 
LoggerFactory.getLogger(AdapterWorkerGuessResource.class);
 
-  private final GuessManagement guessManagement;
+  private final AdapterWorkerGuessManagement guessManagement;
 
-  public GuessResource() {
-    this.guessManagement = new GuessManagement(new 
AdapterContextGenerator().makeGuessSchemaContext());
+  public AdapterWorkerGuessResource() {
+    this.guessManagement = new AdapterWorkerGuessManagement(new 
AdapterContextGenerator().makeGuessSchemaContext());
   }
 
-  public GuessResource(GuessManagement guessManagement) {
+  public AdapterWorkerGuessResource(AdapterWorkerGuessManagement 
guessManagement) {
     this.guessManagement = guessManagement;
   }
 
@@ -65,14 +66,30 @@ public class GuessResource extends 
AbstractSharedRestInterface {
 
       return ok(result);
     } catch (ParseException e) {
-      logger.error("Error while parsing events: ", e);
+      LOG.error("Error while parsing events: ", e);
       throw new SpLogMessageException(HttpStatus.INTERNAL_SERVER_ERROR, 
SpLogMessage.from(e));
     } catch (AdapterException e) {
-      logger.error("Error while guessing schema for AdapterDescription: {}, 
{}", adapterDescription.getElementId(),
-          e.getMessage());
+      LOG.error(
+          "Error while guessing schema for AdapterDescription: {}, {}", 
adapterDescription.getElementId(),
+          e.getMessage()
+      );
       throw new SpLogMessageException(HttpStatus.INTERNAL_SERVER_ERROR, 
SpLogMessage.from(e));
     }
 
   }
+
+  @PostMapping(
+      path = "/sample",
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE)
+  public ResponseEntity<SampleData> getSampleData(@RequestBody 
AdapterDescription adapterDescription)
+      throws AdapterException {
+
+    var sampleData = guessManagement.getSampleData(adapterDescription);
+
+    return ok(sampleData);
+  }
+
+
 }
 
diff --git 
a/streampipes-rest-shared/src/main/java/org/apache/streampipes/rest/shared/exception/RestResponseLogMessageExceptionHandler.java
 
b/streampipes-rest-shared/src/main/java/org/apache/streampipes/rest/shared/exception/SpRestExceptionHandler.java
similarity index 68%
rename from 
streampipes-rest-shared/src/main/java/org/apache/streampipes/rest/shared/exception/RestResponseLogMessageExceptionHandler.java
rename to 
streampipes-rest-shared/src/main/java/org/apache/streampipes/rest/shared/exception/SpRestExceptionHandler.java
index 97c3d5beaf..8b74981166 100644
--- 
a/streampipes-rest-shared/src/main/java/org/apache/streampipes/rest/shared/exception/RestResponseLogMessageExceptionHandler.java
+++ 
b/streampipes-rest-shared/src/main/java/org/apache/streampipes/rest/shared/exception/SpRestExceptionHandler.java
@@ -18,6 +18,10 @@
 
 package org.apache.streampipes.rest.shared.exception;
 
+import org.apache.streampipes.commons.exceptions.connect.AdapterException;
+import org.apache.streampipes.model.monitoring.SpLogMessage;
+
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -25,10 +29,16 @@ import org.springframework.web.context.request.WebRequest;
 import 
org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
 
 @ControllerAdvice
-public class RestResponseLogMessageExceptionHandler extends 
ResponseEntityExceptionHandler {
+public class SpRestExceptionHandler extends ResponseEntityExceptionHandler {
+
+  @ExceptionHandler(value = {AdapterException.class})
+  private ResponseEntity<Object> handleAdapterException(RuntimeException ex, 
WebRequest request) {
+    var spLogMessageException =  new 
SpLogMessageException(HttpStatus.INTERNAL_SERVER_ERROR, SpLogMessage.from(ex));
+    return handleSpLogMessageException(spLogMessageException, request);
+  }
 
   @ExceptionHandler(value = {SpLogMessageException.class})
-  protected ResponseEntity<Object> handleException(
+  protected ResponseEntity<Object> handleSpLogMessageException(
       RuntimeException ex, WebRequest request) {
     var exception = (SpLogMessageException) ex;
     return ResponseEntity
diff --git 
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/GuessResource.java
 
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/GuessResource.java
index f49dfaa7c9..8d3a25e9f7 100644
--- 
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/GuessResource.java
+++ 
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/GuessResource.java
@@ -86,6 +86,24 @@ public class GuessResource extends 
AbstractAdapterResource<GuessManagement> {
     }
   }
 
+  @PostMapping(
+      path = "/sample",
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE)
+  @PreAuthorize("this.hasWriteAuthority()")
+  public ResponseEntity<?> getSampleData(@RequestBody AdapterDescription 
adapterDescription) {
+    try {
+      return ok(managementService.getSampleData(adapterDescription));
+    } catch (WorkerAdapterException e) {
+      LOG.error(e.getMessage());
+      return serverError(e.getExceptionMessage());
+    } catch (NoServiceEndpointsAvailableException | IOException e) {
+      LOG.error(e.getMessage());
+      return serverError(SpLogMessage.from(e));
+    }
+  }
+
+
   /**
    * required by Spring expression
    */
diff --git 
a/streampipes-service-extensions/src/main/java/org/apache/streampipes/service/extensions/StreamPipesExtensionsServiceBase.java
 
b/streampipes-service-extensions/src/main/java/org/apache/streampipes/service/extensions/StreamPipesExtensionsServiceBase.java
index d25b2b45c1..ddb6c1664b 100644
--- 
a/streampipes-service-extensions/src/main/java/org/apache/streampipes/service/extensions/StreamPipesExtensionsServiceBase.java
+++ 
b/streampipes-service-extensions/src/main/java/org/apache/streampipes/service/extensions/StreamPipesExtensionsServiceBase.java
@@ -33,7 +33,7 @@ import 
org.apache.streampipes.model.extensions.svcdiscovery.SpServiceRegistratio
 import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTag;
 import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
 import org.apache.streampipes.rest.extensions.WelcomePage;
-import 
org.apache.streampipes.rest.shared.exception.RestResponseLogMessageExceptionHandler;
+import org.apache.streampipes.rest.shared.exception.SpRestExceptionHandler;
 import org.apache.streampipes.service.base.BaseNetworkingConfig;
 import org.apache.streampipes.service.base.StreamPipesPrometheusConfig;
 import org.apache.streampipes.service.base.StreamPipesServiceBase;
@@ -63,7 +63,7 @@ import java.util.stream.Collectors;
     WebSecurityConfig.class,
     WelcomePage.class,
     ServiceHealthResource.class,
-    RestResponseLogMessageExceptionHandler.class,
+    SpRestExceptionHandler.class,
     StreamPipesPrometheusConfig.class
 })
 @ComponentScan({"org.apache.streampipes.rest.extensions.*", 
"org.apache.streampipes.service.base.rest.*"})
diff --git 
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
 
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
index 4589e61e38..c639dbe6fd 100644
--- 
a/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
+++ 
b/ui/projects/streampipes/platform-services/src/lib/model/gen/streampipes-model-client.ts
@@ -19,7 +19,7 @@
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 3.2.1263 on 2025-12-02 
15:38:17.
+// Generated using typescript-generator version 3.2.1263 on 2025-12-08 
09:56:34.
 
 import { Storable } from './platform-services';
 
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 c9e29a27a6..bc28e9ab67 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
@@ -19,7 +19,7 @@
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 3.2.1263 on 2025-12-02 
15:38:14.
+// Generated using typescript-generator version 3.2.1263 on 2025-12-08 
09:56:32.
 
 export class NamedStreamPipesEntity implements Storable {
     '@class':
@@ -1180,11 +1180,18 @@ export class DashboardEntity implements Storable, 
SpResource {
 export class DashboardItem {
     cols: number;
     component: string;
+    dataViewElementId: string;
+    h: number;
     id: string;
     name: string;
     rows: number;
     settings: string[];
     timeSettings: { [index: string]: any };
+    w: number;
+    /**
+     * @deprecated since 0.99.0, for removal
+     */
+    widgetId: string;
     x: number;
     y: number;
 
@@ -1198,6 +1205,8 @@ export class DashboardItem {
         const instance = target || new DashboardItem();
         instance.cols = data.cols;
         instance.component = data.component;
+        instance.dataViewElementId = data.dataViewElementId;
+        instance.h = data.h;
         instance.id = data.id;
         instance.name = data.name;
         instance.rows = data.rows;
@@ -1207,6 +1216,8 @@ export class DashboardItem {
         instance.timeSettings = __getCopyObjectFn(__identity<any>())(
             data.timeSettings,
         );
+        instance.w = data.w;
+        instance.widgetId = data.widgetId;
         instance.x = data.x;
         instance.y = data.y;
         return instance;
@@ -3695,6 +3706,21 @@ export class RuntimeResolvableTreeInputStaticProperty 
extends StaticProperty {
     }
 }
 
+export class SampleData {
+    samples: { [index: string]: any }[];
+
+    static fromData(data: SampleData, target?: SampleData): SampleData {
+        if (!data) {
+            return data;
+        }
+        const instance = target || new SampleData();
+        instance.samples = __getCopyArrayFn(
+            __getCopyObjectFn(__identity<any>()),
+        )(data.samples);
+        return instance;
+    }
+}
+
 export class SecretStaticProperty extends StaticProperty {
     '@class': 
'org.apache.streampipes.model.staticproperty.SecretStaticProperty';
     'encrypted': boolean;
diff --git 
a/ui/src/app/connect/components/adapter-configuration/adapter-configuration.component.html
 
b/ui/src/app/connect/components/adapter-configuration/adapter-configuration.component.html
index e4d1cd9252..bcec633221 100644
--- 
a/ui/src/app/connect/components/adapter-configuration/adapter-configuration.component.html
+++ 
b/ui/src/app/connect/components/adapter-configuration/adapter-configuration.component.html
@@ -29,7 +29,7 @@
         <button
             mat-icon-button
             color="accent"
-            (click)="removeSelection()"
+            (click)="navigateToAdapterCatalog()"
             [matTooltip]="'Cancel' | translate"
         >
             <i class="material-icons">close</i>
@@ -53,11 +53,25 @@
                     <sp-adapter-settings
                         [adapterDescription]="adapter"
                         (updateAdapterDescriptionEmitter)="adapter = $event"
-                        (clickNextEmitter)="clickSpecificSettingsNextButton()"
-                        (removeSelectionEmitter)="removeSelection()"
+                        (nextEmitter)="clickSpecificSettingsNextButton()"
+                        (cancelEmitter)="navigateToAdapterCatalog()"
                     >
                     </sp-adapter-settings>
                 </mat-step>
+
+                <mat-step>
+                    <ng-template matStepLabel>{{
+                        'Event Preview' | translate
+                    }}</ng-template>
+                    <sp-event-preview
+                        [adapterDescription]="adapter"
+                        (nextEmitter)="clickEventPreviewNextButton()"
+                        (goBackEmitter)="goBack()"
+                        (cancelEmitter)="navigateToAdapterCatalog()"
+                    >
+                    </sp-event-preview>
+                </mat-step>
+
                 <mat-step>
                     <ng-template matStepLabel>{{
                         'Configure fields' | translate
@@ -67,9 +81,9 @@
                         fxFlex="100"
                         [adapterDescription]="adapter"
                         [isEditMode]="isEditMode"
-                        (clickNextEmitter)="clickEventSchemaNextButtonButton()"
+                        (nextEmitter)="clickEventSchemaNextButtonButton()"
                         (goBackEmitter)="goBack()"
-                        (removeSelectionEmitter)="removeSelection()"
+                        (cancelEmitter)="navigateToAdapterCatalog()"
                     >
                     </sp-event-schema>
                 </mat-step>
@@ -81,8 +95,7 @@
                         [adapterDescription]="adapter"
                         [eventSchema]="adapter.dataStream.eventSchema"
                         [isEditMode]="isEditMode"
-                        [stepper]="stepper"
-                        (removeSelectionEmitter)="removeSelection()"
+                        (cancelEmitter)="navigateToAdapterCatalog()"
                         (goBackEmitter)="goBack()"
                         (adapterStartedEmitter)="adapterWasStarted()"
                     >
diff --git 
a/ui/src/app/connect/components/adapter-configuration/adapter-configuration.component.ts
 
b/ui/src/app/connect/components/adapter-configuration/adapter-configuration.component.ts
index c204452b80..89db37a363 100644
--- 
a/ui/src/app/connect/components/adapter-configuration/adapter-configuration.component.ts
+++ 
b/ui/src/app/connect/components/adapter-configuration/adapter-configuration.component.ts
@@ -64,10 +64,14 @@ export class AdapterConfigurationComponent implements 
OnInit {
             : this.translate.instant('New adapter: ') + this.displayName;
     }
 
-    removeSelection() {
+    navigateToAdapterCatalog() {
         this.router.navigate(['connect']).then();
     }
 
+    clickEventPreviewNextButton() {
+        this.goForward();
+    }
+
     clickSpecificSettingsNextButton() {
         this.shepherdService.trigger('specific-settings-next-button');
         this.eventSchemaComponent.guessSchema();
diff --git 
a/ui/src/app/connect/components/adapter-configuration/adapter-settings/adapter-settings.component.html
 
b/ui/src/app/connect/components/adapter-configuration/adapter-settings/adapter-settings.component.html
index bd75b95c2d..50788df4ac 100644
--- 
a/ui/src/app/connect/components/adapter-configuration/adapter-settings/adapter-settings.component.html
+++ 
b/ui/src/app/connect/components/adapter-configuration/adapter-settings/adapter-settings.component.html
@@ -82,7 +82,7 @@
             class="mat-basic"
             mat-flat-button
             style="margin-right: 10px"
-            (click)="removeSelection()"
+            (click)="cancel()"
             data-cy="connect-new-adapter-cancel"
         >
             {{ 'Cancel' | translate }}
@@ -101,7 +101,7 @@
                 class="stepper-button"
                 [disabled]="!specificAdapterSettingsFormValid"
                 data-cy="adapter-settings-next-button"
-                (click)="clickNext()"
+                (click)="next()"
                 color="accent"
                 mat-flat-button
             >
diff --git 
a/ui/src/app/connect/components/adapter-configuration/adapter-settings/adapter-settings.component.ts
 
b/ui/src/app/connect/components/adapter-configuration/adapter-settings/adapter-settings.component.ts
index 3e56321f41..6022095026 100644
--- 
a/ui/src/app/connect/components/adapter-configuration/adapter-settings/adapter-settings.component.ts
+++ 
b/ui/src/app/connect/components/adapter-configuration/adapter-settings/adapter-settings.component.ts
@@ -59,13 +59,12 @@ export class AdapterSettingsComponent implements OnInit {
     /**
      * Cancels the adapter configuration process
      */
-    @Output() removeSelectionEmitter: EventEmitter<boolean> =
-        new EventEmitter();
+    @Output() cancelEmitter: EventEmitter<boolean> = new EventEmitter();
 
     /**
      * Go to next configuration step when this is complete
      */
-    @Output() clickNextEmitter: EventEmitter<MatStepper> = new EventEmitter();
+    @Output() nextEmitter: EventEmitter<MatStepper> = new EventEmitter();
 
     cachedAdapterDescription: AdapterDescription;
     availableTemplates: PipelineElementTemplate[];
@@ -111,12 +110,12 @@ export class AdapterSettingsComponent implements OnInit {
             });
     }
 
-    public removeSelection() {
-        this.removeSelectionEmitter.emit();
+    public cancel() {
+        this.cancelEmitter.emit();
     }
 
-    public clickNext() {
-        this.clickNextEmitter.emit();
+    public next() {
+        this.nextEmitter.emit();
     }
 
     loadTemplate(event: any) {
diff --git 
a/ui/src/app/connect/components/adapter-configuration/event-preview/event-preview.component.html
 
b/ui/src/app/connect/components/adapter-configuration/event-preview/event-preview.component.html
new file mode 100644
index 0000000000..e08f7ec336
--- /dev/null
+++ 
b/ui/src/app/connect/components/adapter-configuration/event-preview/event-preview.component.html
@@ -0,0 +1,136 @@
+<!--
+~ Licensed to the Apache Software Foundation (ASF) under one or more
+~ contributor license agreements.  See the NOTICE file distributed with
+~ this work for additional information regarding copyright ownership.
+~ The ASF licenses this file to You under the Apache License, Version 2.0
+~ (the "License"); you may not use this file except in compliance with
+~ the License.  You may obtain a copy of the License at
+~
+~    http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+~
+-->
+<div fxFlex="100" fxLayout="column">
+    <div fxFlex="100" fxLayout="column">
+        <sp-basic-inner-panel
+            [panelTitle]="'Basic Settings' | translate"
+            outerMargin="20px 0px"
+        >
+            <div header fxLayoutAlign="end center" fxFlex="100">
+                <button
+                    color="accent"
+                    mat-button
+                    matTooltip="Select existing format"
+                    [matMenuTriggerFor]="sampleMenu"
+                    data-cy="select-existing-format-button"
+                >
+                    <mat-icon>format_list_bulleted</mat-icon>
+                    <span>Select existing format</span>
+                </button>
+
+                <mat-menu #sampleMenu="matMenu" data-cy="sample-script-menu">
+                    <button
+                        mat-menu-item
+                        *ngFor="let s of sampleScripts"
+                        (click)="selectSample(s.key)"
+                        data-cy="sample-script-{{ s.key }}"
+                    >
+                        {{ s.title }}
+                    </button>
+                </mat-menu>
+                <button
+                    color="accent"
+                    mat-button
+                    matTooltip="Run script"
+                    (click)="runScript()"
+                >
+                    <mat-icon>play_circle_filled</mat-icon>
+                    <span>Run script</span>
+                </button>
+                <button
+                    color="accent"
+                    mat-button
+                    matTooltip="Get Sample Event"
+                    (click)="getSampleEvent()"
+                >
+                    <mat-icon>refresh</mat-icon>
+                    <span>Get Sample Event</span>
+                </button>
+            </div>
+            <label
+                >Script (provide function body that receives `event` and 
returns
+                transformed event)</label
+            >
+
+            <ngx-codemirror
+                style="width: 100%; height: 220px"
+                [(ngModel)]="script"
+                [options]="editorOptions"
+                data-cy="event-preview-script-editor"
+            ></ngx-codemirror>
+
+            @if (runError) {
+                <div style="color: #b00020; margin-top: 8px">
+                    <strong>Error:</strong> {{ runError }}
+                </div>
+            }
+        </sp-basic-inner-panel>
+    </div>
+
+    <div fxFlex="100" fxLayout="row" fxLayoutGap="15px">
+        <div fxFlex="50" fxLayout="column">
+            <sp-basic-inner-panel
+                [showTitle]="true"
+                [panelTitle]="'Original (Parsed)' | translate"
+            >
+                <pre
+                    [innerHTML]="input | jsonpretty"
+                    class="preview-text"
+                    data-cy="schema-preview-original-event"
+                ></pre>
+            </sp-basic-inner-panel>
+        </div>
+
+        <div fxFlex="50" fxLayout="column">
+            <sp-basic-inner-panel
+                [showTitle]="true"
+                [panelTitle]="'Result' | translate"
+                fxFlex="100"
+                data-cy="connect-schema-update-preview"
+            >
+                <pre
+                    [innerHTML]="output | jsonpretty"
+                    class="preview-text"
+                    data-cy="schema-preview-result-event"
+                ></pre>
+            </sp-basic-inner-panel>
+        </div>
+    </div>
+
+    <div fxLayoutAlign="end" class="mt-10">
+        <button class="mat-basic" mat-flat-button (click)="cancel()">
+            {{ 'Cancel' | translate }}
+        </button>
+        <button
+            class="mat-basic stepper-button"
+            mat-flat-button
+            (click)="goBack()"
+        >
+            {{ 'Back' | translate }}
+        </button>
+        <button
+            class="stepper-button"
+            data-cy="sp-event-schema-next-button"
+            color="accent"
+            mat-flat-button
+            (click)="next()"
+        >
+            {{ 'Next' | translate }}
+        </button>
+    </div>
+</div>
diff --git 
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/util/WorkerPaths.java
 
b/ui/src/app/connect/components/adapter-configuration/event-preview/event-preview.component.scss
similarity index 54%
copy from 
streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/util/WorkerPaths.java
copy to 
ui/src/app/connect/components/adapter-configuration/event-preview/event-preview.component.scss
index 7bd2b0c1ed..77983ac926 100644
--- 
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/util/WorkerPaths.java
+++ 
b/ui/src/app/connect/components/adapter-configuration/event-preview/event-preview.component.scss
@@ -15,30 +15,6 @@
  * limitations under the License.
  *
  */
-package org.apache.streampipes.connect.management.util;
-
-public class WorkerPaths {
-
-  private static final String WorkerMainPath = "/api/v1/worker";
-
-  public static String getStreamInvokePath() {
-    return WorkerMainPath + "/stream/invoke";
-  }
-
-  public static String getStreamStopPath() {
-    return WorkerMainPath + "/stream/stop";
-  }
-
-  public static String getRunningAdaptersPath() {
-    return WorkerMainPath + "/running";
-  }
-
-  public static String getRuntimeResolvablePath(String elementId) {
-    return WorkerMainPath + "/resolvable/" + elementId + "/configurations";
-  }
-
-  public static String getGuessSchemaPath() {
-    return WorkerMainPath + "/guess/schema";
-  }
-
+.stepper-button {
+    margin-left: 10px;
 }
diff --git 
a/ui/src/app/connect/components/adapter-configuration/event-preview/event-preview.component.ts
 
b/ui/src/app/connect/components/adapter-configuration/event-preview/event-preview.component.ts
new file mode 100644
index 0000000000..fbd37fe8f2
--- /dev/null
+++ 
b/ui/src/app/connect/components/adapter-configuration/event-preview/event-preview.component.ts
@@ -0,0 +1,169 @@
+/*
+ * 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 {
+    Component,
+    EventEmitter,
+    inject,
+    Input,
+    OnInit,
+    Output,
+} from '@angular/core';
+import { MatStepper } from '@angular/material/stepper';
+import { RestService } from '../../../services/rest.service';
+import { AdapterDescription } from '@streampipes/platform-services';
+
+@Component({
+    selector: 'sp-event-preview',
+    standalone: false,
+    templateUrl: './event-preview.component.html',
+    styleUrl: './event-preview.component.scss',
+})
+export class EventPreviewComponent {
+    restService = inject(RestService);
+
+    @Input()
+    adapterDescription: AdapterDescription;
+
+    @Output()
+    goBackEmitter: EventEmitter<MatStepper> = new EventEmitter();
+
+    @Output()
+    cancelEmitter: EventEmitter<void> = new EventEmitter();
+
+    @Output()
+    nextEmitter: EventEmitter<MatStepper> = new EventEmitter();
+
+    runError: string | null = null;
+
+    sampleScripts: any[] = [
+        {
+            key: 'identity',
+            title: 'Identity (return event)',
+            value: `// returns the same event
+return event;`,
+        },
+        {
+            key: 'complex',
+            title: 'Simulator (Complex event)',
+            value: ` var flattened = {};
+
+    // 1. Define the keys we want to extract (hardcoded list of top-level 
primitives)
+    var KEYS_TO_EXTRACT = [
+        "phase",
+        "sensorType",
+        "active",
+        "timestamp",
+        "sensorId"
+    ];
+
+    // Handles null or non-object inputs gracefully
+    if (typeof event !== 'object' || event === null) {
+        return flattened;
+    }
+
+    // 2. Iterate only over the hardcoded list of keys using a traditional for 
loop
+    for (var i = 0; i < KEYS_TO_EXTRACT.length; i++) {
+        var key = KEYS_TO_EXTRACT[i];
+        // Use hasOwnProperty to ensure the property exists directly on the 
object
+        if (Object.prototype.hasOwnProperty.call(event, key)) {
+            flattened[key] = event[key];
+        }
+    }
+
+    return flattened;`,
+        },
+    ];
+    selectSample(key: string) {
+        const s = this.sampleScripts.find(x => x.key === key);
+        if (s) {
+            this.script = s.value;
+            this.runError = null;
+            // optional: reset output when selecting a new sample
+            this.output = null;
+        }
+    }
+
+    editorOptions = {
+        mode: 'javascript',
+        autoRefresh: true,
+        theme: 'dracula',
+        autoCloseBrackets: true,
+        lineNumbers: true,
+        lineWrapping: true,
+        gutters: ['CodeMirror-lint-markers'],
+        lint: true,
+        extraKeys: {
+            'Ctrl-Space': 'autocomplete',
+        },
+    };
+
+    input = {};
+    output = {};
+
+    script = `return event;`;
+
+    getSampleEvent(): void {
+        this.restService
+            .getSampleEvents(this.adapterDescription)
+            .subscribe(sampleData => {
+                this.input = sampleData.samples[0];
+            });
+    }
+
+    runScript(): void {
+        this.runError = null;
+        const inputClone = this.input
+            ? JSON.parse(JSON.stringify(this.input))
+            : {};
+        try {
+            // First try: treat the editor content as a function body that 
returns the transformed event
+            try {
+                const fn = new Function('event', this.script);
+                const result = fn(inputClone);
+                this.output = result === undefined ? null : result;
+                return;
+            } catch (e) {
+                // fallback: try to parse the editor contents as a function 
expression, e.g. `(event) => {...}` or `function(event){...}`
+                const maybeFn = eval(`(${this.script})`);
+                if (typeof maybeFn === 'function') {
+                    const result = maybeFn(inputClone);
+                    this.output = result === undefined ? null : result;
+                    return;
+                }
+                // If not a function, throw original error
+                throw e;
+            }
+        } catch (err: any) {
+            this.runError = err && err.message ? err.message : String(err);
+            this.output = null;
+        }
+    }
+
+    public cancel() {
+        this.cancelEmitter.emit();
+    }
+
+    public next() {
+        this.nextEmitter.emit();
+    }
+
+    public goBack() {
+        this.goBackEmitter.emit();
+    }
+}
diff --git 
a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.html
 
b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.html
index d11e03519e..233df63871 100644
--- 
a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.html
+++ 
b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.html
@@ -131,7 +131,7 @@
 </div>
 
 <div fxLayoutAlign="end" class="mt-10">
-    <button class="mat-basic" mat-flat-button (click)="removeSelection()">
+    <button class="mat-basic" mat-flat-button (click)="cancel()">
         {{ 'Cancel' | translate }}
     </button>
     <button class="mat-basic stepper-button" mat-flat-button 
(click)="goBack()">
@@ -143,7 +143,7 @@
         data-cy="sp-event-schema-next-button"
         color="accent"
         mat-flat-button
-        (click)="clickNext()"
+        (click)="next()"
         [disabled]="!validEventSchema"
     >
         {{ 'Next' | translate }}
diff --git 
a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.ts
 
b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.ts
index b7600ba737..09dbd41100 100644
--- 
a/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.ts
+++ 
b/ui/src/app/connect/components/adapter-configuration/schema-editor/event-schema/event-schema.component.ts
@@ -85,13 +85,13 @@ export class EventSchemaComponent implements OnChanges, 
OnDestroy {
      * Cancels the adapter configuration process
      */
     @Output()
-    removeSelectionEmitter: EventEmitter<boolean> = new EventEmitter();
+    cancelEmitter: EventEmitter<boolean> = new EventEmitter();
 
     /**
      * Go to next configuration step when this is complete
      */
     @Output()
-    clickNextEmitter: EventEmitter<MatStepper> = new EventEmitter();
+    nextEmitter: EventEmitter<MatStepper> = new EventEmitter();
 
     _tree: TreeComponent;
 
@@ -297,12 +297,12 @@ export class EventSchemaComponent implements OnChanges, 
OnDestroy {
         }, 200);
     }
 
-    public removeSelection() {
-        this.removeSelectionEmitter.emit();
+    public cancel() {
+        this.cancelEmitter.emit();
     }
 
-    public clickNext() {
-        this.clickNextEmitter.emit();
+    public next() {
+        this.nextEmitter.emit();
     }
 
     public goBack() {
diff --git 
a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
 
b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
index 4ac0e4aa4f..7d2087ca86 100644
--- 
a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
+++ 
b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html
@@ -268,7 +268,7 @@
     </div>
 
     <div fxLayoutAlign="end" style="margin-top: 10px">
-        <button class="mat-basic" mat-flat-button (click)="removeSelection()">
+        <button class="mat-basic" mat-flat-button (click)="cancel()">
             {{ 'Cancel' | translate }}
         </button>
         <button
diff --git 
a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts
 
b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts
index 8088f85f40..d891432dba 100644
--- 
a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts
+++ 
b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts
@@ -68,8 +68,7 @@ export class StartAdapterConfigurationComponent implements 
OnInit {
     /**
      * Cancels the adapter configuration process
      */
-    @Output() removeSelectionEmitter: EventEmitter<boolean> =
-        new EventEmitter();
+    @Output() cancelEmitter: EventEmitter<boolean> = new EventEmitter();
 
     /**
      * Is called when the adapter was created
@@ -268,8 +267,8 @@ export class StartAdapterConfigurationComponent implements 
OnInit {
         }
     }
 
-    public removeSelection() {
-        this.removeSelectionEmitter.emit();
+    public cancel() {
+        this.cancelEmitter.emit();
     }
 
     public goBack() {
diff --git a/ui/src/app/connect/connect.module.ts 
b/ui/src/app/connect/connect.module.ts
index 2b25844896..aa10769fc5 100644
--- a/ui/src/app/connect/connect.module.ts
+++ b/ui/src/app/connect/connect.module.ts
@@ -108,6 +108,8 @@ import { MatTreeModule } from '@angular/material/tree';
 import { TranslateModule } from '@ngx-translate/core';
 import { TranslatePipe } from '@ngx-translate/core';
 import { ConfigurationGroupComponent } from 
'./components/adapter-configuration/adapter-settings/configuration-group/configuration-group.component';
+import { EventPreviewComponent } from 
'./components/adapter-configuration/event-preview/event-preview.component';
+import { CodemirrorModule } from '@ctrl/ngx-codemirror';
 
 @NgModule({
     imports: [
@@ -131,6 +133,7 @@ import { ConfigurationGroupComponent } from 
'./components/adapter-configuration/
         MatProgressBarModule,
         MatButtonToggleModule,
         CoreUiModule,
+        CodemirrorModule,
         FormsModule,
         ReactiveFormsModule,
         CommonModule,
@@ -252,6 +255,7 @@ import { ConfigurationGroupComponent } from 
'./components/adapter-configuration/
         EventSchemaErrorHintsComponent,
         CanNotEditAdapterDialog,
         AllAdapterActionsComponent,
+        EventPreviewComponent,
     ],
     providers: [TimestampPipe],
     schemas: [CUSTOM_ELEMENTS_SCHEMA],
diff --git a/ui/src/app/connect/services/rest.service.ts 
b/ui/src/app/connect/services/rest.service.ts
index 0a181943a0..55a4dd24c7 100644
--- a/ui/src/app/connect/services/rest.service.ts
+++ b/ui/src/app/connect/services/rest.service.ts
@@ -28,6 +28,7 @@ import {
     AdapterEventPreview,
     GuessSchema,
     PlatformServicesCommons,
+    SampleData,
     SpDataStream,
 } from '@streampipes/platform-services';
 import { NGX_LOADING_BAR_IGNORED } from '@ngx-loading-bar/http-client';
@@ -55,6 +56,19 @@ export class RestService {
             );
     }
 
+    // TODO refactor
+    getSampleEvents(adapter: AdapterDescription): Observable<SampleData> {
+        return this.http
+            .post(`${this.connectPath}/master/guess/sample`, adapter, {
+                context: new HttpContext().set(NGX_LOADING_BAR_IGNORED, true),
+            })
+            .pipe(
+                map(response => {
+                    return SampleData.fromData(response as SampleData);
+                }),
+            );
+    }
+
     getAdapterEventPreview(
         adapterEventPreview: AdapterEventPreview,
     ): Observable<Record<string, any>> {

Reply via email to