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>> {