This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch
3208-add-data-processor-to-perform-math-expressions
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to
refs/heads/3208-add-data-processor-to-perform-math-expressions by this push:
new ed3e76422e feat(#3208): Add math expression evaluator, UI fixes
ed3e76422e is described below
commit ed3e76422e617a7a924fb758514076f8e6ef7460
Author: Dominik Riemer <[email protected]>
AuthorDate: Mon Sep 2 22:51:31 2024 +0200
feat(#3208): Add math expression evaluator, UI fixes
---
.../streampipes-processors-enricher-jvm/pom.xml | 6 +
.../jvm/EnricherExtensionModuleExport.java | 4 +-
.../processor/expression/JexlContextGenerator.java | 48 ++++++++
.../jvm/processor/expression/JexlDescription.java | 29 +++++
.../processor/expression/JexlEngineProvider.java | 38 ++++++
.../jvm/processor/expression/JexlEvaluator.java | 42 +++++++
.../expression/MathExpressionFieldExtractor.java | 72 +++++++++++
.../expression/MathExpressionProcessor.java | 131 +++++++++++++++++++++
.../documentation.md | 54 ++++++---
.../icon.png | Bin 0 -> 2484 bytes
.../strings.en | 29 +++++
.../processor/expression/JexlEvaluatorTest.java | 95 +++++++++++++++
.../model/staticproperty/StaticPropertyGroup.java | 2 +-
.../apache/streampipes/sdk/StaticProperties.java | 29 +----
.../pipeline-started-status.component.html | 1 +
.../static-property-util.service.ts | 7 ++
.../save-pipeline/save-pipeline.component.ts | 2 +-
...pipeline-details-expansion-panel.component.html | 2 +-
.../pipeline-element-details-row.component.html | 2 +-
.../pipeline-logs-dialog.component.html | 2 +-
20 files changed, 549 insertions(+), 46 deletions(-)
diff --git a/streampipes-extensions/streampipes-processors-enricher-jvm/pom.xml
b/streampipes-extensions/streampipes-processors-enricher-jvm/pom.xml
index bc17948896..c9177f7fca 100644
--- a/streampipes-extensions/streampipes-processors-enricher-jvm/pom.xml
+++ b/streampipes-extensions/streampipes-processors-enricher-jvm/pom.xml
@@ -46,6 +46,12 @@
<artifactId>js-scriptengine</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-jexl3</artifactId>
+ <version>3.4.0</version>
+ </dependency>
+
<!-- Test dependencies -->
<dependency>
<groupId>org.apache.streampipes</groupId>
diff --git
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/EnricherExtensionModuleExport.java
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/EnricherExtensionModuleExport.java
index f26795f7ac..a762e777ca 100644
---
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/EnricherExtensionModuleExport.java
+++
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/EnricherExtensionModuleExport.java
@@ -22,6 +22,7 @@ import
org.apache.streampipes.extensions.api.connect.StreamPipesAdapter;
import org.apache.streampipes.extensions.api.declarer.IExtensionModuleExport;
import org.apache.streampipes.extensions.api.migration.IModelMigrator;
import org.apache.streampipes.extensions.api.pe.IStreamPipesPipelineElement;
+import
org.apache.streampipes.processors.enricher.jvm.processor.expression.MathExpressionProcessor;
import
org.apache.streampipes.processors.enricher.jvm.processor.jseval.JSEvalProcessor;
import
org.apache.streampipes.processors.enricher.jvm.processor.limitsalert.SensorLimitAlertProcessor;
import
org.apache.streampipes.processors.enricher.jvm.processor.limitsenrichment.QualityControlLimitsEnrichmentProcessor;
@@ -48,7 +49,8 @@ public class EnricherExtensionModuleExport implements
IExtensionModuleExport {
new MathOpProcessor(),
new StaticMathOpProcessor(),
new TrigonometryProcessor(),
- new ValueChangeProcessor()
+ new ValueChangeProcessor(),
+ new MathExpressionProcessor()
);
}
diff --git
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlContextGenerator.java
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlContextGenerator.java
new file mode 100644
index 0000000000..0994a25976
--- /dev/null
+++
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlContextGenerator.java
@@ -0,0 +1,48 @@
+/*
+ * 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.processors.enricher.jvm.processor.expression;
+
+import org.apache.streampipes.model.runtime.Event;
+
+import org.apache.commons.jexl3.MapContext;
+
+public class JexlContextGenerator {
+
+ private final MathExpressionFieldExtractor extractor;
+ private final MapContext mapContext;
+
+ public JexlContextGenerator(MathExpressionFieldExtractor extractor) {
+ this.extractor = extractor;
+ this.mapContext = makeInitialContext();
+ }
+
+ private MapContext makeInitialContext() {
+ var ctx = new MapContext();
+ ctx.set("Math", Math.class);
+ extractor.getInputProperties().forEach(ep ->
+ ctx.set(ep.getRuntimeName(), 0)
+ );
+ return ctx;
+ }
+
+ public MapContext makeContext(Event event) {
+ event.getRaw().forEach(mapContext::set);
+ return mapContext;
+ }
+}
diff --git
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlDescription.java
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlDescription.java
new file mode 100644
index 0000000000..df7c065612
--- /dev/null
+++
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlDescription.java
@@ -0,0 +1,29 @@
+/*
+ * 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.processors.enricher.jvm.processor.expression;
+
+import org.apache.streampipes.model.schema.EventPropertyPrimitive;
+
+public record JexlDescription(EventPropertyPrimitive ep,
+ String script) {
+
+ public String getFieldName() {
+ return ep.getRuntimeName();
+ }
+}
diff --git
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlEngineProvider.java
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlEngineProvider.java
new file mode 100644
index 0000000000..b360d613bb
--- /dev/null
+++
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlEngineProvider.java
@@ -0,0 +1,38 @@
+/*
+ * 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.processors.enricher.jvm.processor.expression;
+
+import org.apache.commons.jexl3.JexlBuilder;
+import org.apache.commons.jexl3.JexlEngine;
+import org.apache.commons.jexl3.JexlFeatures;
+import org.apache.commons.jexl3.introspection.JexlPermissions;
+
+public class JexlEngineProvider {
+
+ public JexlEngine getEngine() {
+ var features = new JexlFeatures()
+ .loops(false)
+ .sideEffect(false)
+ .sideEffectGlobal(false);
+
+ var permissions = new
JexlPermissions.ClassPermissions(java.lang.Math.class);
+
+ return new
JexlBuilder().features(features).permissions(permissions).create();
+ }
+}
diff --git
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlEvaluator.java
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlEvaluator.java
new file mode 100644
index 0000000000..5e232c828e
--- /dev/null
+++
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * 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.processors.enricher.jvm.processor.expression;
+
+import org.apache.streampipes.model.runtime.Event;
+
+import org.apache.commons.jexl3.JexlEngine;
+import org.apache.commons.jexl3.JexlScript;
+import org.apache.commons.jexl3.MapContext;
+
+public class JexlEvaluator {
+
+ private final JexlDescription jexlDescription;
+ private final JexlScript script;
+
+ public JexlEvaluator(JexlDescription jexlDescription,
+ JexlEngine engine) {
+ this.jexlDescription = jexlDescription;
+ this.script = engine.createScript(jexlDescription.script());
+ }
+
+ public void evaluate(MapContext context,
+ Event event) {
+ event.addField(jexlDescription.getFieldName(), script.execute(context));
+ }
+}
diff --git
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/MathExpressionFieldExtractor.java
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/MathExpressionFieldExtractor.java
new file mode 100644
index 0000000000..ed2df9b041
--- /dev/null
+++
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/MathExpressionFieldExtractor.java
@@ -0,0 +1,72 @@
+/*
+ * 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.processors.enricher.jvm.processor.expression;
+
+import
org.apache.streampipes.extensions.api.extractor.IDataProcessorParameterExtractor;
+import org.apache.streampipes.model.graph.DataProcessorInvocation;
+import org.apache.streampipes.model.schema.EventProperty;
+import org.apache.streampipes.model.staticproperty.CodeInputStaticProperty;
+import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty;
+import
org.apache.streampipes.sdk.extractor.ProcessingElementParameterExtractor;
+import org.apache.streampipes.sdk.helpers.EpProperties;
+import org.apache.streampipes.sdk.helpers.Labels;
+import org.apache.streampipes.vocabulary.SO;
+
+import java.util.List;
+
+import static
org.apache.streampipes.processors.enricher.jvm.processor.expression.MathExpressionProcessor.ENRICHED_FIELDS;
+import static
org.apache.streampipes.processors.enricher.jvm.processor.expression.MathExpressionProcessor.EXPRESSION;
+import static
org.apache.streampipes.processors.enricher.jvm.processor.expression.MathExpressionProcessor.FIELD_NAME;
+
+public class MathExpressionFieldExtractor {
+
+ private final DataProcessorInvocation processingElement;
+ private final IDataProcessorParameterExtractor extractor;
+
+ public MathExpressionFieldExtractor(DataProcessorInvocation
processingElement) {
+ this.processingElement = processingElement;
+ this.extractor =
ProcessingElementParameterExtractor.from(processingElement);
+ }
+
+ public MathExpressionFieldExtractor(DataProcessorInvocation
processingElement,
+ IDataProcessorParameterExtractor
extractor) {
+ this.processingElement = processingElement;
+ this.extractor = extractor;
+ }
+
+ public List<JexlDescription> getAdditionalFields() {
+ return extractor
+ .collectionMembersAsGroup(ENRICHED_FIELDS)
+ .stream().map(group -> {
+ var runtimeName = extractor.extractGroupMember(FIELD_NAME,
group).as(FreeTextStaticProperty.class).getValue();
+ var expression = extractor.extractGroupMember(EXPRESSION,
group).as(CodeInputStaticProperty.class).getValue();
+ return new JexlDescription(EpProperties.doubleEp(Labels.empty(),
runtimeName, SO.NUMBER), expression);
+ }).toList();
+ }
+
+ public List<EventProperty> getInputProperties() {
+ var inputStreams = processingElement.getInputStreams();
+ if (!inputStreams.isEmpty()) {
+ return inputStreams.get(0).getEventSchema().getEventProperties();
+ } else {
+ return List.of();
+ }
+ }
+}
+
diff --git
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/MathExpressionProcessor.java
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/MathExpressionProcessor.java
new file mode 100644
index 0000000000..bff0417c75
--- /dev/null
+++
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/MathExpressionProcessor.java
@@ -0,0 +1,131 @@
+/*
+ * 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.processors.enricher.jvm.processor.expression;
+
+import org.apache.streampipes.extensions.api.pe.IStreamPipesDataProcessor;
+import
org.apache.streampipes.extensions.api.pe.config.IDataProcessorConfiguration;
+import
org.apache.streampipes.extensions.api.pe.context.EventProcessorRuntimeContext;
+import org.apache.streampipes.extensions.api.pe.param.IDataProcessorParameters;
+import org.apache.streampipes.extensions.api.pe.routing.SpOutputCollector;
+import
org.apache.streampipes.extensions.api.runtime.ResolvesContainerProvidedOutputStrategy;
+import org.apache.streampipes.model.DataProcessorType;
+import org.apache.streampipes.model.extensions.ExtensionAssetType;
+import org.apache.streampipes.model.graph.DataProcessorInvocation;
+import org.apache.streampipes.model.runtime.Event;
+import org.apache.streampipes.model.schema.EventSchema;
+import org.apache.streampipes.model.staticproperty.CollectionStaticProperty;
+import org.apache.streampipes.sdk.StaticProperties;
+import org.apache.streampipes.sdk.builder.ProcessingElementBuilder;
+import org.apache.streampipes.sdk.builder.StreamRequirementsBuilder;
+import org.apache.streampipes.sdk.builder.processor.DataProcessorConfiguration;
+import
org.apache.streampipes.sdk.extractor.ProcessingElementParameterExtractor;
+import org.apache.streampipes.sdk.helpers.CodeLanguage;
+import org.apache.streampipes.sdk.helpers.EpRequirements;
+import org.apache.streampipes.sdk.helpers.Labels;
+import org.apache.streampipes.sdk.helpers.Locales;
+import org.apache.streampipes.sdk.helpers.OutputStrategies;
+import org.apache.streampipes.sdk.utils.Datatypes;
+
+import org.apache.commons.jexl3.JexlException;
+
+import java.util.List;
+
+public class MathExpressionProcessor implements
+ IStreamPipesDataProcessor,
+ ResolvesContainerProvidedOutputStrategy<DataProcessorInvocation,
ProcessingElementParameterExtractor> {
+
+ private static final String ID =
"org.apache.streampipes.processors.enricher.jvm.processor.expression";
+ static final String ENRICHED_FIELDS = "enriched-fields";
+ static final String FIELD_NAME = "field-name";
+ static final String EXPRESSION = "expression";
+
+ private List<JexlEvaluator> jexlEvaluators;
+ private JexlContextGenerator jexlContextGenerator;
+ private EventProcessorRuntimeContext context;
+
+ @Override
+ public IDataProcessorConfiguration declareConfig() {
+ return DataProcessorConfiguration.create(
+ MathExpressionProcessor::new,
+ ProcessingElementBuilder.create(ID, 0)
+ .category(DataProcessorType.ENRICH)
+ .withLocales(Locales.EN)
+ .withAssets(ExtensionAssetType.DOCUMENTATION,
ExtensionAssetType.ICON)
+ .requiredStream(StreamRequirementsBuilder.create()
+ .requiredProperty(EpRequirements.numberReq())
+ .build())
+ .requiredStaticProperty(makeCollection())
+ .outputStrategy(OutputStrategies.customTransformation())
+ .build()
+ );
+ }
+
+ private CollectionStaticProperty makeCollection() {
+ return StaticProperties.collection(
+ Labels.withId(ENRICHED_FIELDS),
+ false,
+ StaticProperties.freeTextProperty(Labels.withId(FIELD_NAME),
Datatypes.String),
+ StaticProperties.codeStaticProperty(Labels.withId(EXPRESSION),
CodeLanguage.None, getJexlComment()));
+ }
+
+ private String getJexlComment() {
+ return "## Provide JEXL syntax here";
+ }
+
+ @Override
+ public void onPipelineStarted(IDataProcessorParameters params,
+ SpOutputCollector collector,
+ EventProcessorRuntimeContext runtimeContext) {
+ var extractor = new MathExpressionFieldExtractor(params.getModel());
+ var engine = new JexlEngineProvider().getEngine();
+ var scripts = extractor.getAdditionalFields();
+ jexlEvaluators = scripts.stream().map(script -> new JexlEvaluator(script,
engine)).toList();
+ jexlContextGenerator = new JexlContextGenerator(extractor);
+ context = runtimeContext;
+ }
+
+ @Override
+ public void onEvent(Event event, SpOutputCollector collector) {
+ var ctx = jexlContextGenerator.makeContext(event);
+ jexlEvaluators.forEach(evaluator -> {
+ try {
+ evaluator.evaluate(ctx, event);
+ } catch (JexlException e) {
+ context.getLogger().error(e);
+ }
+ });
+ collector.collect(event);
+ }
+
+ @Override
+ public void onPipelineStopped() {
+
+ }
+
+
+ @Override
+ public EventSchema resolveOutputStrategy(DataProcessorInvocation
processingElement,
+ ProcessingElementParameterExtractor
parameterExtractor) {
+ var fieldExtractor = new MathExpressionFieldExtractor(processingElement,
parameterExtractor);
+ var existingFields = fieldExtractor.getInputProperties();
+ var additionalFields = fieldExtractor.getAdditionalFields();
+
existingFields.addAll(additionalFields.stream().map(JexlDescription::ep).toList());
+ return new
EventSchema(processingElement.getInputStreams().get(0).getEventSchema());
+ }
+}
diff --git
a/ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-element-details-row/pipeline-element-details-row.component.html
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/resources/org.apache.streampipes.processors.enricher.jvm.processor.expression/documentation.md
similarity index 51%
copy from
ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-element-details-row/pipeline-element-details-row.component.html
copy to
streampipes-extensions/streampipes-processors-enricher-jvm/src/main/resources/org.apache.streampipes.processors.enricher.jvm.processor.expression/documentation.md
index 284166d2dd..3545bb1c06 100644
---
a/ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-element-details-row/pipeline-element-details-row.component.html
+++
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/resources/org.apache.streampipes.processors.enricher.jvm.processor.expression/documentation.md
@@ -16,20 +16,40 @@
~
-->
-<div fxLayout="row" fxFlex="100">
- <div fxFlex>
- <sp-pipeline-elements-row [pipelineElement]="pipelineElement">
- </sp-pipeline-elements-row>
- </div>
- <div fxLayoutAlign="end center">
- <button
- (click)="openLogsDialog()"
- mat-icon-button
- color="accent"
- matTooltip="Logs"
- [disabled]="!pipelineRunning"
- >
- <mat-icon>topic</mat-icon>
- </button>
- </div>
-</div>
+## Math Expression Evaluator
+
+<p align="center">
+ <img src="icon.png" width="150px;" class="pe-image-documentation"/>
+</p>
+
+***
+
+## Description
+A pipeline element that evaluates Math expressions using the Apache Commons
JEXL library.
+
+***
+
+## Required input
+This processor works with any input stream that contains numerical values.
+
+***
+
+## Configuration
+A math expression can be defined using the JEXL syntax (see
https://commons.apache.org/proper/commons-jexl/index.html).
+
+Example:
+
+```
+flow_rate*2
+```
+
+It is also possible to use methods from `java.lang.Math`:
+
+```
+Math.pow(flow_rate^2)
+```
+
+All fields from th einput stream are available as variables.
+
+## Output
+For each expression, an additional field is created in the output stream.
Field names are user-defined.
diff --git
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/resources/org.apache.streampipes.processors.enricher.jvm.processor.expression/icon.png
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/resources/org.apache.streampipes.processors.enricher.jvm.processor.expression/icon.png
new file mode 100644
index 0000000000..e86193ad0b
Binary files /dev/null and
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/resources/org.apache.streampipes.processors.enricher.jvm.processor.expression/icon.png
differ
diff --git
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/resources/org.apache.streampipes.processors.enricher.jvm.processor.expression/strings.en
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/resources/org.apache.streampipes.processors.enricher.jvm.processor.expression/strings.en
new file mode 100644
index 0000000000..a27ab661cc
--- /dev/null
+++
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/main/resources/org.apache.streampipes.processors.enricher.jvm.processor.expression/strings.en
@@ -0,0 +1,29 @@
+#
+# 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.
+#
+
+
+org.apache.streampipes.processors.enricher.jvm.processor.expression.title=Math
Expression
+org.apache.streampipes.processors.enricher.jvm.processor.expression.description=Evaluates
math expressions
+
+enriched-fields.title=Additional Fields
+enriched-fields.description=Additional fields
+
+field-name.title=Field name
+field-name.description=Field description
+
+expression.title=Expression
+expression.description=The Math expression to apply
diff --git
a/streampipes-extensions/streampipes-processors-enricher-jvm/src/test/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlEvaluatorTest.java
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/test/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlEvaluatorTest.java
new file mode 100644
index 0000000000..09455e153c
--- /dev/null
+++
b/streampipes-extensions/streampipes-processors-enricher-jvm/src/test/java/org/apache/streampipes/processors/enricher/jvm/processor/expression/JexlEvaluatorTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.processors.enricher.jvm.processor.expression;
+
+import org.apache.streampipes.model.runtime.Event;
+import org.apache.streampipes.model.schema.EventPropertyPrimitive;
+import org.apache.streampipes.sdk.helpers.EpProperties;
+import org.apache.streampipes.sdk.helpers.Labels;
+import org.apache.streampipes.vocabulary.SO;
+
+import org.apache.commons.jexl3.JexlEngine;
+import org.apache.commons.jexl3.MapContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class JexlEvaluatorTest {
+
+ private JexlEngine engine;
+
+ @BeforeEach
+ void setup() {
+ this.engine = new JexlEngineProvider().getEngine();
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideTestArguments")
+ void testEvaluate(JexlDescription jexlDescription,
+ Object expectedResult,
+ MapContext context) {
+ var evaluator = new JexlEvaluator(jexlDescription, engine);
+ var event = makeBaseEvent();
+ evaluator.evaluate(context, event);
+
+ assertEquals(4, event.getRaw().size());
+ assertEquals(expectedResult, event.getRaw().get("result1"));
+
+ }
+
+ static Stream<Arguments> provideTestArguments() {
+ MapContext context = new MapContext();
+ context.set("a", 10);
+ context.set("b", 20.0);
+ context.set("c", 2);
+ context.set("Math", Math.class);
+
+ return Stream.of(
+ Arguments.of(number("result1", "a + 5"), 15, context),
+ Arguments.of(number("result1", "b + 5"), 25.0, context),
+ Arguments.of(number("result1", "a + b * 5"), 110.0, context),
+ Arguments.of(number("result1", "a + b * 5"), 110.0, context),
+ Arguments.of(number("result1", "Math.pow(a, c)"), 100.0, context)
+ );
+ }
+
+ private static JexlDescription number(String runtimeName,
+ String script) {
+ return new JexlDescription(numberEp(runtimeName), script);
+ }
+
+ private static EventPropertyPrimitive numberEp(String runtimeName) {
+ return EpProperties.doubleEp(Labels.empty(), runtimeName, SO.NUMBER);
+ }
+
+ private static Event makeBaseEvent() {
+ var event = new Event();
+ event.addField("a", 10);
+ event.addField("b", 20.0);
+ event.addField("c", 2);
+ return event;
+ }
+
+
+}
diff --git
a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyGroup.java
b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyGroup.java
index e783d5e946..506f246d83 100644
---
a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyGroup.java
+++
b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyGroup.java
@@ -29,7 +29,7 @@ public class StaticPropertyGroup extends StaticProperty {
private Boolean showLabel;
- private boolean horizontalRendering;
+ private boolean horizontalRendering = true;
public StaticPropertyGroup() {
super(StaticPropertyType.StaticPropertyGroup);
diff --git
a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java
b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java
index 9dd2df2b4d..2ad5f735e5 100644
---
a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java
+++
b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java
@@ -32,7 +32,6 @@ import
org.apache.streampipes.model.staticproperty.RuntimeResolvableGroupStaticP
import
org.apache.streampipes.model.staticproperty.RuntimeResolvableOneOfStaticProperty;
import
org.apache.streampipes.model.staticproperty.RuntimeResolvableTreeInputStaticProperty;
import org.apache.streampipes.model.staticproperty.SecretStaticProperty;
-import org.apache.streampipes.model.staticproperty.SelectionStaticProperty;
import org.apache.streampipes.model.staticproperty.StaticProperty;
import org.apache.streampipes.model.staticproperty.StaticPropertyAlternative;
import org.apache.streampipes.model.staticproperty.StaticPropertyAlternatives;
@@ -239,14 +238,11 @@ public class StaticProperties {
label.getLabel(), label.getDescription());
}
- public static CollectionStaticProperty collection(Label label,
StaticProperty... sp) {
- for (StaticProperty staticProperty : sp) {
- setHorizontalRendering(staticProperty);
- }
+ public static CollectionStaticProperty collection(Label label, boolean
horizontalAlignment, StaticProperty... sp) {
if (sp.length > 1) {
StaticPropertyGroup group = StaticProperties.group(label);
- group.setHorizontalRendering(true);
+ group.setHorizontalRendering(horizontalAlignment);
group.setStaticProperties(Arrays.asList(sp));
return new CollectionStaticProperty(label.getInternalId(),
label.getLabel(),
@@ -257,6 +253,10 @@ public class StaticProperties {
}
}
+ public static CollectionStaticProperty collection(Label label,
StaticProperty... sp) {
+ return collection(label, true, sp);
+ }
+
public static CodeInputStaticProperty codeStaticProperty(Label label,
CodeLanguage
codeLanguage,
String
defaultSkeleton) {
@@ -266,21 +266,4 @@ public class StaticProperties {
codeInputStaticProperty.setCodeTemplate(defaultSkeleton);
return codeInputStaticProperty;
}
-
- private static StaticProperty setHorizontalRendering(StaticProperty sp) {
- if (sp instanceof StaticPropertyGroup) {
- ((StaticPropertyGroup) sp).setHorizontalRendering(true);
- ((StaticPropertyGroup) sp).getStaticProperties().stream()
- .forEach(property -> setHorizontalRendering(property));
- } else if (sp instanceof SelectionStaticProperty) {
- ((SelectionStaticProperty) sp).setHorizontalRendering(true);
- } else if (sp instanceof StaticPropertyAlternatives) {
- ((StaticPropertyAlternatives) sp).getAlternatives().stream()
- .forEach(property ->
setHorizontalRendering(property.getStaticProperty()));
-
- }
-
- return sp;
- }
-
}
diff --git
a/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
b/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
index 85f64484d8..45e70af034 100644
---
a/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
+++
b/ui/src/app/core-ui/pipeline/pipeline-started-status/pipeline-started-status.component.html
@@ -82,6 +82,7 @@
class="w-100"
>
<sp-pipeline-operation-status
+ fxLayout="column"
[pipelineOperationStatus]="pipelineOperationStatus"
>
</sp-pipeline-operation-status>
diff --git
a/ui/src/app/core-ui/static-properties/static-property-util.service.ts
b/ui/src/app/core-ui/static-properties/static-property-util.service.ts
index bed7c81cd8..b85882cd26 100644
--- a/ui/src/app/core-ui/static-properties/static-property-util.service.ts
+++ b/ui/src/app/core-ui/static-properties/static-property-util.service.ts
@@ -19,6 +19,7 @@
import { Injectable } from '@angular/core';
import {
AnyStaticProperty,
+ CodeInputStaticProperty,
CollectionStaticProperty,
ColorPickerStaticProperty,
FileStaticProperty,
@@ -77,6 +78,12 @@ export class StaticPropertyUtilService {
clone = new ColorPickerStaticProperty();
clone.id = id;
clone.selectedProperty = val.selectedColor;
+ } else if (val instanceof CodeInputStaticProperty) {
+ clone = new CodeInputStaticProperty();
+ clone.elementId = id;
+ clone.codeTemplate = val.codeTemplate;
+ clone.value = val.value;
+ clone.language = val.language;
} else if (val instanceof StaticPropertyGroup) {
clone = new StaticPropertyGroup();
clone.elementId = id;
diff --git a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
index 7efa549cf1..58e0bee821 100644
--- a/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
+++ b/ui/src/app/editor/dialog/save-pipeline/save-pipeline.component.ts
@@ -296,7 +296,7 @@ export class SavePipelineComponent implements OnInit {
if (this.shepherdService.isTourActive()) {
this.shepherdService.hideCurrentStep();
}
- if (this.storageOptions.navigateToPipelineOverview) {
+ if (this.storageOptions.navigateToPipelineOverview && status?.success)
{
this.navigateToPipelineOverview();
}
}
diff --git
a/ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-details-expansion-panel.component.html
b/ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-details-expansion-panel.component.html
index 848ae64550..0f65683cf6 100644
---
a/ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-details-expansion-panel.component.html
+++
b/ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-details-expansion-panel.component.html
@@ -28,7 +28,7 @@
>
<sp-pipeline-element-details-row
[pipelineElement]="element"
- [logInfo]="logInfo[element.elementId]"
+ [logInfo]="logInfo[element.elementId] || []"
[pipelineRunning]="pipeline.running"
>
</sp-pipeline-element-details-row>
diff --git
a/ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-element-details-row/pipeline-element-details-row.component.html
b/ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-element-details-row/pipeline-element-details-row.component.html
index 284166d2dd..4c3a6cf22e 100644
---
a/ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-element-details-row/pipeline-element-details-row.component.html
+++
b/ui/src/app/pipeline-details/components/pipeline-details-expansion-panel/pipeline-element-details-row/pipeline-element-details-row.component.html
@@ -27,7 +27,7 @@
mat-icon-button
color="accent"
matTooltip="Logs"
- [disabled]="!pipelineRunning"
+ [disabled]="!pipelineRunning || logInfo.length === 0"
>
<mat-icon>topic</mat-icon>
</button>
diff --git
a/ui/src/app/pipeline-details/dialogs/pipeline-logs/pipeline-logs-dialog.component.html
b/ui/src/app/pipeline-details/dialogs/pipeline-logs/pipeline-logs-dialog.component.html
index a473730836..8edd5242bb 100644
---
a/ui/src/app/pipeline-details/dialogs/pipeline-logs/pipeline-logs-dialog.component.html
+++
b/ui/src/app/pipeline-details/dialogs/pipeline-logs/pipeline-logs-dialog.component.html
@@ -19,7 +19,7 @@
<div class="sp-dialog-container">
<div class="sp-dialog-content">
<div fxFlex="100" fxLayout="column">
- <div fxFlex="100" fxLayout="column">
+ <div fxFlex="100" fxLayout="column" class="p-10">
<div *ngFor="let logEntry of logInfo">
<sp-exception-message
[message]="logEntry.errorMessage"