You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2026/01/02 12:56:48 UTC
(streampipes) branch add-multi-language-support-templates created (now 23765e02b3)
This is an automated email from the ASF dual-hosted git repository.
riemer pushed a change to branch add-multi-language-support-templates
in repository https://gitbox.apache.org/repos/asf/streampipes.git
at 23765e02b3 Add multi-language support to connect transformation
This branch includes the following new commits:
new 23765e02b3 Add multi-language support to connect transformation
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(streampipes) 01/01: Add multi-language support to connect transformation
Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch add-multi-language-support-templates
in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 23765e02b3a0a127258caf88a753605dc313af48
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Fri Jan 2 13:56:27 2026 +0100
Add multi-language support to connect transformation
---
streampipes-connect-transformer-api/pom.xml | 10 ++-
.../transformer/api/TransformationEngine.java | 3 +-
.../transformer/api/TransformationEngines.java | 13 +++-
.../transformer/groovy/GroovyScriptEngine.java | 11 +++-
.../transformer/js/GraalJsScriptEngine.java | 16 ++++-
.../streampipes/model/connect/ScriptMetadata.java | 12 ++--
.../svcdiscovery/SpServiceRegistration.java | 23 ++++++-
.../ExtensionsServiceEndpointGenerator.java | 10 +--
.../endpoint/ExtensionsServiceEndpointUtils.java | 11 ++++
.../TransformationScriptLanguageResource.java | 66 +++++++++++++++++++
.../svcdiscovery/api/ISpServiceDiscovery.java | 4 ++
.../svcdiscovery/SpServiceDiscoveryCore.java | 12 +++-
.../StreamPipesExtensionsServiceBase.java | 9 ++-
.../lib/apis/connect-script-languages.service.ts | 29 +++++++++
.../lib/apis/connect-script-templates.service.ts | 0
.../src/lib/model/gen/streampipes-model.ts | 47 +++++++++++++-
.../platform-services/src/public-api.ts | 1 +
.../adapter-configuration-state.service.ts | 14 +++-
.../configure-fields-preview.component.scss | 4 +-
.../configure-fields-error-message.component.html | 9 ++-
.../configure-fields-error-message.component.scss | 2 +-
.../configure-schema.component.html | 75 ++++++++++++++++++----
.../configure-schema.component.scss | 4 +-
.../configure-schema/configure-schema.component.ts | 71 ++++++++++++++++----
24 files changed, 390 insertions(+), 66 deletions(-)
diff --git a/streampipes-connect-transformer-api/pom.xml b/streampipes-connect-transformer-api/pom.xml
index 1278b99369..801c646bb3 100644
--- a/streampipes-connect-transformer-api/pom.xml
+++ b/streampipes-connect-transformer-api/pom.xml
@@ -31,9 +31,15 @@
<artifactId>streampipes-connect-transformer-api</artifactId>
<properties>
- <maven.compiler.source>21</maven.compiler.source>
- <maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.streampipes</groupId>
+ <artifactId>streampipes-model</artifactId>
+ <version>0.99.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
</project>
diff --git a/streampipes-connect-transformer-api/src/main/java/org/apache/streampipes/connect/transformer/api/TransformationEngine.java b/streampipes-connect-transformer-api/src/main/java/org/apache/streampipes/connect/transformer/api/TransformationEngine.java
index cb5b210c1f..11e34c774b 100644
--- a/streampipes-connect-transformer-api/src/main/java/org/apache/streampipes/connect/transformer/api/TransformationEngine.java
+++ b/streampipes-connect-transformer-api/src/main/java/org/apache/streampipes/connect/transformer/api/TransformationEngine.java
@@ -19,6 +19,7 @@
package org.apache.streampipes.connect.transformer.api;
import org.apache.streampipes.connect.transformer.api.exception.ScriptCompilationException;
+import org.apache.streampipes.model.connect.ScriptMetadata;
/**
* Compiles code templates into reusable transformers.
@@ -28,7 +29,7 @@ public interface TransformationEngine {
/**
* Identifier of the underlying scripting language.
*/
- String language();
+ ScriptMetadata metadata();
/**
* Compile the user-provided script into an executable transformer.
diff --git a/streampipes-connect-transformer-api/src/main/java/org/apache/streampipes/connect/transformer/api/TransformationEngines.java b/streampipes-connect-transformer-api/src/main/java/org/apache/streampipes/connect/transformer/api/TransformationEngines.java
index 300668bae7..99b2a8863c 100644
--- a/streampipes-connect-transformer-api/src/main/java/org/apache/streampipes/connect/transformer/api/TransformationEngines.java
+++ b/streampipes-connect-transformer-api/src/main/java/org/apache/streampipes/connect/transformer/api/TransformationEngines.java
@@ -18,7 +18,10 @@
package org.apache.streampipes.connect.transformer.api;
+import org.apache.streampipes.model.connect.ScriptMetadata;
+
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
@@ -29,10 +32,18 @@ public enum TransformationEngines {
private final Map<String, Supplier<TransformationEngine>> transformationEngines = new HashMap<>();
public void registerEngine(Supplier<TransformationEngine> engineSupplier) {
- transformationEngines.put(engineSupplier.get().language(), engineSupplier);
+ transformationEngines.put(engineSupplier.get().metadata().language(), engineSupplier);
}
public TransformationEngine getTransformationEngine(String language) {
return transformationEngines.get(language).get();
}
+
+ public List<ScriptMetadata> getAvailableEngineMetadata() {
+ return transformationEngines
+ .values()
+ .stream()
+ .map(transformationEngineSupplier -> transformationEngineSupplier.get().metadata())
+ .toList();
+ }
}
diff --git a/streampipes-connect-transformer-groovy/src/main/java/org/apache/streampipes/connect/transformer/groovy/GroovyScriptEngine.java b/streampipes-connect-transformer-groovy/src/main/java/org/apache/streampipes/connect/transformer/groovy/GroovyScriptEngine.java
index 406a33bad6..a217b8b3aa 100644
--- a/streampipes-connect-transformer-groovy/src/main/java/org/apache/streampipes/connect/transformer/groovy/GroovyScriptEngine.java
+++ b/streampipes-connect-transformer-groovy/src/main/java/org/apache/streampipes/connect/transformer/groovy/GroovyScriptEngine.java
@@ -23,6 +23,7 @@ import org.apache.streampipes.connect.transformer.api.TransformationEngine;
import org.apache.streampipes.connect.transformer.api.exception.ScriptCompilationException;
import org.apache.streampipes.connect.transformer.api.exception.ScriptExecutionException;
import org.apache.streampipes.connect.transformer.api.utils.TransformationEngineConversionUtils;
+import org.apache.streampipes.model.connect.ScriptMetadata;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
@@ -35,8 +36,12 @@ import java.util.Map;
public class GroovyScriptEngine implements TransformationEngine {
@Override
- public String language() {
- return "groovy";
+ public ScriptMetadata metadata() {
+ return new ScriptMetadata(
+ "groovy",
+ "Groovy",
+ ""
+ );
}
@Override
@@ -61,7 +66,7 @@ public class GroovyScriptEngine implements TransformationEngine {
binding.setVariable("input", input);
Script scriptInstance = InvokerHelper.createScript(scriptClass, binding);
Object result = scriptInstance.run();
- return TransformationEngineConversionUtils.ensureMap(result, language());
+ return TransformationEngineConversionUtils.ensureMap(result, metadata().language());
} catch (Exception e) {
throw new ScriptExecutionException("Groovy template execution failed", e);
}
diff --git a/streampipes-connect-transformer-js/src/main/java/org/apache/streampipes/connect/transformer/js/GraalJsScriptEngine.java b/streampipes-connect-transformer-js/src/main/java/org/apache/streampipes/connect/transformer/js/GraalJsScriptEngine.java
index a677d91782..6cb9b267a7 100644
--- a/streampipes-connect-transformer-js/src/main/java/org/apache/streampipes/connect/transformer/js/GraalJsScriptEngine.java
+++ b/streampipes-connect-transformer-js/src/main/java/org/apache/streampipes/connect/transformer/js/GraalJsScriptEngine.java
@@ -22,6 +22,7 @@ import org.apache.streampipes.connect.transformer.api.ScriptTransformer;
import org.apache.streampipes.connect.transformer.api.TransformationEngine;
import org.apache.streampipes.connect.transformer.api.exception.ScriptCompilationException;
import org.apache.streampipes.connect.transformer.api.exception.ScriptExecutionException;
+import org.apache.streampipes.model.connect.ScriptMetadata;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
@@ -34,8 +35,17 @@ import java.util.Optional;
public class GraalJsScriptEngine implements TransformationEngine {
@Override
- public String language() {
- return "javascript";
+ public ScriptMetadata metadata() {
+ return new ScriptMetadata(
+ "javascript",
+ "JavaScript",
+ """
+ // returns the same event
+ function transform(event) {
+ return event;
+ }
+ """
+ );
}
@Override
@@ -83,7 +93,7 @@ public class GraalJsScriptEngine implements TransformationEngine {
throws ScriptExecutionException {
try {
Value result = transformFunction.execute(input);
- return PolyglotResultConverter.ensureMap(result, language());
+ return PolyglotResultConverter.ensureMap(result, metadata().language());
} catch (PolyglotException e) {
throw new ScriptExecutionException("Graal JS script execution failed", e);
}
diff --git a/ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.scss b/streampipes-model/src/main/java/org/apache/streampipes/model/connect/ScriptMetadata.java
similarity index 76%
copy from ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.scss
copy to streampipes-model/src/main/java/org/apache/streampipes/model/connect/ScriptMetadata.java
index 8a0f012b14..e6b6633de2 100644
--- a/ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.scss
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/connect/ScriptMetadata.java
@@ -16,10 +16,12 @@
*
*/
-.error-color {
- color: #963e3e;
-}
+package org.apache.streampipes.model.connect;
+
+import org.apache.streampipes.model.shared.annotation.TsModel;
-.error-text {
- font-size: 12pt;
+@TsModel
+public record ScriptMetadata(String language,
+ String name,
+ String template) {
}
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/extensions/svcdiscovery/SpServiceRegistration.java b/streampipes-model/src/main/java/org/apache/streampipes/model/extensions/svcdiscovery/SpServiceRegistration.java
index 756750a8e2..e1d6f26905 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/extensions/svcdiscovery/SpServiceRegistration.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/extensions/svcdiscovery/SpServiceRegistration.java
@@ -17,6 +17,7 @@
*/
package org.apache.streampipes.model.extensions.svcdiscovery;
+import org.apache.streampipes.model.connect.ScriptMetadata;
import org.apache.streampipes.model.extensions.ExtensionItemDescription;
import org.apache.streampipes.model.shared.annotation.TsModel;
import org.apache.streampipes.model.shared.api.Storable;
@@ -24,6 +25,7 @@ import org.apache.streampipes.model.shared.api.Storable;
import com.google.gson.annotations.SerializedName;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
@TsModel
@@ -43,6 +45,7 @@ public class SpServiceRegistration implements Storable {
private String healthCheckPath;
private long firstTimeSeenUnhealthy = 0;
private SpServiceStatus status = SpServiceStatus.REGISTERED;
+ private List<ScriptMetadata> supportedScriptLanguages;
private Set<ExtensionItemDescription> providedExtensions;
@@ -55,6 +58,7 @@ public class SpServiceRegistration implements Storable {
String host,
int port,
Set<SpServiceTag> tags,
+ List<ScriptMetadata> supportedScriptLanguages,
String healthCheckPath,
Set<ExtensionItemDescription> providedExtensions) {
this.svcType = svcType;
@@ -63,6 +67,7 @@ public class SpServiceRegistration implements Storable {
this.host = host;
this.port = port;
this.tags = tags;
+ this.supportedScriptLanguages = supportedScriptLanguages;
this.healthCheckPath = healthCheckPath;
this.labels = new HashSet<>();
this.providedExtensions = providedExtensions;
@@ -74,9 +79,20 @@ public class SpServiceRegistration implements Storable {
String host,
Integer port,
Set<SpServiceTag> tags,
+ List<ScriptMetadata> supportedScriptLanguages,
String healthCheckPath,
Set<ExtensionItemDescription> providedExtensions) {
- return new SpServiceRegistration(svcType, svcGroup, svcId, host, port, tags, healthCheckPath, providedExtensions);
+ return new SpServiceRegistration(
+ svcType,
+ svcGroup,
+ svcId,
+ host,
+ port,
+ tags,
+ supportedScriptLanguages,
+ healthCheckPath,
+ providedExtensions
+ );
}
public int getWeight() {
@@ -201,4 +217,9 @@ public class SpServiceRegistration implements Storable {
public void setProvidedExtensions(Set<ExtensionItemDescription> providedExtensions) {
this.providedExtensions = providedExtensions;
}
+
+ public List<ScriptMetadata> getSupportedScriptLanguages() {
+ return supportedScriptLanguages;
+ }
+
}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointGenerator.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointGenerator.java
index 5987e769c3..1a59bc8888 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointGenerator.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointGenerator.java
@@ -35,7 +35,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.stream.Stream;
public class ExtensionsServiceEndpointGenerator implements IExtensionsServiceEndpointGenerator {
@@ -88,7 +87,7 @@ public class ExtensionsServiceEndpointGenerator implements IExtensionsServiceEnd
Set<SpServiceTag> customServiceTags) {
return SpServiceDiscovery.getServiceDiscovery()
.getServiceEndpoints(DefaultSpServiceTypes.EXT, true,
- getDesiredServiceTags(appId, spServiceUrlProvider, customServiceTags));
+ ExtensionsServiceEndpointUtils.getDesiredServiceTags(appId, spServiceUrlProvider, customServiceTags));
}
private String getServiceURL(String appId, SpServiceUrlProvider spServiceUrlProvider,
@@ -106,11 +105,4 @@ public class ExtensionsServiceEndpointGenerator implements IExtensionsServiceEnd
public static boolean filtersSupported(SpServiceRegistration service, String tag) {
return new HashSet<>(service.getTags()).stream().anyMatch(t -> t.asString().equals(tag));
}
-
- private List<String> getDesiredServiceTags(String appId, SpServiceUrlProvider serviceUrlProvider,
- Set<SpServiceTag> customServiceTags) {
- return Stream
- .concat(Stream.of(serviceUrlProvider.getServiceTag(appId)), customServiceTags.stream())
- .map(SpServiceTag::asString).toList();
- }
}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java
index 7c63a315bf..ef4a028ca8 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java
@@ -19,6 +19,7 @@ package org.apache.streampipes.manager.execution.endpoint;
import org.apache.streampipes.model.base.NamedStreamPipesEntity;
import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTag;
import org.apache.streampipes.model.graph.DataProcessorDescription;
import org.apache.streampipes.model.graph.DataProcessorInvocation;
import org.apache.streampipes.model.graph.DataSinkDescription;
@@ -26,7 +27,10 @@ import org.apache.streampipes.model.graph.DataSinkInvocation;
import org.apache.streampipes.storage.management.StorageDispatcher;
import org.apache.streampipes.svcdiscovery.api.model.SpServiceUrlProvider;
+import java.util.List;
import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.stream.Stream;
public class ExtensionsServiceEndpointUtils {
@@ -51,6 +55,13 @@ public class ExtensionsServiceEndpointUtils {
}
}
+ public static List<String> getDesiredServiceTags(String appId, SpServiceUrlProvider serviceUrlProvider,
+ Set<SpServiceTag> customServiceTags) {
+ return Stream
+ .concat(Stream.of(serviceUrlProvider.getServiceTag(appId)), customServiceTags.stream())
+ .map(SpServiceTag::asString).toList();
+ }
+
private static boolean isDataProcessor(NamedStreamPipesEntity entity) {
return entity instanceof DataProcessorInvocation || entity instanceof DataProcessorDescription;
}
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/TransformationScriptLanguageResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/TransformationScriptLanguageResource.java
new file mode 100644
index 0000000000..5000e107cf
--- /dev/null
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/TransformationScriptLanguageResource.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.rest.impl.connect;
+
+import org.apache.streampipes.connect.transformer.api.TransformationEngines;
+import org.apache.streampipes.manager.execution.endpoint.ExtensionsServiceEndpointUtils;
+import org.apache.streampipes.model.connect.ScriptMetadata;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.svcdiscovery.SpServiceDiscovery;
+import org.apache.streampipes.svcdiscovery.api.model.DefaultSpServiceTypes;
+import org.apache.streampipes.svcdiscovery.api.model.SpServiceUrlProvider;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/v2/connect/master/script-languages")
+public class TransformationScriptLanguageResource {
+
+ @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
+ public List<ScriptMetadata> getAll(@RequestBody AdapterDescription adapterDescription) {
+ var languagesSupportedByCore = TransformationEngines.INSTANCE.getAvailableEngineMetadata();
+ var matchingServices = SpServiceDiscovery.getServiceDiscovery()
+ .getService(DefaultSpServiceTypes.EXT, true,
+ ExtensionsServiceEndpointUtils.getDesiredServiceTags(
+ adapterDescription.getAppId(),
+ SpServiceUrlProvider.ADAPTER,
+ adapterDescription.getDeploymentConfiguration()
+ .getDesiredServiceTags())
+ );
+
+ if (!matchingServices.isEmpty()) {
+ return matchingServices.get(0)
+ .getSupportedScriptLanguages()
+ .stream()
+ .filter(metadata -> languagesSupportedByCore
+ .stream()
+ .anyMatch(coreLanguage -> coreLanguage.language().equals(metadata.language()))
+ )
+ .toList();
+ } else {
+ throw new IllegalArgumentException("No supported languages found");
+ }
+ }
+}
diff --git a/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/ISpServiceDiscovery.java b/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/ISpServiceDiscovery.java
index afee5cdb74..646ce8ba62 100644
--- a/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/ISpServiceDiscovery.java
+++ b/streampipes-service-discovery-api/src/main/java/org/apache/streampipes/svcdiscovery/api/ISpServiceDiscovery.java
@@ -46,6 +46,10 @@ public interface ISpServiceDiscovery {
List<SpServiceRegistration> getService(boolean restrictToHealthy);
+ List<SpServiceRegistration> getService(String serviceGroup,
+ boolean restrictToHealthy,
+ List<String> filterByTags);
+
/**
* Get all service
* @return list of services
diff --git a/streampipes-service-discovery/src/main/java/org/apache/streampipes/svcdiscovery/SpServiceDiscoveryCore.java b/streampipes-service-discovery/src/main/java/org/apache/streampipes/svcdiscovery/SpServiceDiscoveryCore.java
index ced6b806cd..26deec5803 100644
--- a/streampipes-service-discovery/src/main/java/org/apache/streampipes/svcdiscovery/SpServiceDiscoveryCore.java
+++ b/streampipes-service-discovery/src/main/java/org/apache/streampipes/svcdiscovery/SpServiceDiscoveryCore.java
@@ -58,7 +58,7 @@ public class SpServiceDiscoveryCore implements ISpServiceDiscovery {
}
@Override
- public List<String> getServiceEndpoints(String serviceGroup,
+ public List<SpServiceRegistration> getService(String serviceGroup,
boolean restrictToHealthy,
List<String> filterByTags) {
List<SpServiceRegistration> activeServices = findServices(0);
@@ -68,10 +68,18 @@ public class SpServiceDiscoveryCore implements ISpServiceDiscovery {
.filter(service -> allFiltersSupported(service, filterByTags))
.filter(service -> !restrictToHealthy
|| service.getStatus() != SpServiceStatus.UNHEALTHY)
- .map(this::makeServiceUrl)
.collect(Collectors.toList());
}
+ @Override
+ public List<String> getServiceEndpoints(String serviceGroup,
+ boolean restrictToHealthy,
+ List<String> filterByTags) {
+ var services = getService(serviceGroup, restrictToHealthy, filterByTags);
+
+ return services.stream().map(this::makeServiceUrl).toList();
+ }
+
@Override
public List<SpServiceRegistration> getService(boolean restrictToHealthy) {
List<SpServiceRegistration> activeServices = findServices(0);
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 c11ff8e60f..aa2481afee 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
@@ -88,13 +88,15 @@ public abstract class StreamPipesExtensionsServiceBase extends StreamPipesServic
serviceDef.setServiceId(serviceId);
DeclarersSingleton.getInstance().populate(networkingConfig.getHost(), networkingConfig.getPort(), serviceDef);
SpRateLimiter.INSTANCE.createRateLimiter();
- startExtensionsService(this.getClass(), serviceDef, networkingConfig);
- ServiceLoadDataReportGenerator.getInstance().initialize();
- registerTransformationEngines(List.of(
+ registerTransformationEngines(List.of(
GroovyScriptEngine::new,
GraalJsScriptEngine::new
));
+
+ startExtensionsService(this.getClass(), serviceDef, networkingConfig);
+ ServiceLoadDataReportGenerator.getInstance().initialize();
+
} catch (UnknownHostException e) {
LOG.error(
"Could not auto-resolve host address - "
@@ -129,6 +131,7 @@ public abstract class StreamPipesExtensionsServiceBase extends StreamPipesServic
networkingConfig.getHost(),
networkingConfig.getPort(),
getServiceTags(extensions),
+ TransformationEngines.INSTANCE.getAvailableEngineMetadata(),
getHealthCheckPath(),
extensions);
diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/connect-script-languages.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/connect-script-languages.service.ts
new file mode 100644
index 0000000000..73cb673591
--- /dev/null
+++ b/ui/projects/streampipes/platform-services/src/lib/apis/connect-script-languages.service.ts
@@ -0,0 +1,29 @@
+import { inject, Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import {
+ AdapterDescription,
+ PlatformServicesCommons,
+ ScriptMetadata,
+} from '@streampipes/platform-services';
+import { Observable } from 'rxjs';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class ConnectScriptLanguagesService {
+ private http = inject(HttpClient);
+ private platformServicesCommons = inject(PlatformServicesCommons);
+
+ getAll(
+ adapterDescription: AdapterDescription,
+ ): Observable<ScriptMetadata[]> {
+ return this.http.post<ScriptMetadata[]>(
+ this.baseUrl,
+ adapterDescription,
+ );
+ }
+
+ get baseUrl(): string {
+ return `${this.platformServicesCommons.apiBasePath}/connect/master/script-languages`;
+ }
+}
diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/connect-script-templates.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/connect-script-templates.service.ts
new file mode 100644
index 0000000000..e69de29bb2
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 1d042c59d3..1972c1d960 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
@@ -16,10 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
+
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
-// Generated using typescript-generator version 3.2.1263 on 2025-12-30 09:52:24.
+// Generated using typescript-generator version 3.2.1263 on 2026-01-02 11:22:00.
export class NamedStreamPipesEntity implements Storable {
'@class':
@@ -151,6 +152,9 @@ export class AdapterDescription extends VersionedNamedStreamPipesEntity {
}
}
+/**
+ * @deprecated since 0.99.0, for removal
+ */
export class TransformationRuleDescription {
'@class':
| 'org.apache.streampipes.model.connect.rules.value.ValueTransformationRuleDescription'
@@ -224,6 +228,9 @@ export class TransformationRuleDescription {
}
}
+/**
+ * @deprecated since 0.99.0, for removal
+ */
export class ValueTransformationRuleDescription extends TransformationRuleDescription {
'@class':
| 'org.apache.streampipes.model.connect.rules.value.ValueTransformationRuleDescription'
@@ -295,6 +302,9 @@ export class AddTimestampRuleDescription extends ValueTransformationRuleDescript
}
}
+/**
+ * @deprecated since 0.99.0, for removal
+ */
export class AddValueTransformationRuleDescription extends ValueTransformationRuleDescription {
'@class': 'org.apache.streampipes.model.connect.rules.value.AddValueTransformationRuleDescription';
'datatype': string;
@@ -757,6 +767,9 @@ export class Certificate implements Storable {
}
}
+/**
+ * @deprecated since 0.99.0, for removal
+ */
export class ChangeDatatypeTransformationRuleDescription extends ValueTransformationRuleDescription {
'@class': 'org.apache.streampipes.model.connect.rules.value.ChangeDatatypeTransformationRuleDescription';
'originalDatatypeXsd': string;
@@ -1800,6 +1813,9 @@ export class EventPropertyPrimitive extends EventProperty {
}
}
+/**
+ * @deprecated since 0.99.0, for removal
+ */
export class StreamTransformationRuleDescription extends TransformationRuleDescription {
'@class':
| 'org.apache.streampipes.model.connect.rules.stream.StreamTransformationRuleDescription'
@@ -1835,6 +1851,9 @@ export class StreamTransformationRuleDescription extends TransformationRuleDescr
}
}
+/**
+ * @deprecated since 0.99.0, for removal
+ */
export class EventRateTransformationRuleDescription extends StreamTransformationRuleDescription {
'@class': 'org.apache.streampipes.model.connect.rules.stream.EventRateTransformationRuleDescription';
'aggregationTimeWindow': number;
@@ -3424,6 +3443,9 @@ export class RemoveDuplicateRule {
}
}
+/**
+ * @deprecated since 0.99.0, for removal
+ */
export class RemoveDuplicatesTransformationRuleDescription extends StreamTransformationRuleDescription {
'@class': 'org.apache.streampipes.model.connect.rules.stream.RemoveDuplicatesTransformationRuleDescription';
'filterTimeWindow': string;
@@ -3744,6 +3766,26 @@ export class SampleData {
}
}
+export class ScriptMetadata {
+ language: string;
+ name: string;
+ template: string;
+
+ static fromData(
+ data: ScriptMetadata,
+ target?: ScriptMetadata,
+ ): ScriptMetadata {
+ if (!data) {
+ return data;
+ }
+ const instance = target || new ScriptMetadata();
+ instance.language = data.language;
+ instance.name = data.name;
+ instance.template = data.template;
+ return instance;
+ }
+}
+
export class SecretStaticProperty extends StaticProperty {
'@class': 'org.apache.streampipes.model.staticproperty.SecretStaticProperty';
'encrypted': boolean;
@@ -4376,6 +4418,9 @@ export class TreeInputNode {
}
}
+/**
+ * @deprecated since 0.99.0, for removal
+ */
export class UnitTransformRuleDescription extends ValueTransformationRuleDescription {
'@class': 'org.apache.streampipes.model.connect.rules.value.UnitTransformRuleDescription';
'fromUnitRessourceURL': string;
diff --git a/ui/projects/streampipes/platform-services/src/public-api.ts b/ui/projects/streampipes/platform-services/src/public-api.ts
index 2eed5bd462..0897190af8 100644
--- a/ui/projects/streampipes/platform-services/src/public-api.ts
+++ b/ui/projects/streampipes/platform-services/src/public-api.ts
@@ -29,6 +29,7 @@ export * from './lib/apis/asset-management.service';
export * from './lib/apis/compact-pipeline.service';
export * from './lib/apis/certificate.service';
export * from './lib/apis/chart.service';
+export * from './lib/apis/connect-script-languages.service';
export * from './lib/apis/dashboard.service';
export * from './lib/apis/dashboard-kiosk.service';
export * from './lib/apis/datalake-rest.service';
diff --git a/ui/src/app/connect/components/adapter-configuration/adapter-configuration-state-service/adapter-configuration-state.service.ts b/ui/src/app/connect/components/adapter-configuration/adapter-configuration-state-service/adapter-configuration-state.service.ts
index 1b1cb0ad17..11c92c62e1 100644
--- a/ui/src/app/connect/components/adapter-configuration/adapter-configuration-state-service/adapter-configuration-state.service.ts
+++ b/ui/src/app/connect/components/adapter-configuration/adapter-configuration-state-service/adapter-configuration-state.service.ts
@@ -110,7 +110,11 @@ export class AdapterConfigurationStateService {
// 3. Automatically run the script after getting the sample
const currentScript =
updatedAdapter.transformationConfig.script;
- this.runScript(updatedAdapter, currentScript);
+ this.runScript(
+ updatedAdapter,
+ currentScript,
+ updatedAdapter.transformationConfig.language,
+ );
},
error: (error: HttpErrorResponse) => {
// Update state with error AND metadata (error/idle)
@@ -122,7 +126,11 @@ export class AdapterConfigurationStateService {
});
}
- public runScript(adapter: AdapterDescription, script: string): void {
+ public runScript(
+ adapter: AdapterDescription,
+ script: string,
+ language: string,
+ ): void {
// 1. Prepare state for loading
this.updateState({
isRunningScript: true,
@@ -132,7 +140,7 @@ export class AdapterConfigurationStateService {
// 2. Update the local adapter object with the latest script from the UI
const updatedAdapter = { ...adapter };
updatedAdapter.transformationConfig.script = script;
- updatedAdapter.transformationConfig.language = 'javascript';
+ updatedAdapter.transformationConfig.language = language;
// 3. Execute the API call
this.restService.sampleTransform(updatedAdapter).subscribe({
diff --git a/ui/src/app/connect/components/adapter-configuration/configure-fields/configure-fields-preview/configure-fields-preview.component.scss b/ui/src/app/connect/components/adapter-configuration/configure-fields/configure-fields-preview/configure-fields-preview.component.scss
index b4c4b08df6..c4108bbd62 100644
--- a/ui/src/app/connect/components/adapter-configuration/configure-fields/configure-fields-preview/configure-fields-preview.component.scss
+++ b/ui/src/app/connect/components/adapter-configuration/configure-fields/configure-fields-preview/configure-fields-preview.component.scss
@@ -18,11 +18,11 @@
.preview-text {
margin: var(--space-2xs);
- background-color: var(--color-code-bg);
+ background-color: var(--color-bg-0);
font:
var(--font-size-xs) Inconsolata,
monospace;
- color: white;
+ color: var(--color-default-text);
padding: 10px;
max-width: 100%;
max-height: 300px;
diff --git a/ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.html b/ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.html
index 70f5c17c8c..3f1eafffbd 100644
--- a/ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.html
+++ b/ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.html
@@ -18,14 +18,17 @@
<div fxLayout="column" fxFlex="100">
<div fxLayout="row" fxLayoutAlign="center center" fxFlex="100">
- <div fxLayoutAlign="start center" class="error-text">
+ <div fxLayoutAlign="start center" class="error-text mt-10">
{{
'There was an error while guessing the schema of your configured data stream'
| translate
}}:
</div>
</div>
- <div fxLayout="row" fxLayoutAlign="center center" class="mt-10">
- <sp-exception-message [message]="errorMessage"></sp-exception-message>
+ <div fxLayout="row" fxLayoutAlign="center center" class="mt-10 w-100">
+ <sp-exception-message
+ [message]="errorMessage"
+ class="w-100"
+ ></sp-exception-message>
</div>
</div>
diff --git a/ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.scss b/ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.scss
index 8a0f012b14..ac0cecd92a 100644
--- a/ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.scss
+++ b/ui/src/app/connect/components/adapter-configuration/configure-fields/error-message/configure-fields-error-message.component.scss
@@ -21,5 +21,5 @@
}
.error-text {
- font-size: 12pt;
+ font-size: var(--font-size-md);
}
diff --git a/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.html b/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.html
index 564eb2780a..c7d1f9feb3 100644
--- a/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.html
+++ b/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.html
@@ -18,20 +18,52 @@
<div fxFlex="100" fxLayout="column">
<div fxFlex="100" fxLayout="column">
<sp-basic-inner-panel
- innerPadding="0"
- [panelTitle]="'Basic Settings' | translate"
+ [panelTitle]="'Transformation' | translate"
outerMargin="20px 0px"
>
- <div header fxLayoutAlign="end center" fxFlex="100">
+ <div
+ header
+ fxLayoutAlign="end center"
+ fxFlex="100"
+ fxLayoutGap="10px"
+ >
+ @if (selectedScriptMetadata() !== undefined) {
+ <button
+ mat-button
+ [matMenuTriggerFor]="langMenu"
+ aria-label="Select template language"
+ >
+ {{ selectedScriptMetadata().name | titlecase }}
+ <mat-icon>arrow_drop_down</mat-icon>
+ </button>
+
+ <mat-menu #langMenu="matMenu">
+ @for (
+ script of availableScripts;
+ track script.language
+ ) {
+ <button
+ mat-menu-item
+ (click)="onLanguageChange(script)"
+ >
+ <span>{{ script.name | titlecase }}</span>
+ @if (
+ script.language ===
+ selectedScriptMetadata().language
+ ) {
+ <mat-icon class="ms-auto">check</mat-icon>
+ }
+ </button>
+ }
+ </mat-menu>
+ }
<button
- color="accent"
mat-button
- matTooltip="Run script"
- data-cy="configure-schema-run-script-button"
- (click)="runScript()"
+ data-cy="reset-script"
+ (click)="resetScript()"
>
- <mat-icon>play_circle_filled</mat-icon>
- <span>Run script</span>
+ <mat-icon>settings_backup_restore</mat-icon>
+ <span>Reset script</span>
</button>
</div>
<div class="code-editor-outer">
@@ -43,6 +75,28 @@
data-cy="configure-schema-script-editor"
></ngx-codemirror>
</div>
+ <div fxLayoutGap="10px">
+ <button
+ class="mt-sm"
+ mat-flat-button
+ matTooltip="Run script"
+ data-cy="configure-schema-run-script-button"
+ (click)="runScript()"
+ >
+ <mat-icon>play_circle_filled</mat-icon>
+ <span>Run script</span>
+ </button>
+ <button
+ class="mat-basic mt-sm"
+ mat-flat-button
+ matTooltip="Run script"
+ data-cy="add-script-template-button"
+ (click)="runScript()"
+ >
+ <mat-icon>play_circle_filled</mat-icon>
+ <span>Create script template</span>
+ </button>
+ </div>
</sp-basic-inner-panel>
</div>
@@ -56,12 +110,11 @@
<div header fxLayoutAlign="end center" fxFlex="100">
<button
data-cy="connect-get-new-sample-button"
- color="accent"
mat-button
- matTooltip="Get New Sample"
(click)="getSampleEvent()"
>
<mat-icon>refresh</mat-icon>
+ <span>{{ 'Get new sample' | translate }}</span>
</button>
</div>
@if (isSampleLoading()) {
diff --git a/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.scss b/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.scss
index b6331428c8..5010c7939e 100644
--- a/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.scss
+++ b/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.scss
@@ -30,10 +30,10 @@
.preview-text {
margin: var(--space-2xs);
- background-color: var(--color-code-bg);
+ background-color: var(--color-bg-0);
font:
var(--font-size-xs) Inconsolata,
monospace;
- color: white;
+ color: var(--color-default-text);
padding: 10px;
}
diff --git a/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.ts b/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.ts
index 794225d259..dd8a7ae5de 100644
--- a/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.ts
+++ b/ui/src/app/connect/components/adapter-configuration/configure-schema/configure-schema.component.ts
@@ -27,7 +27,11 @@ import {
signal,
} from '@angular/core';
import { MatStepper } from '@angular/material/stepper';
-import { AdapterDescription } from '@streampipes/platform-services';
+import {
+ AdapterDescription,
+ ConnectScriptLanguagesService,
+ ScriptMetadata,
+} from '@streampipes/platform-services';
import { AdapterConfigurationStateService } from '../adapter-configuration-state-service/adapter-configuration-state.service';
@Component({
@@ -38,6 +42,7 @@ import { AdapterConfigurationStateService } from '../adapter-configuration-state
})
export class ConfigureSchemaComponent implements OnInit {
private stateService = inject(AdapterConfigurationStateService);
+ private scriptLanguagesService = inject(ConnectScriptLanguagesService);
@Input()
adapterDescription: AdapterDescription;
@@ -51,6 +56,8 @@ export class ConfigureSchemaComponent implements OnInit {
@Output()
nextEmitter: EventEmitter<MatStepper> = new EventEmitter();
+ availableScripts: ScriptMetadata[] = [];
+
isSampleLoading = computed(() => this.stateService.state().isGettingSample);
sampleErrorMessage = computed(() => this.stateService.state().sampleError);
@@ -70,10 +77,9 @@ export class ConfigureSchemaComponent implements OnInit {
?.outputs?.[0] || {},
);
- script = signal(`// returns the same event
-function transform(event) {
- return event;
-}`);
+ script = signal(undefined);
+ initialScript = signal<{ meta: ScriptMetadata; script: string }>(undefined);
+ selectedScriptMetadata = signal(undefined);
editorOptions = {
mode: 'javascript',
@@ -94,25 +100,64 @@ function transform(event) {
}
private initializeScriptVariable(): void {
- const currentScript =
- this.adapterDescription.transformationConfig.script;
- if (currentScript) {
- this.script.set(currentScript);
- } else {
- this.adapterDescription.transformationConfig.script = this.script();
- }
+ this.scriptLanguagesService
+ .getAll(this.adapterDescription)
+ .subscribe(res => {
+ this.availableScripts = res;
+ const currentScript =
+ this.adapterDescription.transformationConfig.script;
+ let meta: ScriptMetadata;
+ if (currentScript) {
+ this.script.set(currentScript);
+ meta = this.availableScripts.find(
+ s =>
+ s.language ===
+ this.adapterDescription.transformationConfig
+ .language,
+ );
+ this.initialScript.set({ meta, script: currentScript });
+ } else {
+ meta = this.availableScripts.find(
+ s => s.language === 'javascript',
+ );
+ this.adapterDescription.transformationConfig.script =
+ meta.template;
+ this.adapterDescription.transformationConfig.language =
+ meta.language;
+ this.script.set(meta.template);
+ }
+ this.selectedScriptMetadata.set(meta);
+ });
}
onCodeChange(newCode: string) {
this.script.set(newCode);
}
+ onLanguageChange(newLanguage: ScriptMetadata) {
+ this.selectedScriptMetadata.set(newLanguage);
+ this.script.set(newLanguage.template);
+ }
+
+ resetScript(): void {
+ if (this.initialScript() !== undefined) {
+ this.script.set(this.initialScript().script);
+ this.selectedScriptMetadata.set(this.initialScript().meta);
+ } else {
+ this.script.set(this.selectedScriptMetadata().template);
+ }
+ }
+
getSampleEvent(): void {
this.stateService.getSampleEvent(this.adapterDescription);
}
runScript(): void {
- this.stateService.runScript(this.adapterDescription, this.script());
+ this.stateService.runScript(
+ this.adapterDescription,
+ this.script(),
+ this.selectedScriptMetadata().language,
+ );
}
public cancel() {