diff --git a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/compact/generator/AdapterEnrichmentRuleGenerator.java b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/compact/generator/AdapterEnrichmentRuleGenerator.java index 5abb7a99ce..9c19b30791 100644 --- a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/compact/generator/AdapterEnrichmentRuleGenerator.java +++ b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/compact/generator/AdapterEnrichmentRuleGenerator.java @@ -25,6 +25,8 @@ public class AdapterEnrichmentRuleGenerator implements AdapterModelGenerator { + public static final String TIMESTAMP_FIELD = "timestamp"; + @Override public void apply(AdapterDescription adapterDescription, CompactAdapter compactAdapter) throws Exception { @@ -33,7 +35,7 @@ public void apply(AdapterDescription adapterDescription, var timestampRule = new AddTimestampRuleDescription(compactAdapter.enrich().timestamp()); adapterDescription.getRules().add(timestampRule); adapterDescription.getEventSchema().addEventProperty( - EpProperties.timestampProperty("timestamp") + EpProperties.timestampProperty(TIMESTAMP_FIELD) ); } } diff --git a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/compact/generator/CompactAdapterGenerator.java b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/compact/generator/CompactAdapterGenerator.java new file mode 100644 index 0000000000..6857d0c4ef --- /dev/null +++ b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/compact/generator/CompactAdapterGenerator.java @@ -0,0 +1,123 @@ +/* + * 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.connect.management.compact.generator; + +import org.apache.streampipes.connect.shared.preprocessing.convert.ToOriginalSchemaConverter; +import org.apache.streampipes.manager.template.CompactConfigGenerator; +import org.apache.streampipes.model.connect.adapter.AdapterDescription; +import org.apache.streampipes.model.connect.adapter.compact.CompactEventProperty; +import org.apache.streampipes.model.connect.adapter.compact.CreateOptions; +import org.apache.streampipes.model.connect.adapter.compact.EnrichmentConfig; +import org.apache.streampipes.model.connect.adapter.compact.TransformationConfig; +import org.apache.streampipes.model.connect.rules.TransformationRuleDescription; +import org.apache.streampipes.model.connect.rules.schema.RenameRuleDescription; +import org.apache.streampipes.model.connect.rules.value.AddTimestampRuleDescription; +import org.apache.streampipes.model.connect.rules.value.UnitTransformRuleDescription; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CompactAdapterGenerator { + + private final AdapterDescription adapterDescription; + + public CompactAdapterGenerator(AdapterDescription adapterDescription) { + this.adapterDescription = adapterDescription; + } + + public List> getConfig() { + var configs = new ArrayList>(); + adapterDescription.getConfig().forEach(c -> { + configs.add(new CompactConfigGenerator(c).toTemplateValue()); + }); + return configs; + } + + public Map getSchema() { + var map = new HashMap(); + var originalProperties = new ToOriginalSchemaConverter( + adapterDescription.getEventSchema().getEventProperties() + ).getTransformedProperties(); + originalProperties + .forEach(ep -> map.put(ep.getRuntimeName(), new CompactEventProperty( + ep.getLabel(), + ep.getDescription(), + ep.getPropertyScope(), + ep.getSemanticType() + ))); + return map; + } + + public EnrichmentConfig getEnrichmentConfig() { + if (hasTimestampEnrichmentRule()) { + return new EnrichmentConfig(AdapterEnrichmentRuleGenerator.TIMESTAMP_FIELD); + } else { + return null; + } + } + + public TransformationConfig getTransformationConfig() { + var renameRules = new HashMap(); + var unitTransformRules = new HashMap(); + if (hasTransformationRule()) { + if (hasRule(RenameRuleDescription.class)) { + var rules = getRules(RenameRuleDescription.class); + rules.forEach(rule -> { + renameRules.put(rule.getOldRuntimeKey(), rule.getNewRuntimeKey()); + }); + } else if (hasRule(UnitTransformRuleDescription.class)) { + var rules = getRules(UnitTransformRuleDescription.class); + rules.forEach(rule -> { + unitTransformRules.put(rule.getRuntimeKey(), rule.getToUnitRessourceURL()); + }); + } + } + return new TransformationConfig(renameRules, unitTransformRules); + } + + public CreateOptions getCreateOptions() { + return new CreateOptions( + true, + true + ); + } + + private boolean hasTimestampEnrichmentRule() { + return hasRule(AddTimestampRuleDescription.class); + } + + private boolean hasTransformationRule() { + return adapterDescription.getRules().stream() + .anyMatch(r -> hasRule(RenameRuleDescription.class) || hasRule(UnitTransformRuleDescription.class)); + } + + private boolean hasRule(Class rule) { + return adapterDescription.getRules().stream().anyMatch(r -> r.getClass().equals(rule)); + } + + private List getRules(Class rule) { + return adapterDescription.getRules() + .stream() + .filter(rule::isInstance) + .map(rule::cast) + .toList(); + } +} diff --git a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/CompactAdapterManagement.java b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/CompactAdapterManagement.java index a973a22d85..12db048447 100644 --- a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/CompactAdapterManagement.java +++ b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/CompactAdapterManagement.java @@ -19,6 +19,7 @@ package org.apache.streampipes.connect.management.management; import org.apache.streampipes.connect.management.compact.generator.AdapterModelGenerator; +import org.apache.streampipes.connect.management.compact.generator.CompactAdapterGenerator; import org.apache.streampipes.model.connect.adapter.AdapterDescription; import org.apache.streampipes.model.connect.adapter.compact.CompactAdapter; import org.apache.streampipes.storage.api.IAdapterStorage; @@ -45,6 +46,22 @@ public AdapterDescription convertToAdapterDescription(CompactAdapter compactAdap return adapterDescription; } + public CompactAdapter convertToCompactAdapter(AdapterDescription adapterDescription) throws Exception { + var generator = new CompactAdapterGenerator(adapterDescription); + + return new CompactAdapter( + adapterDescription.getElementId(), + adapterDescription.getName(), + adapterDescription.getDescription(), + adapterDescription.getAppId(), + generator.getConfig(), + generator.getSchema(), + generator.getEnrichmentConfig(), + generator.getTransformationConfig(), + generator.getCreateOptions() + ); + } + public AdapterDescription convertToAdapterDescription(CompactAdapter compactAdapter, AdapterDescription existingAdapter) throws Exception { var adapterDescription = convertToAdapterDescription(compactAdapter); diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/connect/adapter/compact/CompactAdapter.java b/streampipes-model/src/main/java/org/apache/streampipes/model/connect/adapter/compact/CompactAdapter.java index d8a0a93f29..1129736376 100644 --- a/streampipes-model/src/main/java/org/apache/streampipes/model/connect/adapter/compact/CompactAdapter.java +++ b/streampipes-model/src/main/java/org/apache/streampipes/model/connect/adapter/compact/CompactAdapter.java @@ -36,9 +36,12 @@ package org.apache.streampipes.model.connect.adapter.compact; +import org.apache.streampipes.model.shared.annotation.TsModel; + import java.util.List; import java.util.Map; +@TsModel public record CompactAdapter( String id, String name, diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/connect/adapter/compact/CompactEventProperty.java b/streampipes-model/src/main/java/org/apache/streampipes/model/connect/adapter/compact/CompactEventProperty.java index c0cdd7e6af..479b42b0dc 100644 --- a/streampipes-model/src/main/java/org/apache/streampipes/model/connect/adapter/compact/CompactEventProperty.java +++ b/streampipes-model/src/main/java/org/apache/streampipes/model/connect/adapter/compact/CompactEventProperty.java @@ -18,6 +18,9 @@ package org.apache.streampipes.model.connect.adapter.compact; +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) public record CompactEventProperty( String label, String description, diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/CompactConfigGenerator.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/CompactConfigGenerator.java new file mode 100644 index 0000000000..3ec54266b7 --- /dev/null +++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/CompactConfigGenerator.java @@ -0,0 +1,211 @@ +/* + * 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.manager.template; + +import org.apache.streampipes.model.staticproperty.AnyStaticProperty; +import org.apache.streampipes.model.staticproperty.CodeInputStaticProperty; +import org.apache.streampipes.model.staticproperty.CollectionStaticProperty; +import org.apache.streampipes.model.staticproperty.ColorPickerStaticProperty; +import org.apache.streampipes.model.staticproperty.FileStaticProperty; +import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty; +import org.apache.streampipes.model.staticproperty.MappingPropertyNary; +import org.apache.streampipes.model.staticproperty.MappingPropertyUnary; +import org.apache.streampipes.model.staticproperty.MatchingStaticProperty; +import org.apache.streampipes.model.staticproperty.OneOfStaticProperty; +import org.apache.streampipes.model.staticproperty.Option; +import org.apache.streampipes.model.staticproperty.RuntimeResolvableGroupStaticProperty; +import org.apache.streampipes.model.staticproperty.RuntimeResolvableTreeInputStaticProperty; +import org.apache.streampipes.model.staticproperty.SecretStaticProperty; +import org.apache.streampipes.model.staticproperty.SlideToggleStaticProperty; +import org.apache.streampipes.model.staticproperty.StaticProperty; +import org.apache.streampipes.model.staticproperty.StaticPropertyAlternative; +import org.apache.streampipes.model.staticproperty.StaticPropertyAlternatives; +import org.apache.streampipes.model.staticproperty.StaticPropertyGroup; +import org.apache.streampipes.model.staticproperty.StaticPropertyVisitor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CompactConfigGenerator implements StaticPropertyVisitor { + + private final Map config; + private final StaticProperty staticProperty; + + public CompactConfigGenerator(StaticProperty staticProperty) { + this.config = new HashMap<>(); + this.staticProperty = staticProperty; + } + + public Map toTemplateValue() { + staticProperty.accept(this); + return config; + } + + @Override + public void visit(AnyStaticProperty property) { + addConfig( + property, + property.getOptions().stream().filter(Option::isSelected).map(Option::getName).toList() + ); + } + + @Override + public void visit(CodeInputStaticProperty codeInputStaticProperty) { + addConfig( + codeInputStaticProperty, + codeInputStaticProperty.getValue() + ); + } + + @Override + public void visit(CollectionStaticProperty collectionStaticProperty) { + config.put( + collectionStaticProperty.getInternalName(), + addListEntry(collectionStaticProperty.getMembers()) + ); + } + + @Override + public void visit(ColorPickerStaticProperty colorPickerStaticProperty) { + addConfig( + colorPickerStaticProperty, + colorPickerStaticProperty.getSelectedColor() + ); + } + + @Override + public void visit(FileStaticProperty fileStaticProperty) { + addConfig( + fileStaticProperty, + fileStaticProperty.getLocationPath() + ); + } + + @Override + public void visit(FreeTextStaticProperty freeTextStaticProperty) { + addConfig( + freeTextStaticProperty, + freeTextStaticProperty.getValue() + ); + } + + @Override + public void visit(MappingPropertyNary mappingPropertyNary) { + addConfig( + mappingPropertyNary, + mappingPropertyNary.getSelectedProperties() + ); + } + + @Override + public void visit(MappingPropertyUnary mappingPropertyUnary) { + addConfig( + mappingPropertyUnary, + mappingPropertyUnary.getSelectedProperty() + ); + } + + @Override + public void visit(MatchingStaticProperty matchingStaticProperty) { + // not supported + } + + @Override + public void visit(OneOfStaticProperty oneOfStaticProperty) { + addConfig( + oneOfStaticProperty, + oneOfStaticProperty.getOptions().stream().filter(Option::isSelected).findFirst().map(Option::getName) + ); + } + + @Override + public void visit(SecretStaticProperty secretStaticProperty) { + config.put(secretStaticProperty.getInternalName(), secretStaticProperty.getValue()); + config.put("encrypted", secretStaticProperty.getEncrypted()); + } + + @Override + public void visit(StaticPropertyAlternative staticPropertyAlternative) { + + } + + @Override + public void visit(StaticPropertyAlternatives staticPropertyAlternatives) { + var selectedAlternativeOpt = staticPropertyAlternatives.getAlternatives() + .stream() + .filter(StaticPropertyAlternative::getSelected) + .findFirst(); + if (selectedAlternativeOpt.isPresent()) { + var selectedAlternative = selectedAlternativeOpt.get(); + config.put(staticPropertyAlternatives.getInternalName(), selectedAlternative.getInternalName()); + if (selectedAlternative.getStaticProperty() != null) { + var alternative = new CompactConfigGenerator(selectedAlternative.getStaticProperty()).toTemplateValue(); + config.putAll(alternative); + } + } + } + + @Override + public void visit(StaticPropertyGroup staticPropertyGroup) { + config.putAll(addNestedEntry(staticPropertyGroup.getStaticProperties())); + } + + @Override + public void visit(SlideToggleStaticProperty slideToggleStaticProperty) { + addConfig( + slideToggleStaticProperty, + slideToggleStaticProperty.isSelected() + ); + } + + @Override + public void visit(RuntimeResolvableTreeInputStaticProperty treeInputStaticProperty) { + addConfig( + treeInputStaticProperty, + treeInputStaticProperty.getSelectedNodesInternalNames() + ); + } + + @Override + public void visit(RuntimeResolvableGroupStaticProperty groupStaticProperty) { + + } + + private void addConfig(StaticProperty staticProperty, Object value) { + config.put(staticProperty.getInternalName(), value); + } + + public List> addListEntry(List staticProperties) { + return staticProperties.stream() + .map(sp -> new CompactConfigGenerator(sp).toTemplateValue()) + .toList(); + } + + public Map addNestedEntry(List staticProperties) { + Map entry = new HashMap<>(); + + staticProperties.forEach(sp -> { + Map groupEntries = new CompactConfigGenerator(sp).toTemplateValue(); + entry.putAll(groupEntries); + }); + + return entry; + } +} diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java index 11ec281927..fbcd30907a 100644 --- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java +++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java @@ -27,7 +27,9 @@ import org.apache.streampipes.model.staticproperty.MappingPropertyUnary; import org.apache.streampipes.model.staticproperty.MatchingStaticProperty; import org.apache.streampipes.model.staticproperty.OneOfStaticProperty; +import org.apache.streampipes.model.staticproperty.Option; import org.apache.streampipes.model.staticproperty.RuntimeResolvableGroupStaticProperty; +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.SlideToggleStaticProperty; @@ -132,6 +134,10 @@ public void visit(OneOfStaticProperty oneOfStaticProperty) { String value = getConfigValueAsString(oneOfStaticProperty); oneOfStaticProperty.getOptions().forEach(option -> option.setSelected(option.getName().equals(value))); + if (oneOfStaticProperty instanceof RuntimeResolvableOneOfStaticProperty + && oneOfStaticProperty.getOptions().isEmpty()) { + oneOfStaticProperty.setOptions(List.of(new Option(value, true))); + } } } @@ -204,7 +210,7 @@ public void visit(RuntimeResolvableTreeInputStaticProperty property) { @Override public void visit(RuntimeResolvableGroupStaticProperty groupStaticProperty) { - // TODO not yet supported + // TODO } @@ -289,29 +295,4 @@ private List> getCaseInsensitiveList(Map map } throw new IllegalArgumentException("Key '" + key + "' not found"); } - - -// private String getAsString(StaticProperty sp) { -// return configs.get(sp.getInternalName()).toString(); -// } -// -// private boolean getAsBoolean(StaticProperty sp) { -// return Boolean.parseBoolean(configs.get(sp.getInternalName()).toString()); -// } -// -// private Map getAsMap(StaticProperty sp) { -// return (Map) configs.get(sp.getInternalName()); -// } -// -// private Map getAsMap(StaticProperty sp, String subkey) { -// return (Map) getAsMap(sp).get(subkey); -// } -// -// private Map getAsMap(Map map, String key) { -// return (Map) map.get(key); -// } -// -// private List> getAsList(StaticProperty sp, String key) { -// return (List>) getAsMap(sp).get(key); -// } } diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java index 0961a8c5c4..3ca4465d29 100644 --- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java +++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java @@ -22,11 +22,13 @@ import org.apache.streampipes.commons.prometheus.adapter.AdapterMetricsManager; import org.apache.streampipes.connect.management.management.AdapterMasterManagement; import org.apache.streampipes.connect.management.management.AdapterUpdateManagement; +import org.apache.streampipes.connect.management.management.CompactAdapterManagement; import org.apache.streampipes.manager.pipeline.PipelineManager; import org.apache.streampipes.model.client.user.DefaultRole; import org.apache.streampipes.model.client.user.Permission; import org.apache.streampipes.model.connect.adapter.AdapterDescription; import org.apache.streampipes.model.connect.adapter.PipelineUpdateInfo; +import org.apache.streampipes.model.connect.adapter.compact.CompactAdapter; import org.apache.streampipes.model.message.Message; import org.apache.streampipes.model.message.Notifications; import org.apache.streampipes.model.monitoring.SpLogMessage; @@ -34,6 +36,7 @@ import org.apache.streampipes.resource.management.PermissionResourceManager; import org.apache.streampipes.resource.management.SpResourceManager; import org.apache.streampipes.rest.security.AuthConstants; +import org.apache.streampipes.rest.shared.constants.SpMediaType; import org.apache.streampipes.storage.api.IPipelineStorage; import org.apache.streampipes.storage.management.StorageDispatcher; @@ -66,7 +69,7 @@ public class AdapterResource extends AbstractAdapterResource new AdapterMasterManagement( StorageDispatcher.INSTANCE.getNoSqlStore() - .getAdapterInstanceStorage(), + .getAdapterInstanceStorage(), new SpResourceManager().manageAdapters(), new SpResourceManager().manageDataStreams(), AdapterMetricsManager.INSTANCE.getAdapterMetrics() @@ -93,6 +96,16 @@ public ResponseEntity addAdapter(@RequestBody AdapterDescript return ok(Notifications.success(adapterId)); } + @PostMapping(path = "compact", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = { + MediaType.APPLICATION_JSON_VALUE, + SpMediaType.YAML, + SpMediaType.YML + }) + @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE) + public ResponseEntity convertToCompactAdapter(@RequestBody AdapterDescription adapterDescription) throws Exception { + return ok(new CompactAdapterManagement(List.of()).convertToCompactAdapter(adapterDescription)); + } + @PutMapping(produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE) public ResponseEntity updateAdapter(@RequestBody AdapterDescription adapterDescription) { @@ -108,7 +121,7 @@ public ResponseEntity updateAdapter(@RequestBody AdapterDescr } @PutMapping(path = "pipeline-migration-preflight", consumes = MediaType.APPLICATION_JSON_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) + produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE) public ResponseEntity> performPipelineMigrationPreflight( @RequestBody AdapterDescription adapterDescription @@ -119,17 +132,26 @@ public ResponseEntity> performPipelineMigrationPrefligh return ok(migrations); } - @GetMapping(path = "/{id}", produces = { MediaType.APPLICATION_JSON_VALUE, "application/yaml" }) + @GetMapping(path = "/{id}", produces = {MediaType.APPLICATION_JSON_VALUE, SpMediaType.YAML, SpMediaType.YML}) @PreAuthorize(AuthConstants.HAS_READ_ADAPTER_PRIVILEGE) - public ResponseEntity getAdapter(@PathVariable("id") String adapterId) { + public ResponseEntity getAdapter(@PathVariable("id") String adapterId, + @RequestParam(value = "output", + defaultValue = "full", + required = false) String outputMode) { try { AdapterDescription adapterDescription = getAdapterDescription(adapterId); - - return ok(adapterDescription); + if (outputMode.equalsIgnoreCase("compact")) { + return ok(toCompactAdapterDescription(adapterDescription)); + } else { + return ok(adapterDescription); + } } catch (AdapterException e) { LOG.error("Error while getting adapter with id {}", adapterId, e); return fail(); + } catch (Exception e) { + LOG.error("Error while transforming adapter {}", adapterId, e); + return fail(); } } @@ -164,7 +186,7 @@ public ResponseEntity deleteAdapter(@PathVariable("id") String elementId, boolean deleteAssociatedPipelines) { List pipelinesUsingAdapter = getPipelinesUsingAdapter(elementId); IPipelineStorage pipelineStorageAPI = StorageDispatcher.INSTANCE.getNoSqlStore() - .getPipelineStorageAPI(); + .getPipelineStorageAPI(); if (pipelinesUsingAdapter.isEmpty()) { try { @@ -178,11 +200,11 @@ public ResponseEntity deleteAdapter(@PathVariable("id") String elementId, List namesOfPipelinesUsingAdapter = pipelinesUsingAdapter .stream() .map(pipelineId -> pipelineStorageAPI.getElementById( - pipelineId) - .getName()) + pipelineId) + .getName()) .collect(Collectors.toList()); return ResponseEntity.status(HttpStatus.SC_CONFLICT) - .body(String.join(", ", namesOfPipelinesUsingAdapter)); + .body(String.join(", ", namesOfPipelinesUsingAdapter)); } else { PermissionResourceManager permissionResourceManager = new PermissionResourceManager(); // find out the names of pipelines that have an owner and the owner is not the current user @@ -204,16 +226,16 @@ public ResponseEntity deleteAdapter(@PathVariable("id") String elementId, } managementService.deleteAdapter(elementId); return ok(Notifications.success("Adapter with id: " + elementId - + " and all pipelines using the adapter are deleted.")); + + " and all pipelines using the adapter are deleted.")); } catch (Exception e) { LOG.error("Error while deleting adapter with id " - + elementId + " and all pipelines using the adapter", e); + + elementId + " and all pipelines using the adapter", e); return ok(Notifications.error(e.getMessage())); } } else { // otherwise, hint the user the names of pipelines using the adapter but not owned by the user return ResponseEntity.status(HttpStatus.SC_CONFLICT) - .body(String.join(", ", namesOfPipelinesNotOwnedByUser)); + .body(String.join(", ", namesOfPipelinesNotOwnedByUser)); } } } @@ -226,7 +248,7 @@ public ResponseEntity getAllAdapters() { } catch (AdapterException e) { LOG.error("Error while getting all adapters", e); return ResponseEntity.status(500) - .build(); + .build(); } } @@ -234,10 +256,14 @@ private AdapterDescription getAdapterDescription(String adapterId) throws Adapte return managementService.getAdapter(adapterId); } + private CompactAdapter toCompactAdapterDescription(AdapterDescription adapterDescription) throws Exception { + return new CompactAdapterManagement(List.of()).convertToCompactAdapter(adapterDescription); + } + private List getPipelinesUsingAdapter(String adapterId) { return StorageDispatcher.INSTANCE.getNoSqlStore() - .getPipelineStorageAPI() - .getPipelinesUsingAdapter(adapterId); + .getPipelineStorageAPI() + .getPipelinesUsingAdapter(adapterId); } } diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java index 97dbb7912a..88424c2256 100644 --- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java +++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java @@ -31,6 +31,7 @@ import org.apache.streampipes.model.message.Notifications; import org.apache.streampipes.resource.management.SpResourceManager; import org.apache.streampipes.rest.security.AuthConstants; +import org.apache.streampipes.rest.shared.constants.SpMediaType; import org.apache.streampipes.rest.shared.exception.BadRequestException; import org.apache.streampipes.storage.management.StorageDispatcher; @@ -69,8 +70,8 @@ public CompactAdapterResource() { @PostMapping( consumes = { MediaType.APPLICATION_JSON_VALUE, - "application/yaml", - "application/yml" + SpMediaType.YML, + SpMediaType.YAML } ) @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE) diff --git a/ui/projects/streampipes/platform-services/src/lib/apis/adapter.service.ts b/ui/projects/streampipes/platform-services/src/lib/apis/adapter.service.ts index a18160a427..ff32b390f4 100644 --- a/ui/projects/streampipes/platform-services/src/lib/apis/adapter.service.ts +++ b/ui/projects/streampipes/platform-services/src/lib/apis/adapter.service.ts @@ -24,6 +24,7 @@ import { Observable } from 'rxjs'; import { PlatformServicesCommons } from './commons.service'; import { AdapterDescription, + CompactAdapter, Message, PipelineUpdateInfo, } from '../model/gen/streampipes-model'; @@ -55,6 +56,15 @@ export class AdapterService { ); } + convertToCompactAdapter( + adapterDescription: AdapterDescription, + ): Observable { + return this.http.post( + this.connectPath + `/master/adapters/compact`, + adapterDescription, + ); + } + requestAdapterDescriptions(path: string): Observable { return this.http.get(this.connectPath + path).pipe( map(response => { 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 235a32498e..8840666969 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 @@ -20,11 +20,10 @@ /* tslint:disable */ /* eslint-disable */ // @ts-nocheck -// Generated using typescript-generator version 3.2.1263 on 2024-10-02 14:54:56. +// Generated using typescript-generator version 3.2.1263 on 2024-10-08 10:25:48. export class NamedStreamPipesEntity implements Storable { '@class': - | 'org.apache.streampipes.model.template.PipelineTemplateDescription' | 'org.apache.streampipes.model.SpDataStream' | 'org.apache.streampipes.model.base.VersionedNamedStreamPipesEntity' | 'org.apache.streampipes.model.connect.adapter.AdapterDescription' @@ -644,28 +643,6 @@ export class AssetExportConfiguration { } } -export class BoundPipelineElement { - connectedTo: BoundPipelineElement[]; - pipelineElementTemplate: InvocableStreamPipesEntity; - - static fromData( - data: BoundPipelineElement, - target?: BoundPipelineElement, - ): BoundPipelineElement { - if (!data) { - return data; - } - const instance = target || new BoundPipelineElement(); - instance.connectedTo = __getCopyArrayFn(BoundPipelineElement.fromData)( - data.connectedTo, - ); - instance.pipelineElementTemplate = InvocableStreamPipesEntity.fromData( - data.pipelineElementTemplate, - ); - return instance; - } -} - export class CanvasPosition { x: number; y: number; @@ -773,6 +750,64 @@ export class ColorPickerStaticProperty extends StaticProperty { } } +export class CompactAdapter { + appId: string; + configuration: { [index: string]: any }[]; + createOptions: CreateOptions; + description: string; + enrich: EnrichmentConfig; + id: string; + name: string; + schema: { [index: string]: CompactEventProperty }; + transform: TransformationConfig; + + static fromData( + data: CompactAdapter, + target?: CompactAdapter, + ): CompactAdapter { + if (!data) { + return data; + } + const instance = target || new CompactAdapter(); + instance.appId = data.appId; + instance.configuration = __getCopyArrayFn( + __getCopyObjectFn(__identity()), + )(data.configuration); + instance.createOptions = CreateOptions.fromData(data.createOptions); + instance.description = data.description; + instance.enrich = EnrichmentConfig.fromData(data.enrich); + instance.id = data.id; + instance.name = data.name; + instance.schema = __getCopyObjectFn(CompactEventProperty.fromData)( + data.schema, + ); + instance.transform = TransformationConfig.fromData(data.transform); + return instance; + } +} + +export class CompactEventProperty { + description: string; + label: string; + propertyScope: string; + semanticType: string; + + static fromData( + data: CompactEventProperty, + target?: CompactEventProperty, + ): CompactEventProperty { + if (!data) { + return data; + } + const instance = target || new CompactEventProperty(); + instance.description = data.description; + instance.label = data.label; + instance.propertyScope = data.propertyScope; + instance.semanticType = data.semanticType; + return instance; + } +} + export class CompactPipeline { createOptions: CreateOptions; description: string; @@ -1478,6 +1513,22 @@ export interface EndpointSelectable { selectedEndpointUrl: string; } +export class EnrichmentConfig { + timestamp: string; + + static fromData( + data: EnrichmentConfig, + target?: EnrichmentConfig, + ): EnrichmentConfig { + if (!data) { + return data; + } + const instance = target || new EnrichmentConfig(); + instance.timestamp = data.timestamp; + return instance; + } +} + export class ValueSpecification { '@class': | 'org.apache.streampipes.model.schema.QuantitativeValue' @@ -3024,32 +3075,6 @@ export class PipelineStatusMessage { } } -export class PipelineTemplateDescription extends NamedStreamPipesEntity { - '@class': 'org.apache.streampipes.model.template.PipelineTemplateDescription'; - 'boundTo': BoundPipelineElement[]; - 'pipelineTemplateDescription': string; - 'pipelineTemplateId': string; - 'pipelineTemplateName': string; - - static 'fromData'( - data: PipelineTemplateDescription, - target?: PipelineTemplateDescription, - ): PipelineTemplateDescription { - if (!data) { - return data; - } - const instance = target || new PipelineTemplateDescription(); - super.fromData(data, instance); - instance.boundTo = __getCopyArrayFn(BoundPipelineElement.fromData)( - data.boundTo, - ); - instance.pipelineTemplateDescription = data.pipelineTemplateDescription; - instance.pipelineTemplateId = data.pipelineTemplateId; - instance.pipelineTemplateName = data.pipelineTemplateName; - return instance; - } -} - export class PipelineTemplateGenerationRequest { pipelineDescription: string; pipelineName: string; @@ -3074,37 +3099,6 @@ export class PipelineTemplateGenerationRequest { } } -export class PipelineTemplateInvocation { - '@class': 'org.apache.streampipes.model.template.PipelineTemplateInvocation'; - 'dataStreamId': string; - 'kviName': string; - 'pipelineTemplateDescription': PipelineTemplateDescription; - 'pipelineTemplateId': string; - 'staticProperties': StaticPropertyUnion[]; - - static 'fromData'( - data: PipelineTemplateInvocation, - target?: PipelineTemplateInvocation, - ): PipelineTemplateInvocation { - if (!data) { - return data; - } - const instance = target || new PipelineTemplateInvocation(); - instance['@class'] = data['@class']; - instance.dataStreamId = data.dataStreamId; - instance.kviName = data.kviName; - instance.pipelineTemplateDescription = - PipelineTemplateDescription.fromData( - data.pipelineTemplateDescription, - ); - instance.pipelineTemplateId = data.pipelineTemplateId; - instance.staticProperties = __getCopyArrayFn( - StaticProperty.fromDataUnion, - )(data.staticProperties); - return instance; - } -} - export class PipelineUpdateInfo { canAutoMigrate: boolean; migrationInfo: string; @@ -3960,6 +3954,26 @@ export class TransformOutputStrategy extends OutputStrategy { } } +export class TransformationConfig { + measurementUnit: { [index: string]: string }; + rename: { [index: string]: string }; + + static fromData( + data: TransformationConfig, + target?: TransformationConfig, + ): TransformationConfig { + if (!data) { + return data; + } + const instance = target || new TransformationConfig(); + instance.measurementUnit = __getCopyObjectFn(__identity())( + data.measurementUnit, + ); + instance.rename = __getCopyObjectFn(__identity())(data.rename); + return instance; + } +} + export class TreeInputNode { children: TreeInputNode[]; dataNode: boolean; diff --git a/ui/src/app/connect/components/adapter-code-panel/adapter-code-panel.component.html b/ui/src/app/connect/components/adapter-code-panel/adapter-code-panel.component.html new file mode 100644 index 0000000000..52b5d4e5f4 --- /dev/null +++ b/ui/src/app/connect/components/adapter-code-panel/adapter-code-panel.component.html @@ -0,0 +1,36 @@ + + +
+ + +

+        
+ +

+        
+
+
diff --git a/ui/src/app/connect/components/adapter-code-panel/adapter-code-panel.component.scss b/ui/src/app/connect/components/adapter-code-panel/adapter-code-panel.component.scss new file mode 100644 index 0000000000..c02ba8acc6 --- /dev/null +++ b/ui/src/app/connect/components/adapter-code-panel/adapter-code-panel.component.scss @@ -0,0 +1,30 @@ +/*! + * 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. + * + */ + +.preview-text { + background-color: black; + font: + 9pt Inconsolata, + monospace; + text-shadow: 0 0 5px #c8c8c8; + color: white; + padding: 10px; + max-width: 100%; + overflow-y: scroll; + white-space: pre-wrap; +} diff --git a/ui/src/app/connect/components/adapter-code-panel/adapter-code-panel.component.ts b/ui/src/app/connect/components/adapter-code-panel/adapter-code-panel.component.ts new file mode 100644 index 0000000000..5c742add6f --- /dev/null +++ b/ui/src/app/connect/components/adapter-code-panel/adapter-code-panel.component.ts @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Component, Input, OnInit } from '@angular/core'; +import { + AdapterDescription, + AdapterService, + CompactAdapter, +} from '@streampipes/platform-services'; + +@Component({ + selector: 'sp-adapter-code-panel', + templateUrl: './adapter-code-panel.component.html', + styleUrls: ['./adapter-code-panel.component.scss'], +}) +export class AdapterCodePanelComponent implements OnInit { + @Input() + adapterDescription: AdapterDescription; + + @Input() + maxHeight = '300px'; + + compactAdapter: CompactAdapter; + + constructor(private adapterService: AdapterService) {} + + ngOnInit(): void { + this.adapterService + .convertToCompactAdapter(this.adapterDescription) + .subscribe(res => { + this.compactAdapter = res; + }); + } +} diff --git a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/adapter-options-panel/adapter-options-panel.component.html b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/adapter-options-panel/adapter-options-panel.component.html index 2bcfd840d1..b3afa5c944 100644 --- a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/adapter-options-panel/adapter-options-panel.component.html +++ b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/adapter-options-panel/adapter-options-panel.component.html @@ -18,17 +18,16 @@
-
- {{ optionIcon }} -
-
+
+ {{ optionIcon }}
-
+
{{ optionTitle }}
- {{ - optionDescription - }} + {{ optionDescription }} + +
- -
diff --git a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html index 66cbbed55f..f7ff5b8c7b 100644 --- a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html +++ b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.html @@ -145,6 +145,21 @@ + + + @if (showCode) { + + + } +
diff --git a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts index e6b97f915a..e8fc266424 100644 --- a/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts +++ b/ui/src/app/connect/components/adapter-configuration/start-adapter-configuration/start-adapter-configuration.component.ts @@ -89,6 +89,7 @@ export class StartAdapterConfigurationComponent implements OnInit { dataLakeTimestampField: string; startAdapterNow = true; + showCode = false; constructor( private dialogService: DialogService, diff --git a/ui/src/app/connect/components/adapter-details/abstract-adapter-details.directive.ts b/ui/src/app/connect/components/adapter-details/abstract-adapter-details.directive.ts index 394274eb12..1298c1dea9 100644 --- a/ui/src/app/connect/components/adapter-details/abstract-adapter-details.directive.ts +++ b/ui/src/app/connect/components/adapter-details/abstract-adapter-details.directive.ts @@ -28,7 +28,9 @@ import { SpBreadcrumbService, } from '@streampipes/shared-ui'; import { SpAdapterDetailsTabs } from './adapter-details-tabs'; +import { Directive } from '@angular/core'; +@Directive() export abstract class SpAbstractAdapterDetailsDirective { currentAdapterId: string; tabs: SpNavigationItem[] = []; diff --git a/ui/src/app/connect/components/adapter-details/adapter-details-code/adapter-details-code.component.html b/ui/src/app/connect/components/adapter-details/adapter-details-code/adapter-details-code.component.html new file mode 100644 index 0000000000..6a93d34e7f --- /dev/null +++ b/ui/src/app/connect/components/adapter-details/adapter-details-code/adapter-details-code.component.html @@ -0,0 +1,38 @@ + + + +
+ @if (adapter) { +
+ + +
+ } +
+
diff --git a/ui/src/app/connect/components/adapter-details/adapter-details-code/adapter-details-code.component.ts b/ui/src/app/connect/components/adapter-details/adapter-details-code/adapter-details-code.component.ts new file mode 100644 index 0000000000..07dfacae09 --- /dev/null +++ b/ui/src/app/connect/components/adapter-details/adapter-details-code/adapter-details-code.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; +import { SpAbstractAdapterDetailsDirective } from '../abstract-adapter-details.directive'; + +@Component({ + selector: 'sp-adapter-details-code', + templateUrl: './adapter-details-code.component.html', +}) +export class AdapterDetailsCodeComponent + extends SpAbstractAdapterDetailsDirective + implements OnInit +{ + ngOnInit() { + super.onInit(); + } + + onAdapterLoaded(): void {} +} diff --git a/ui/src/app/connect/components/adapter-details/adapter-details-tabs.ts b/ui/src/app/connect/components/adapter-details/adapter-details-tabs.ts index c4633a0fb4..8d3bf34714 100644 --- a/ui/src/app/connect/components/adapter-details/adapter-details-tabs.ts +++ b/ui/src/app/connect/components/adapter-details/adapter-details-tabs.ts @@ -36,6 +36,11 @@ export class SpAdapterDetailsTabs { itemTitle: 'Logs', itemLink: ['connect', 'details', elementId, 'logs'], }, + { + itemId: 'code', + itemTitle: 'Code', + itemLink: ['connect', 'details', elementId, 'code'], + }, ]; } } diff --git a/ui/src/app/connect/connect.module.ts b/ui/src/app/connect/connect.module.ts index 0d0e598a31..5d2e8b4756 100644 --- a/ui/src/app/connect/connect.module.ts +++ b/ui/src/app/connect/connect.module.ts @@ -104,6 +104,9 @@ import { AdapterStatusLightComponent } from './components/existing-adapters/adap import { SpAdapterDeploymentSettingsComponent } from './components/adapter-configuration/adapter-settings/adapter-deployment-settings/adapter-deployment-settings.component'; import { SpAdapterDocumentationDialogComponent } from './dialog/adapter-documentation/adapter-documentation-dialog.component'; import { AdapterDetailsDataComponent } from './components/adapter-details/adapter-details-data/adapter-details-data.component'; +import { AdapterCodePanelComponent } from './components/adapter-code-panel/adapter-code-panel.component'; +import { YamlPrettyPrintPipe } from './filter/yaml-pretty-print.pipe'; +import { AdapterDetailsCodeComponent } from './components/adapter-details/adapter-details-code/adapter-details-code.component'; @NgModule({ imports: [ @@ -184,6 +187,10 @@ import { AdapterDetailsDataComponent } from './components/adapter-details/adapte path: 'logs', component: SpAdapterDetailsLogsComponent, }, + { + path: 'code', + component: AdapterDetailsCodeComponent, + }, ], }, ], @@ -193,9 +200,11 @@ import { AdapterDetailsDataComponent } from './components/adapter-details/adapte ], exports: [ErrorMessageComponent], declarations: [ + AdapterCodePanelComponent, AdapterConfigurationHeaderComponent, AdapterConfigurationComponent, AdapterDescriptionComponent, + AdapterDetailsCodeComponent, AdapterDetailsDataComponent, AdapterStartedDialog, AdapterStatusLightComponent, @@ -214,6 +223,7 @@ import { AdapterDetailsDataComponent } from './components/adapter-details/adapte ExistingAdaptersComponent, AdapterFilterPipe, JsonPrettyPrintPipe, + YamlPrettyPrintPipe, AdapterConfigurationComponent, TimestampPipe, EditCorrectionValueComponent, diff --git a/ui/src/app/connect/filter/yaml-pretty-print.pipe.ts b/ui/src/app/connect/filter/yaml-pretty-print.pipe.ts new file mode 100644 index 0000000000..88f8b4b459 --- /dev/null +++ b/ui/src/app/connect/filter/yaml-pretty-print.pipe.ts @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Injectable, Pipe, PipeTransform } from '@angular/core'; +import { parse, stringify } from 'yaml'; + +@Pipe({ + name: 'yamlpretty', +}) +@Injectable({ providedIn: 'root' }) +export class YamlPrettyPrintPipe implements PipeTransform { + transform(obj: any) { + return stringify(obj).replace(/ /g, ' ').replace(/\n/g, '
'); + } +}