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:49 UTC
(streampipes) 01/01: Add multi-language support to connect transformation
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() {