From 60cac327c2abb0b0c94d949a2ca5a2a880701df7 Mon Sep 17 00:00:00 2001 From: Tim <50115603+bossenti@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:02:32 +0100 Subject: [PATCH 01/20] refactor: remove legacy set reference (#2195) --- .../pipeline-element-options.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts b/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts index b8e0478d1a..b409d77d52 100644 --- a/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts +++ b/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts @@ -131,9 +131,7 @@ export class PipelineElementOptionsComponent implements OnInit, OnDestroy { ); this.pipelineElementCssType = this.pipelineElement.type; - this.isDataSource = - this.pipelineElement.type === 'stream' || - this.pipelineElement.type === 'set'; + this.isDataSource = this.pipelineElement.type === 'stream'; if ( this.isDataSource || From 648a8a2d044a448afbb1eac58bb1e7f4e9b3ccd6 Mon Sep 17 00:00:00 2001 From: Philipp Zehnder Date: Fri, 17 Nov 2023 18:10:58 +0100 Subject: [PATCH 02/20] 2200 include typescript generator maven plugin to plugin managment (#2201) * refactor(#2200): Add typescript generator plugin to maven dependency management * refactor(#2200): Fix header * refactor(#2200): Fix header --- pom.xml | 7 +++++++ streampipes-model-client/pom.xml | 1 - streampipes-model/pom.xml | 1 - 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index fef4b34cc0..bccd9c07c1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,5 @@ + + # StreamPipes k8s - The Operator's Dream + StreamPipes k8s is a helm chart to deploy StreamPipes on Kubernetes. @@ -28,20 +30,25 @@ We provide two helm chart templates to get you going: - **full**: contains more pipeline elements, requires **>16 GB RAM** (recommended) ## Prerequisite + Requires Helm (https://helm.sh/) and an active connection to a kubernetes cluster with a running tiller server. Tested with: + * K8s v1.19.3 * Helm v3.1.2 * Minikube v1.15.1 (recommended for local testing) -> **NOTE**: We experienced some problems with using host path volumes in Docker Desktop environments for persistent storage. Therefore, we suggest to use minikube for local testing. +> **NOTE**: We experienced some problems with using host path volumes in Docker Desktop environments for persistent +> storage. Therefore, we suggest to use minikube for local testing. ## Local testing -We recommend using [minikube](https://minikube.sigs.k8s.io/docs/) for local testing. Follow instructions in their docs to setup test environment +We recommend using [minikube](https://minikube.sigs.k8s.io/docs/) for local testing. Follow instructions in their docs +to setup test environment Once installed, start local minikube node with a mapped host volume: + ```bash minikube start --mount-string ${HOME}/streampipes-k8s:/streampipes-k8s --mount --memory=4g --cpus=4 ``` @@ -55,7 +62,9 @@ helm install streampipes ./ # full template only recommend if you have sufficient resources # helm install streampipes ./ --set deployment=full ``` + After a while, all containers should successfully started, indicated by the `Running` status. + ```bash kubectl get pods NAME READY STATUS RESTARTS AGE @@ -68,239 +77,256 @@ ui-b94bd9766-rm6zb 2/2 Running 0 3m27 ``` For **minikube users**: -> **NOTE**: If you're running Docker Desktop or Minikube with a local k8s cluster, the above step to use your host IP might not work. Luckily, you can port-forward a service port to your localhost using the following command to be able to access the UI either via `http://localhost` or `http://` (you require sudo to run this command in order to bind to a privileged port). +> **NOTE**: If you're running Docker Desktop or Minikube with a local k8s cluster, the above step to use your host IP +> might not work. Luckily, you can port-forward a service port to your localhost using the following command to be able to +> access the UI either via `http://localhost` or `http://` (you require sudo to run this command in order to bind +> to a privileged port). + ```bash kubectl port-forward svc/ui --address=0.0.0.0 80:80 ``` **Deleting** the current helm chart deployment: + ```bash helm del streampipes ``` We retain the created persistent volume. You need to manually delete it: + ```bash rm -rf ${HOME}/streampipes-k8s ``` -##Parameters - -###Common parameters -| Parameter Name | Description | Value | -|--------------------------------------------------|---------------------------------------------------------|-----------------------------------------| -| deployment | Deployment type (lite or full) | lite | -| preferredBroker | Preferred broker for deployment | "nats" | -| monitoringSystem | Enable monitoring system (true/false) | false | -| pullPolicy | Image pull policy | "Always" | -| restartPolicy | Restart policy for the container | Always | -| persistentVolumeReclaimPolicy | Reclaim policy for persistent volumes | "Delete" | -| persistentVolumeAccessModes | Access mode for persistent volumes | "ReadWriteOnce" | -| initialDelaySeconds | Initial delay for liveness and readiness probes | 60 | -| periodSeconds | Interval between liveness and readiness probes | 30 | -| failureThreshold | Number of consecutive failures for readiness probes | 30 | -| hostPath | Host path for the application | "" | - -###StreamPipes common parameters -| Parameter Name | Description | Value | -|-------------------------------------------------|---------------------------------------------------------|------------------------------------------| -| streampipes.version | StreamPipes version | "0.93.0-SNAPSHOT" | -| streampipes.registry | StreamPipes registry URL | "apachestreampipes" | -| streampipes.auth.secretName | The secret name for storing secrets | "sp-secrets" | -| streampipes.auth.users.admin.user | The initial admin user | "admin@streampipes.apache.org" | -| streampipes.auth.users.admin.password | The initial admin password (leave empty for autogen) | "admin" | -| streampipes.auth.users.service.user | The initial service account user | "sp-service-client" | -| streampipes.auth.users.service.secret | The initial service account secret | empty (auto-generated) | -| streampipes.auth.encryption.passcode | Passcode for value encryption | empty (auto-generated) | -| streampipes.core.appName | StreamPipes backend application name | "backend" | -| streampipes.core.port | StreamPipes backend port | 8030 | -| streampipes.core.persistence.storageClassName | Storage class name for backend PVs | "hostpath" | -| streampipes.core.persistence.storageSize | Size of the backend PV | "1Gi" | -| streampipes.core.persistence.claimName | Name of the backend PersistentVolumeClaim | "backend-pvc" | -| streampipes.core.persistence.pvName | Name of the backend PersistentVolume | "backend-pv" | -| streampipes.core.service.name | Name of the backend service | "backend" | -| streampipes.core.service.port | TargetPort of the StreamPipes backend service | 8030 | -| streampipes.ui.appName | StreamPipes UI application name | "ui" | -| streampipes.ui.resolverActive | Flag for enabling DNS resolver for Nginx proxy | true | -| streampipes.ui.port | StreamPipes UI port | 8088 | -| streampipes.ui.resolver | DNS resolver for Nginx proxy | "kube-dns.kube-system.svc.cluster.local" | -| streampipes.ui.service.name | Name of the UI service | "ui" | -| streampipes.ui.service.type | Type of the UI service | "ClusterIP" | -| streampipes.ui.service.nodePort | Node port for the UI service | 8088 | -| streampipes.ui.service.port | TargetPort of the StreamPipes UI service | 8088 | -| streampipes.ingress.active | Flag for enabling Ingress for StreamPipes | false | -| streampipes.ingress.annotations | Annotations for Ingress | {} | -| streampipes.ingress.host | Hostname for Ingress | "" | -| streampipes.ingressroute.active | Flag for enabling IngressRoute for StreamPipes | true | -| streampipes.ingressroute.annotations | Annotations for IngressRoute | {} | -| streampipes.ingressroute.entryPoints | Entry points for IngressRoute | ["web", "websecure"] | -| streampipes.ingressroute.host | Hostname for IngressRoute | "" | -| streampipes.ingressroute.certResolverActive | Flag for enabling certificate resolver for IngressRoute | true | -| streampipes.ingressroute.certResolver | Certificate resolver for IngressRoute | "" | - - -###Extensions common parameters -| Parameter Name | Description | Value | -|-------------------------------------------------|---------------------------------------------------------|------------------------------------------| -| extensions.iiot.appName | IIoT extensions application name | extensions-all-iiot | -| extensions.iiot.port | Port for the IIoT extensions application | 8090 | -| extensions.iiot.service.name | Name of the IIoT extensions service | extensions-all-iiot | -| extensions.iiot.service.port | TargetPort of the IIoT extensions service | 8090 | - - -###External common parameters - -####Consul common parameters -| Parameter Name | Description | Value | -|-------------------------------------------------|----------------------------------------------------------|------------------------------------------| -| external.consul.appName | Consul application name | "consul" | -| external.consul.version | Consul version | 1.14.3 | -| external.consul.webPort | Port number for the Consul web interface | 8500 | -| external.consul.dnsPort | Port number for the Consul DNS interface | 8600 | -| external.consul.persistence.storageClassName | Storage class name for Consul PVs | "hostpath" | -| external.consul.persistence.storageSize | Size of the Consul PV | "1Gi" | -| external.consul.persistence.claimName | Name of the Consul PersistentVolumeClaim | "consul-pvc" | -| external.consul.persistence.pvName | Name of the Consul PersistentVolume | "consul-pv" | -| external.consul.service.name | Name of the Consul service | "consul" | -| external.consul.service.webPort | TargetPort of the Consul service for web interface | 8500 | -| external.consul.service.dnsPort | TargetPort of the Consul service for DNS interface | 8600 | - -####Couchdb common parameters -| Parameter Name | Description | Value | -|-------------------------------------------------|----------------------------------------------------------|------------------------------------------| -| external.couchdb.appName | CouchDB application name | "couchdb" | -| external.couchdb.version | CouchDB version | 3.3.1 | -| external.couchdb.user | CouchDB admin username | "admin" | -| external.couchdb.password | CouchDB admin password | empty (auto-generated) | -| external.couchdb.port | Port for the CouchDB service | 5984 | -| external.couchdb.service.name | Name of the CouchDB service | "couchdb" | -| external.couchdb.service.port | TargetPort of the CouchDB service | 5984 | -| external.couchdb.persistence.storageClassName | Storage class name for CouchDB PVs | "hostpath" | -| external.couchdb.persistence.storageSize | Size of the CouchDB PV | "1Gi" | -| external.couchdb.persistence.claimName | Name of the CouchDB PersistentVolumeClaim | "couchdb-pvc" | -| external.couchdb.persistence.pvName | Name of the CouchDB PersistentVolume | "couchdb-pv" | - -####Influxdb common parameters -| Parameter Name | Description | Value | -|-------------------------------------------------|----------------------------------------------------------|------------------------------------------| -| external.influxdb.appName | InfluxDB application name | "influxdb" | -| external.influxdb.version | InfluxDB version | 2.6 | -| external.influxdb.username | InfluxDB admin username | "admin" | -| external.influxdb.password | InfluxDB admin password | empty (auto-generated) | -| external.influxdb.adminToken | InfluxDB admin token | empty (auto-generated) | -| external.influxdb.initOrg | InfluxDB initial organization | "sp" | -| external.influxdb.initBucket | InfluxDB initial bucket | "sp" | -| external.influxdb.initMode | InfluxDB initialization mode | "setup" | -| external.influxdb.apiPort | Port number for the InfluxDB service (API) | 8083 | -| external.influxdb.httpPort | Port number for the InfluxDB service (HTTP) | 8086 | -| external.influxdb.grpcPort | Port number for the InfluxDB service (gRPC) | 8090 | -| external.influxdb.service.name | Name of the InfluxDB service | "influxdb" | -| external.influxdb.service.apiPort | TargetPort of the InfluxDB service for API | 8083 | -| external.influxdb.service.httpPort | TargetPort of the InfluxDB service for HTTP | 8086 | -| external.influxdb.service.grpcPort | TargetPort of the InfluxDB service for gRPC | 8090 | -| external.influxdb.persistence.storageClassName | Storage class name for InfluxDB PVs | "hostpath" | -| external.influxdb.persistence.storageSize | Size of the InfluxDB PV | "1Gi" | -| external.influxdb.persistence.storageSizeV1 | Size of the InfluxDB PV for v1 databases | "1Gi" | -| external.influxdb.persistence.claimName | Name of the InfluxDBv2 PersistentVolumeClaim | "influxdb2-pvc" | -| external.influxdb.persistence.claimNameV1 | Name of the InfluxDBv1 PersistentVolumeClaim | "influxdb-pvc" | -| external.influxdb.persistence.pvName | Name of the InfluxDBv2 PersistentVolume | "influxdb2-pv" | -| external.influxdb.persistence.pvNameV1 | Name of the InfluxDBv1 PersistentVolume | "influxdb-pv" | - - -####Nats common parameters -| Parameter Name | Description | Value | -|-------------------------------------------------|----------------------------------------------------------|------------------------------------------| -| external.nats.appName | NATS application name | "nats" | -| external.nats.port | Port for the NATS service | 4222 | -| external.nats.version | NATS version | | -| external.nats.service.type | Type of the NATS service | "NodePort" | -| external.nats.service.externalTrafficPolicy | External traffic policy for the NATS service | "Local" | -| external.nats.service.name | Name of the NATS service | "nats" | -| external.nats.service.port | TargetPort of the NATS service | 4222 | - - -####Kafka common parameters -| Parameter Name | Description | Value | -|-------------------------------------------------|----------------------------------------------------------|------------------------------------------| -| external.kafka.appName | Kafka application name | "kafka" | -| external.kafka.version | Kafka version | 2.2.0 | -| external.kafka.port | Port for the Kafka service | 9092 | -| external.kafka.external.hostname | Name which will be advertised to external clients. Clients which use (default) port 9094 | "localhost" -| external.kafka.service.name | Name of the Kafka service | "kafka" | -| external.kafka.service.port | TargetPort of the Kafka service | 9092 | -| external.kafka.service.portOutside | Port for Kafka client outside of the cluster | 9094 | -| external.kafka.persistence.storageClassName | Storage class name for Kafka PVs | "hostpath" | -| external.kafka.persistence.storageSize | Size of the Kafka PV | "1Gi" | -| external.kafka.persistence.claimName | Name of the Kafka PersistentVolumeClaim | "kafka-pvc" | -| external.kafka.persistence.pvName | Name of the Kafka PersistentVolume | "kafka-pv" | + +## Parameters + +### Common parameters + +| Parameter Name | Description | Value | +|-------------------------------|-----------------------------------------------------|-----------------| +| deployment | Deployment type (lite or full) | lite | +| preferredBroker | Preferred broker for deployment | "nats" | +| monitoringSystem | Enable monitoring system (true/false) | false | +| pullPolicy | Image pull policy | "Always" | +| restartPolicy | Restart policy for the container | Always | +| persistentVolumeReclaimPolicy | Reclaim policy for persistent volumes | "Delete" | +| persistentVolumeAccessModes | Access mode for persistent volumes | "ReadWriteOnce" | +| initialDelaySeconds | Initial delay for liveness and readiness probes | 60 | +| periodSeconds | Interval between liveness and readiness probes | 30 | +| failureThreshold | Number of consecutive failures for readiness probes | 30 | +| hostPath | Host path for the application | "" | + +### StreamPipes common parameters + +| Parameter Name | Description | Value | +|-----------------------------------------------|---------------------------------------------------------|------------------------------------------| +| streampipes.version | StreamPipes version | "0.93.0-SNAPSHOT" | +| streampipes.registry | StreamPipes registry URL | "apachestreampipes" | +| streampipes.auth.secretName | The secret name for storing secrets | "sp-secrets" | +| streampipes.auth.users.admin.user | The initial admin user | "admin@streampipes.apache.org" | +| streampipes.auth.users.admin.password | The initial admin password (leave empty for autogen) | "admin" | +| streampipes.auth.users.service.user | The initial service account user | "sp-service-client" | +| streampipes.auth.users.service.secret | The initial service account secret | empty (auto-generated) | +| streampipes.auth.encryption.passcode | Passcode for value encryption | empty (auto-generated) | +| streampipes.core.appName | StreamPipes backend application name | "backend" | +| streampipes.core.port | StreamPipes backend port | 8030 | +| streampipes.core.persistence.storageClassName | Storage class name for backend PVs | "hostpath" | +| streampipes.core.persistence.storageSize | Size of the backend PV | "1Gi" | +| streampipes.core.persistence.claimName | Name of the backend PersistentVolumeClaim | "backend-pvc" | +| streampipes.core.persistence.pvName | Name of the backend PersistentVolume | "backend-pv" | +| streampipes.core.service.name | Name of the backend service | "backend" | +| streampipes.core.service.port | TargetPort of the StreamPipes backend service | 8030 | +| streampipes.ui.appName | StreamPipes UI application name | "ui" | +| streampipes.ui.resolverActive | Flag for enabling DNS resolver for Nginx proxy | true | +| streampipes.ui.port | StreamPipes UI port | 8088 | +| streampipes.ui.resolver | DNS resolver for Nginx proxy | "kube-dns.kube-system.svc.cluster.local" | +| streampipes.ui.service.name | Name of the UI service | "ui" | +| streampipes.ui.service.type | Type of the UI service | "ClusterIP" | +| streampipes.ui.service.nodePort | Node port for the UI service | 8088 | +| streampipes.ui.service.port | TargetPort of the StreamPipes UI service | 8088 | +| streampipes.ingress.active | Flag for enabling Ingress for StreamPipes | false | +| streampipes.ingress.annotations | Annotations for Ingress | {} | +| streampipes.ingress.host | Hostname for Ingress | "" | +| streampipes.ingressroute.active | Flag for enabling IngressRoute for StreamPipes | true | +| streampipes.ingressroute.annotations | Annotations for IngressRoute | {} | +| streampipes.ingressroute.entryPoints | Entry points for IngressRoute | ["web", "websecure"] | +| streampipes.ingressroute.host | Hostname for IngressRoute | "" | +| streampipes.ingressroute.certResolverActive | Flag for enabling certificate resolver for IngressRoute | true | +| streampipes.ingressroute.certResolver | Certificate resolver for IngressRoute | "" | + +### Extensions common parameters + +| Parameter Name | Description | Value | +|------------------------------|-------------------------------------------|---------------------| +| extensions.iiot.appName | IIoT extensions application name | extensions-all-iiot | +| extensions.iiot.port | Port for the IIoT extensions application | 8090 | +| extensions.iiot.service.name | Name of the IIoT extensions service | extensions-all-iiot | +| extensions.iiot.service.port | TargetPort of the IIoT extensions service | 8090 | + +### External common parameters + +#### Consul common parameters + +| Parameter Name | Description | Value | +|----------------------------------------------|----------------------------------------------------|--------------| +| external.consul.appName | Consul application name | "consul" | +| external.consul.version | Consul version | 1.14.3 | +| external.consul.webPort | Port number for the Consul web interface | 8500 | +| external.consul.dnsPort | Port number for the Consul DNS interface | 8600 | +| external.consul.persistence.storageClassName | Storage class name for Consul PVs | "hostpath" | +| external.consul.persistence.storageSize | Size of the Consul PV | "1Gi" | +| external.consul.persistence.claimName | Name of the Consul PersistentVolumeClaim | "consul-pvc" | +| external.consul.persistence.pvName | Name of the Consul PersistentVolume | "consul-pv" | +| external.consul.service.name | Name of the Consul service | "consul" | +| external.consul.service.webPort | TargetPort of the Consul service for web interface | 8500 | +| external.consul.service.dnsPort | TargetPort of the Consul service for DNS interface | 8600 | + +#### Couchdb common parameters + +| Parameter Name | Description | Value | +|-----------------------------------------------|-------------------------------------------|------------------------| +| external.couchdb.appName | CouchDB application name | "couchdb" | +| external.couchdb.version | CouchDB version | 3.3.1 | +| external.couchdb.user | CouchDB admin username | "admin" | +| external.couchdb.password | CouchDB admin password | empty (auto-generated) | +| external.couchdb.port | Port for the CouchDB service | 5984 | +| external.couchdb.service.name | Name of the CouchDB service | "couchdb" | +| external.couchdb.service.port | TargetPort of the CouchDB service | 5984 | +| external.couchdb.persistence.storageClassName | Storage class name for CouchDB PVs | "hostpath" | +| external.couchdb.persistence.storageSize | Size of the CouchDB PV | "1Gi" | +| external.couchdb.persistence.claimName | Name of the CouchDB PersistentVolumeClaim | "couchdb-pvc" | +| external.couchdb.persistence.pvName | Name of the CouchDB PersistentVolume | "couchdb-pv" | + +#### Influxdb common parameters + +| Parameter Name | Description | Value | +|------------------------------------------------|----------------------------------------------|------------------------| +| external.influxdb.appName | InfluxDB application name | "influxdb" | +| external.influxdb.version | InfluxDB version | 2.6 | +| external.influxdb.username | InfluxDB admin username | "admin" | +| external.influxdb.password | InfluxDB admin password | empty (auto-generated) | +| external.influxdb.adminToken | InfluxDB admin token | empty (auto-generated) | +| external.influxdb.initOrg | InfluxDB initial organization | "sp" | +| external.influxdb.initBucket | InfluxDB initial bucket | "sp" | +| external.influxdb.initMode | InfluxDB initialization mode | "setup" | +| external.influxdb.apiPort | Port number for the InfluxDB service (API) | 8083 | +| external.influxdb.httpPort | Port number for the InfluxDB service (HTTP) | 8086 | +| external.influxdb.grpcPort | Port number for the InfluxDB service (gRPC) | 8090 | +| external.influxdb.service.name | Name of the InfluxDB service | "influxdb" | +| external.influxdb.service.apiPort | TargetPort of the InfluxDB service for API | 8083 | +| external.influxdb.service.httpPort | TargetPort of the InfluxDB service for HTTP | 8086 | +| external.influxdb.service.grpcPort | TargetPort of the InfluxDB service for gRPC | 8090 | +| external.influxdb.persistence.storageClassName | Storage class name for InfluxDB PVs | "hostpath" | +| external.influxdb.persistence.storageSize | Size of the InfluxDB PV | "1Gi" | +| external.influxdb.persistence.storageSizeV1 | Size of the InfluxDB PV for v1 databases | "1Gi" | +| external.influxdb.persistence.claimName | Name of the InfluxDBv2 PersistentVolumeClaim | "influxdb2-pvc" | +| external.influxdb.persistence.claimNameV1 | Name of the InfluxDBv1 PersistentVolumeClaim | "influxdb-pvc" | +| external.influxdb.persistence.pvName | Name of the InfluxDBv2 PersistentVolume | "influxdb2-pv" | +| external.influxdb.persistence.pvNameV1 | Name of the InfluxDBv1 PersistentVolume | "influxdb-pv" | + +#### Nats common parameters + +| Parameter Name | Description | Value | +|---------------------------------------------|----------------------------------------------|------------| +| external.nats.appName | NATS application name | "nats" | +| external.nats.port | Port for the NATS service | 4222 | +| external.nats.version | NATS version | | +| external.nats.service.type | Type of the NATS service | "NodePort" | +| external.nats.service.externalTrafficPolicy | External traffic policy for the NATS service | "Local" | +| external.nats.service.name | Name of the NATS service | "nats" | +| external.nats.service.port | TargetPort of the NATS service | 4222 | + +#### Kafka common parameters + +| Parameter Name | Description | Value | +|---------------------------------------------|------------------------------------------------------------------------------------------|-------------| +| external.kafka.appName | Kafka application name | "kafka" | +| external.kafka.version | Kafka version | 2.2.0 | +| external.kafka.port | Port for the Kafka service | 9092 | +| external.kafka.external.hostname | Name which will be advertised to external clients. Clients which use (default) port 9094 | "localhost" | +| external.kafka.service.name | Name of the Kafka service | "kafka" | +| external.kafka.service.port | TargetPort of the Kafka service | 9092 | +| external.kafka.service.portOutside | Port for Kafka client outside of the cluster | 9094 | +| external.kafka.persistence.storageClassName | Storage class name for Kafka PVs | "hostpath" | +| external.kafka.persistence.storageSize | Size of the Kafka PV | "1Gi" | +| external.kafka.persistence.claimName | Name of the Kafka PersistentVolumeClaim | "kafka-pvc" | +| external.kafka.persistence.pvName | Name of the Kafka PersistentVolume | "kafka-pv" | | -####Zookeeper common parameters -| Parameter Name | Description | Value | -|-------------------------------------------------|----------------------------------------------------------|------------------------------------------| -| external.zookeeper.appName | ZooKeeper application name | "zookeeper" | -| external.zookeeper.version | ZooKeeper version | 3.4.13 | -| external.zookeeper.port | Port for the ZooKeeper service | 2181 | -| external.zookeeper.service.name | Name of the ZooKeeper service | "zookeeper" | -| external.zookeeper.service.port | TargetPort of the ZooKeeper service | 2181 | -| external.zookeeper.persistence.storageClassName | Storage class name for ZooKeeper PVs | "hostpath" | -| external.zookeeper.persistence.storageSize | Size of the ZooKeeper PV | "1Gi" | -| external.zookeeper.persistence.claimName | Name of the ZooKeeper PersistentVolumeClaim | "zookeeper-pvc" | -| external.zookeeper.persistence.pvName | Name of the ZooKeeper PersistentVolume | "zookeeper-pv" | - - -####Pulsar common parameters -| Parameter Name | Description | Value | -|-------------------------------------------------|----------------------------------------------------------|------------------------------------------| -| external.pulsar.appName | pulsar application name | "pulsar" | -| external.pulsar.version | pulsar version | 3.0.0 | -| external.pulsar.port | Port for the pulsar service | 6650 | -| external.pulsar.service.name | Name of the pulsar service | "pulsar" | -| external.pulsar.service.port | TargetPort of the pulsar service | 6650 | -| external.pulsar.persistence.storageClassName | Storage class name for pulsar PVs | "hostpath" | -| external.pulsar.persistence.storageSize | Size of the pulsar PV | "1Gi" | -| external.pulsar.persistence.claimName | Name of the pulsar PersistentVolumeClaim | "pulsar-pvc" | -| external.pulsar.persistence.pvName | Name of the pulsar PersistentVolume | "pulsar-pv" | - -###Monitoring common parameters +#### Zookeeper common parameters + +| Parameter Name | Description | Value | +|-------------------------------------------------|---------------------------------------------|-----------------| +| external.zookeeper.appName | ZooKeeper application name | "zookeeper" | +| external.zookeeper.version | ZooKeeper version | 3.4.13 | +| external.zookeeper.port | Port for the ZooKeeper service | 2181 | +| external.zookeeper.service.name | Name of the ZooKeeper service | "zookeeper" | +| external.zookeeper.service.port | TargetPort of the ZooKeeper service | 2181 | +| external.zookeeper.persistence.storageClassName | Storage class name for ZooKeeper PVs | "hostpath" | +| external.zookeeper.persistence.storageSize | Size of the ZooKeeper PV | "1Gi" | +| external.zookeeper.persistence.claimName | Name of the ZooKeeper PersistentVolumeClaim | "zookeeper-pvc" | +| external.zookeeper.persistence.pvName | Name of the ZooKeeper PersistentVolume | "zookeeper-pv" | + +#### Pulsar common parameters + +| Parameter Name | Description | Value | +|----------------------------------------------|------------------------------------------|--------------| +| external.pulsar.appName | pulsar application name | "pulsar" | +| external.pulsar.version | pulsar version | 3.0.0 | +| external.pulsar.port | Port for the pulsar service | 6650 | +| external.pulsar.service.name | Name of the pulsar service | "pulsar" | +| external.pulsar.service.port | TargetPort of the pulsar service | 6650 | +| external.pulsar.persistence.storageClassName | Storage class name for pulsar PVs | "hostpath" | +| external.pulsar.persistence.storageSize | Size of the pulsar PV | "1Gi" | +| external.pulsar.persistence.claimName | Name of the pulsar PersistentVolumeClaim | "pulsar-pvc" | +| external.pulsar.persistence.pvName | Name of the pulsar PersistentVolume | "pulsar-pv" | + +### Monitoring common parameters #### Monitoring - Prometheus -| Parameter Name | Description | Value | -|-------------------------------------------------|----------------------------------------------------------|------------------------------------------| -| prometheus.appName | Prometheus application name | "prometheus" | -| prometheus.version | Prometheus version | 2.45.0 | -| prometheus.port | Prometheus port | 9090 | -| prometheus.service.name | Prometheus service name | "prometheus" | -| prometheus.service.port | Prometheus service port | 9090 | -| prometheus.persistence.storageClassName | Prometheus storage class name | "hostpath" | -| prometheus.persistence.storageSize | Prometheus storage size | "2Gi" | -| prometheus.persistence.claimName | Prometheus PVC claim name | "prometheus-pvc" | -| prometheus.persistence.pvName | Prometheus PV name | "prometheus-pv" | -| prometheus.persistence.tokenStorageSize | Prometheus token storage size | "16Ki" | -| prometheus.config.scrapeInterval | Prometheus scrape interval | 10s | -| prometheus.config.evaluationInterval | Prometheus evaluation interval | 15s | -| prometheus.config.backendJobName | Prometheus backend job name | "backend" | -| prometheus.config.extensionsName | Prometheus extensions job name | "extensions-all-iiot" | -| prometheus.config.tokenFileName | Prometheus token file name | "token" | -| prometheus.config.tokenFileDir | Prometheus token file directory | "/opt/data" + +| Parameter Name | Description | Value | +|-----------------------------------------|---------------------------------|-----------------------| +| prometheus.appName | Prometheus application name | "prometheus" | +| prometheus.version | Prometheus version | 2.45.0 | +| prometheus.port | Prometheus port | 9090 | +| prometheus.service.name | Prometheus service name | "prometheus" | +| prometheus.service.port | Prometheus service port | 9090 | +| prometheus.persistence.storageClassName | Prometheus storage class name | "hostpath" | +| prometheus.persistence.storageSize | Prometheus storage size | "2Gi" | +| prometheus.persistence.claimName | Prometheus PVC claim name | "prometheus-pvc" | +| prometheus.persistence.pvName | Prometheus PV name | "prometheus-pv" | +| prometheus.persistence.tokenStorageSize | Prometheus token storage size | "16Ki" | +| prometheus.config.scrapeInterval | Prometheus scrape interval | 10s | +| prometheus.config.evaluationInterval | Prometheus evaluation interval | 15s | +| prometheus.config.backendJobName | Prometheus backend job name | "backend" | +| prometheus.config.extensionsName | Prometheus extensions job name | "extensions-all-iiot" | +| prometheus.config.tokenFileName | Prometheus token file name | "token" | +| prometheus.config.tokenFileDir | Prometheus token file directory | "/opt/data" | #### Monitoring - Grafana -| Parameter Name | Description | Value | -|-------------------------------------------------|----------------------------------------------------------|------------------------------------------| -| grafana.appName | Grafana application name | "grafana" | -| grafana.version | Grafana version | 10.1.2 | -| grafana.port | Grafana port | 3000 | -| grafana.service.name | Grafana service name | "grafana" | -| grafana.service.port | Grafana service port | 3000 | -| grafana.persistence.storageClassName | Grafana storage class name | "hostpath" | -| grafana.persistence.storageSize | Grafana storage size | "1Gi" | -| grafana.persistence.claimName | Grafana PVC claim name | "grafana-pvc" | -| grafana.persistence.pvName | Grafana PV name | "grafana-pv" | + +| Parameter Name | Description | Value | +|--------------------------------------|----------------------------|---------------| +| grafana.appName | Grafana application name | "grafana" | +| grafana.version | Grafana version | 10.1.2 | +| grafana.port | Grafana port | 3000 | +| grafana.service.name | Grafana service name | "grafana" | +| grafana.service.port | Grafana service port | 3000 | +| grafana.persistence.storageClassName | Grafana storage class name | "hostpath" | +| grafana.persistence.storageSize | Grafana storage size | "1Gi" | +| grafana.persistence.claimName | Grafana PVC claim name | "grafana-pvc" | +| grafana.persistence.pvName | Grafana PV name | "grafana-pv" | ## Bugs and Feature Requests -If you've found a bug or have a feature that you'd love to see in StreamPipes, feel free to create an issue on [GitHub](https://github.com/apache/streampipes/issues). +If you've found a bug or have a feature that you'd love to see in StreamPipes, feel free to create an issue +on [GitHub](https://github.com/apache/streampipes/issues). ## Get help -If you have any problems during the installation or questions around StreamPipes, you'll get help through one of our community channels: + +If you have any problems during the installation or questions around StreamPipes, you'll get help through one of our +community channels: - [Slack](https://slack.streampipes.org) - [Mailing Lists](https://streampipes.apache.org/mailinglists.html) @@ -308,20 +334,27 @@ If you have any problems during the installation or questions around StreamPipes And don't forget to follow us on [Twitter](https://twitter.com/streampipes)! ## Contribute + We welcome contributions to StreamPipes. If you are interested in contributing to StreamPipes, let us know! You'll - get to know an open-minded and motivated team working together to build the next IIoT analytics toolbox. +get to know an open-minded and motivated team working together to build the next IIoT analytics toolbox. Here are some first steps in case you want to contribute: + * Subscribe to our dev mailing list [dev-subscribe@streampipes.apache.org](dev-subscribe@streampipes.apache.org) -* Send an email, tell us about your interests and which parts of StreamPipes you'd like to contribute (e.g., core or UI)! +* Send an email, tell us about your interests and which parts of StreamPipes you'd like to contribute (e.g., core or + UI)! * Ask for a mentor who helps you to understand the code base and guides you through the first setup steps -* Find an issue on [GitHub](https://github.com/apache/streampipes/issues) which is tagged with a _good first issue_ tag -* Have a look at our developer wiki at [https://cwiki.apache.org/confluence/display/STREAMPIPES](https://cwiki.apache.org/confluence/display/STREAMPIPES) to learn more about StreamPipes development. +* Find an issue on [GitHub](https://github.com/apache/streampipes/issues) which is tagged with a _good first issue_ tag +* Have a look at our developer wiki + at [https://cwiki.apache.org/confluence/display/STREAMPIPES](https://cwiki.apache.org/confluence/display/STREAMPIPES) + to learn more about StreamPipes development. Have fun! ## Feedback + We'd love to hear your feedback! Subscribe to [users@streampipes.apache.org](mailto:users@streampipes.apache.org) ## License + [Apache License 2.0](../LICENSE) diff --git a/prometheus-grafana/README.md b/prometheus-grafana/README.md index b0e2a0aba7..a94c751394 100644 --- a/prometheus-grafana/README.md +++ b/prometheus-grafana/README.md @@ -17,7 +17,7 @@ --> -##Prometheus Configuration +## Prometheus Configuration In the [dashboards](./prometheus) directory, you can find sample configuration file for Prometheus. ## Grafana Dashboards @@ -25,5 +25,5 @@ In the [dashboards](./prometheus) directory, you can find sample configuration f In the [dashboards](./grafana/dashboards) directory, you can find sample grafana dashboards for several StreamPipes components. -##note +## note The metrics displayed in the [dashboards](. /grafana/dashboards) directory dashboards show metrics that have a filter condition related to the job_name, if you change the job_name in the example you must also change the condition in the grafana dashboard. \ No newline at end of file From 276299730301e410501220aff52e7da65a335c9c Mon Sep 17 00:00:00 2001 From: Muyang Ye Date: Sun, 26 Nov 2023 12:55:07 -0800 Subject: [PATCH 15/20] #2151 & #2223: Implement E2E tests for enhanced adapter deletion and Extend reset REST endpoint to remove users (#2197) * implement unit tests for enhanced adapter deletion * better structure * fix * delete other users in reset * change comment --- .../streampipes/rest/impl/ResetResource.java | 17 +++- ui/cypress/support/utils/UserUtils.ts | 17 +++- .../support/utils/connect/ConnectUtils.ts | 73 +++++++++++++- .../enhancedDeleteAdapter.smoke.spec.ts | 95 +++++++++++++++++++ .../delete-adapter-dialog.component.html | 1 + 5 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 ui/cypress/tests/adapter/enhancedDeleteAdapter.smoke.spec.ts diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ResetResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ResetResource.java index b3f7b6719e..3bcbd6a17b 100644 --- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ResetResource.java +++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/ResetResource.java @@ -18,8 +18,9 @@ package org.apache.streampipes.rest.impl; +import org.apache.streampipes.model.client.user.Principal; +import org.apache.streampipes.model.client.user.PrincipalType; import org.apache.streampipes.model.message.Notifications; -import org.apache.streampipes.model.message.SuccessMessage; import org.apache.streampipes.rest.ResetManagement; import org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource; import org.apache.streampipes.rest.shared.annotation.JacksonSerialized; @@ -34,6 +35,8 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import java.util.ArrayList; + @Path("/v2/reset") public class ResetResource extends AbstractAuthGuardedRestResource { private static final Logger logger = LoggerFactory.getLogger(ResetResource.class); @@ -44,7 +47,17 @@ public class ResetResource extends AbstractAuthGuardedRestResource { @Operation(summary = "Resets StreamPipes instance") public Response reset() { ResetManagement.reset(getAuthenticatedUsername()); - SuccessMessage message = Notifications.success("Reset of system successfully performed"); + var userStorage = getUserStorage(); + // Delete all users other than current user (admin) and their resources + var allUsers = new ArrayList(userStorage.getAllUsers()); + for (var user : allUsers) { + if (user.getPrincipalType() == PrincipalType.USER_ACCOUNT + && !user.getPrincipalId().equals(getAuthenticatedUserSid())) { + ResetManagement.reset(user.getUsername()); + userStorage.deleteUser(user.getPrincipalId()); + } + } + var message = Notifications.success("Reset of system successfully performed"); return ok(message); } diff --git a/ui/cypress/support/utils/UserUtils.ts b/ui/cypress/support/utils/UserUtils.ts index 60e735e597..fb505aa81a 100644 --- a/ui/cypress/support/utils/UserUtils.ts +++ b/ui/cypress/support/utils/UserUtils.ts @@ -27,6 +27,15 @@ export class UserUtils { .addRole(UserRole.ROLE_ADMIN) .build(); + public static adapterAndPipelineAdminUser = UserBuilder.create( + 'anpadmin@streampipes.apache.org', + ) + .setName('anpadmin') + .setPassword('anpadmin') + .addRole(UserRole.ROLE_PIPELINE_ADMIN) + .addRole(UserRole.ROLE_CONNECT_ADMIN) + .build(); + public static goToUserConfiguration() { cy.visit('#/configuration/security'); } @@ -42,9 +51,11 @@ export class UserUtils { cy.dataCy('new-user-password-repeat').type(user.password); // Set role - cy.dataCy('role-' + user.role[0]) - .children() - .click(); + for (var i = 0; i < user.role.length; i++) { + cy.dataCy('role-' + user.role[i]) + .children() + .click(); + } cy.dataCy('new-user-enabled').children().click(); // Store diff --git a/ui/cypress/support/utils/connect/ConnectUtils.ts b/ui/cypress/support/utils/connect/ConnectUtils.ts index b6a3f28e80..22c57d853b 100644 --- a/ui/cypress/support/utils/connect/ConnectUtils.ts +++ b/ui/cypress/support/utils/connect/ConnectUtils.ts @@ -22,6 +22,8 @@ import { ConnectEventSchemaUtils } from '../ConnectEventSchemaUtils'; import { DataLakeUtils } from '../datalake/DataLakeUtils'; import { ConnectBtns } from './ConnectBtns'; import { AdapterBuilder } from '../../builder/AdapterBuilder'; +import { UserUtils } from '../UserUtils'; +import { PipelineUtils } from '../PipelineUtils'; export class ConnectUtils { public static testAdapter(adapterConfiguration: AdapterInput) { @@ -193,11 +195,10 @@ export class ConnectUtils { public static deleteAdapter() { // Delete adapter - cy.visit('#/connect'); + this.goToConnect(); cy.dataCy('delete-adapter').should('have.length', 1); - cy.dataCy('delete-adapter').click(); - cy.dataCy('delete-adapter-confirmation').click(); + this.clickDelete(); cy.dataCy('adapter-deletion-in-progress', { timeout: 10000 }).should( 'be.visible', ); @@ -207,6 +208,72 @@ export class ConnectUtils { ); } + public static deleteAdapterAndAssociatedPipelines(switchUserCheck = false) { + // Delete adapter and associated pipelines + this.goToConnect(); + cy.dataCy('delete-adapter').should('have.length', 1); + this.clickDelete(); + cy.dataCy('delete-adapter-and-associated-pipelines-confirmation', { + timeout: 10000, + }).should('be.visible'); + cy.dataCy( + 'delete-adapter-and-associated-pipelines-confirmation', + ).click(); + cy.dataCy('adapter-deletion-in-progress', { timeout: 10000 }).should( + 'be.visible', + ); + if (switchUserCheck) { + cy.switchUser(UserUtils.adapterAndPipelineAdminUser); + } + this.checkAdapterAndAssociatedPipelinesDeleted(); + } + + // NOTE: this function will leave the adapter and associated pipelines running, + // please make sure to clean up after calling this function + public static deleteAdapterAndAssociatedPipelinesPermissionDenied() { + // Associated pipelines not owned by the user (unless admin) should not be deleted during adapter deletion + this.goToConnect(); + cy.dataCy('delete-adapter').should('have.length', 1); + this.clickDelete(); + cy.dataCy('delete-adapter-and-associated-pipelines-confirmation', { + timeout: 10000, + }).should('be.visible'); + cy.dataCy( + 'delete-adapter-and-associated-pipelines-confirmation', + ).click(); + cy.dataCy('adapter-deletion-permission-denied', { + timeout: 10000, + }).should('be.visible'); + cy.get('.sp-dialog-actions').click(); + this.checkAdapterNotDeleted(); + } + + public static clickDelete() { + cy.dataCy('delete-adapter').click(); + cy.dataCy('delete-adapter-confirmation').click(); + } + + public static checkAdapterNotDeleted() { + this.goToConnect(); + cy.dataCy('delete-adapter', { timeout: 20000 }).should( + 'have.length', + 1, + ); + } + + public static checkAdapterAndAssociatedPipelinesDeleted() { + this.goToConnect(); + cy.dataCy('delete-adapter', { timeout: 20000 }).should( + 'have.length', + 0, + ); + PipelineUtils.goToPipelines(); + cy.dataCy('delete-pipeline', { timeout: 10000 }).should( + 'have.length', + 0, + ); + } + public static setUpPreprocessingRuleTest(): AdapterInput { const adapterConfiguration = AdapterBuilder.create('File_Stream') .setStoreInDataLake() diff --git a/ui/cypress/tests/adapter/enhancedDeleteAdapter.smoke.spec.ts b/ui/cypress/tests/adapter/enhancedDeleteAdapter.smoke.spec.ts new file mode 100644 index 0000000000..6bb2cc3c59 --- /dev/null +++ b/ui/cypress/tests/adapter/enhancedDeleteAdapter.smoke.spec.ts @@ -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. + * + */ + +import { AdapterBuilder } from '../../support/builder/AdapterBuilder'; +import { ConnectUtils } from '../../support/utils/connect/ConnectUtils'; +import { PipelineBuilder } from '../../support/builder/PipelineBuilder'; +import { PipelineElementBuilder } from '../../support/builder/PipelineElementBuilder'; +import { UserUtils } from '../../support/utils/UserUtils'; +import { PipelineUtils } from '../../support/utils/PipelineUtils'; + +const adapterName = 'simulator'; + +const adapterInput = AdapterBuilder.create('Machine_Data_Simulator') + .setName(adapterName) + .addInput('input', 'wait-time-ms', '1000') + .build(); + +const pipelineInput = PipelineBuilder.create('Pipeline Test') + .addSource(adapterName) + .addProcessingElement( + PipelineElementBuilder.create('field_renamer') + .addInput('drop-down', 'convert-property', 'timestamp') + .addInput('input', 'field-name', 't') + .build(), + ) + .addSink( + PipelineElementBuilder.create('data_lake') + .addInput('input', 'db_measurement', 'demo') + .build(), + ) + .build(); + +describe('Test Enhanced Adapter Deletion', () => { + // In the beginning, create the non-admin user + before(() => { + cy.initStreamPipesTest(); + UserUtils.addUser(UserUtils.adapterAndPipelineAdminUser); + cy.logout(); + }); + + beforeEach('Setup Test', () => { + cy.visit('#/login'); + cy.dataCy('login-email').type( + UserUtils.adapterAndPipelineAdminUser.email, + ); + cy.dataCy('login-password').type( + UserUtils.adapterAndPipelineAdminUser.password, + ); + cy.dataCy('login-button').click(); + cy.wait(1000); + }); + + it('Test Delete Adapter and Associated Pipelines', () => { + ConnectUtils.testAdapter(adapterInput); + PipelineUtils.addPipeline(pipelineInput); + PipelineUtils.addPipeline(pipelineInput); + ConnectUtils.deleteAdapterAndAssociatedPipelines(); + }); + + it('Test Admin Should Be Able to Delete Adapter and Not Owned Associated Pipelines', () => { + // Let the user create the adapter and the pipeline + ConnectUtils.testAdapter(adapterInput); + PipelineUtils.addPipeline(pipelineInput); + PipelineUtils.addPipeline(pipelineInput); + // Then let the admin delete them + cy.switchUser(UserUtils.adminUser); + ConnectUtils.deleteAdapterAndAssociatedPipelines(true); + }); + + it('Test Delete Adapter and Associated Pipelines Permission Denied', () => { + // Let the admin create the adapter and the pipeline + cy.switchUser(UserUtils.adminUser); + ConnectUtils.testAdapter(adapterInput); + PipelineUtils.addPipeline(pipelineInput); + PipelineUtils.addPipeline(pipelineInput); + // Then the user shouldn't be able to delete them + cy.switchUser(UserUtils.adapterAndPipelineAdminUser); + ConnectUtils.deleteAdapterAndAssociatedPipelinesPermissionDenied(); + }); +}); diff --git a/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html b/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html index c5a73031b4..0f08cec5c4 100644 --- a/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html +++ b/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html @@ -67,6 +67,7 @@

fxLayoutAlign="center center" fxLayout="column" style="width: 100%" + data-cy="adapter-deletion-permission-denied" >

From 3ba4f777594666fd23b0bac72a2c238e97b2e300 Mon Sep 17 00:00:00 2001 From: Isaak Krut Date: Mon, 27 Nov 2023 05:22:09 -0500 Subject: [PATCH 16/20] feat(#2192): add download button in asset management overview (#2224) * Added Asset Download button * Updated to use FileSaver --- .../tests/assetManagement/createAsset.spec.ts | 27 +++++++++++++++++++ .../asset-overview.component.html | 12 +++++++++ .../asset-overview.component.ts | 18 +++++++++++++ 3 files changed, 57 insertions(+) diff --git a/ui/cypress/tests/assetManagement/createAsset.spec.ts b/ui/cypress/tests/assetManagement/createAsset.spec.ts index 1ff47dfb32..fb5c529013 100644 --- a/ui/cypress/tests/assetManagement/createAsset.spec.ts +++ b/ui/cypress/tests/assetManagement/createAsset.spec.ts @@ -89,9 +89,36 @@ describe('Creates a new adapter, add to assets and export assets', () => { cy.dataCy('import-button').click(); // Check if import was successful + cy.visit('#/connect'); + cy.dataCy('adapters-table').children().should('have.length', 1); cy.visit('#/assets'); cy.dataCy('assets-table').should('have.length', 1); + + // Export Asset via Assets page + cy.dataCy('download').click(); + + // Delete Adapter and Asset + cy.visit('#/connect'); + cy.dataCy('delete-adapter').click(); + cy.dataCy('delete-adapter-confirmation').click(); + + cy.visit('#/assets'); + cy.dataCy('delete').click(); + + // Import downloaded Asset + cy.visit('#/configuration/export'); + cy.dataCy('import-application-data-button').click(); + cy.get('input[type="file"]').selectFile( + 'cypress/downloads/assetExport.zip', + { force: true }, + ); + cy.dataCy('next-import-button').click(); + cy.dataCy('import-button').click(); + + // Check if import was successful cy.visit('#/connect'); cy.dataCy('adapters-table').children().should('have.length', 1); + cy.visit('#/assets'); + cy.dataCy('assets-table').should('have.length', 1); }); }); diff --git a/ui/src/app/assets/components/asset-overview/asset-overview.component.html b/ui/src/app/assets/components/asset-overview/asset-overview.component.html index 8e73c95bd8..d08ce9d0e8 100644 --- a/ui/src/app/assets/components/asset-overview/asset-overview.component.html +++ b/ui/src/app/assets/components/asset-overview/asset-overview.component.html @@ -198,6 +198,18 @@
>delete + diff --git a/ui/src/app/assets/components/asset-overview/asset-overview.component.ts b/ui/src/app/assets/components/asset-overview/asset-overview.component.ts index cf942f1951..31c8ddf0ba 100644 --- a/ui/src/app/assets/components/asset-overview/asset-overview.component.ts +++ b/ui/src/app/assets/components/asset-overview/asset-overview.component.ts @@ -32,6 +32,9 @@ import { SpAssetRoutes } from '../../assets.routes'; import { AssetUploadDialogComponent } from '../../dialog/asset-upload/asset-upload-dialog.component'; import { Router } from '@angular/router'; import { SpCreateAssetDialogComponent } from '../../dialog/create-asset/create-asset-dialog.component'; +import { DataExportService } from '../../../configuration/export/data-export.service'; +import { mergeMap } from 'rxjs/operators'; +import { saveAs } from 'file-saver'; @Component({ selector: 'sp-asset-overview-component', @@ -50,6 +53,7 @@ export class SpAssetOverviewComponent implements OnInit { private breadcrumbService: SpBreadcrumbService, private dialogService: DialogService, private router: Router, + private dataExportService: DataExportService, ) {} ngOnInit(): void { @@ -147,4 +151,18 @@ export class SpAssetOverviewComponent implements OnInit { this.loadAssets(); }); } + + downloadAsset(asset: SpAssetModel) { + this.dataExportService + .getExportPreview([asset._id]) + .pipe( + mergeMap(preview => + this.dataExportService.triggerExport(preview), + ), + ) + .subscribe((data: Blob) => { + const blob = new Blob([data], { type: 'application/zip' }); + saveAs(blob, 'assetExport'); + }); + } } From 4b019a8f8b63ada7bbe525df211c43b5c1861093 Mon Sep 17 00:00:00 2001 From: Marcelfrueh <78954450+Marcelfrueh@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:25:04 +0100 Subject: [PATCH 17/20] Add new test for widgets (#2226) --- ui/cypress/support/utils/DashboardUtils.ts | 55 ++++++++++++- .../tests/dashboard/dashboardWidgets.spec.ts | 81 +++++++++++++++++++ .../widget/dashboard-widget.component.html | 7 +- 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 ui/cypress/tests/dashboard/dashboardWidgets.spec.ts diff --git a/ui/cypress/support/utils/DashboardUtils.ts b/ui/cypress/support/utils/DashboardUtils.ts index 04a72a75dc..46268b4173 100644 --- a/ui/cypress/support/utils/DashboardUtils.ts +++ b/ui/cypress/support/utils/DashboardUtils.ts @@ -43,7 +43,11 @@ export class DashboardUtils { cy.dataCy('edit-dashboard-' + dashboardName).click(); } - public static addWidget(pipelineName: string, widgetType: string) { + public static addWidget( + pipelineName: string, + widgetType: string, + options?, + ) { // Add raw data widget cy.dataCy('dashboard-add-widget').click(); @@ -52,11 +56,27 @@ export class DashboardUtils { // Select widget cy.dataCy('dashboard-select-widget-' + widgetType).click(); + + if (widgetType == 'area' || widgetType == 'line') { + cy.dataCy('min-y-axis-key').type(options.minYaxis); + cy.dataCy('max-y-axis-key').type(options.maxYaxis); + } else if (widgetType == 'gauge') { + cy.dataCy('min-key').type(options.minYaxis); + cy.dataCy('max-key').type(options.maxYaxis); + } else if (widgetType == 'status') { + cy.dataCy('interval-key').type(options.intervalKey); + } else if (widgetType == 'trafficlight') { + cy.dataCy('critical-value-key').type(options.criticalValue); + cy.dataCy('warning-range').type(options.warningRange); + } + // optional configure widget cy.dataCy('dashboard-new-widget-next-btn').click(); // Finish edit mode cy.dataCy('dashboard-save-edit-mode').click(); + + cy.wait(1000); } public static validateRawWidgetEvents(amountOfEvents: number) { @@ -64,4 +84,37 @@ export class DashboardUtils { .its('length') .should('be.gte', amountOfEvents); } + + public static removeWidgetFromDashboard(dashboardName: string) { + cy.visit('#/dashboard'); + cy.dataCy('edit-dashboard-' + dashboardName).click(); + cy.dataCy('widget-remove-button').click(); + } + + public static testWidget( + widgetType: string, + dashboardName: string, + options?: { + minYaxis?: string; + maxYaxis?: string; + intervalKey?: string; + criticalValue?: string; + warningRange?: string; + }, + ) { + DashboardUtils.addWidget('Persist_simulator', widgetType, options); + if (widgetType == 'status') { + cy.get(`sp-dashboard-${widgetType.toLowerCase()}-widget`).should( + 'be.visible', + ); + } else if (widgetType == 'trafficlight') { + cy.get(`sp-traffic-light-widget`).should('be.visible'); + } else { + cy.get(`sp-${widgetType.toLowerCase()}-widget`).should( + 'be.visible', + ); + } + + DashboardUtils.removeWidgetFromDashboard(dashboardName); + } } diff --git a/ui/cypress/tests/dashboard/dashboardWidgets.spec.ts b/ui/cypress/tests/dashboard/dashboardWidgets.spec.ts new file mode 100644 index 0000000000..2ef797590c --- /dev/null +++ b/ui/cypress/tests/dashboard/dashboardWidgets.spec.ts @@ -0,0 +1,81 @@ +/* + * 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 { ConnectUtils } from '../../support/utils/connect/ConnectUtils'; +import { DashboardUtils } from '../../support/utils/DashboardUtils'; + +describe('Test All Widgets', () => { + const dashboardName = 'testDashboard'; + + beforeEach('Setup Test', () => { + cy.initStreamPipesTest(); + ConnectUtils.addMachineDataSimulator('simulator', true); + DashboardUtils.goToDashboard(); + }); + + it('Perform Test', () => { + DashboardUtils.addAndEditDashboard(dashboardName); + + // Test area chart + DashboardUtils.testWidget('area', dashboardName, { + minYaxis: '0', + maxYaxis: '50', + }); + + // Test bar race chart + DashboardUtils.testWidget('bar-race', dashboardName); + + // Test gauge widget + DashboardUtils.testWidget('gauge', dashboardName, { + minYaxis: '0', + maxYaxis: '50', + }); + + // Test html widget + DashboardUtils.testWidget('html', dashboardName); + + // Test line widget + DashboardUtils.testWidget('line', dashboardName, { + minYaxis: '0', + maxYaxis: '50', + }); + + // Test raw widget + DashboardUtils.testWidget('raw', dashboardName); + + // Test single value widget + DashboardUtils.testWidget('number', dashboardName); + + // Test stacked line chart widget + DashboardUtils.testWidget('stacked-line-chart', dashboardName); + + // Test status widget + DashboardUtils.testWidget('status', dashboardName, { + intervalKey: '5', + }); + + // Test table widget + DashboardUtils.testWidget('table', dashboardName); + + // Test traffic light widget + DashboardUtils.testWidget('trafficlight', dashboardName, { + warningRange: '5', + criticalValue: '5', + }); + }); +}); diff --git a/ui/src/app/dashboard/components/widget/dashboard-widget.component.html b/ui/src/app/dashboard/components/widget/dashboard-widget.component.html index a0ae353ddb..9ed91e5b88 100644 --- a/ui/src/app/dashboard/components/widget/dashboard-widget.component.html +++ b/ui/src/app/dashboard/components/widget/dashboard-widget.component.html @@ -31,7 +31,12 @@ > settings - From d5295c8bebe89797fb4f866b3d628deda5e695dd Mon Sep 17 00:00:00 2001 From: Marcelfrueh <78954450+Marcelfrueh@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:26:09 +0100 Subject: [PATCH 18/20] Remove lodash dependency from UI (#2193) * remove lodash * Remove lodash from dependencies * Modified deepCopy function --- ui/angular.json | 2 +- ui/package-lock.json | 1 - ui/package.json | 1 - .../pipeline-element-options.component.ts | 31 ++++++++++++++++--- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/ui/angular.json b/ui/angular.json index e9ef1b77d8..710ed50252 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -18,7 +18,7 @@ "main": "src/main.ts", "tsConfig": "src/tsconfig.app.json", "polyfills": "src/polyfills.ts", - "allowedCommonJsDependencies": ["lodash", "codemirror"], + "allowedCommonJsDependencies": ["codemirror"], "assets": [ "src/assets", { diff --git a/ui/package-lock.json b/ui/package-lock.json index edf64ef933..1eddf947c3 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -51,7 +51,6 @@ "jshint": "^2.13.6", "konva": "9.2.0", "leaflet": "1.9.3", - "lodash": "4.17.21", "material-icons": "^1.13.1", "ngx-color-picker": "^14.0.0", "ngx-echarts": "^15.0.3", diff --git a/ui/package.json b/ui/package.json index 3bcc385160..89408d6362 100644 --- a/ui/package.json +++ b/ui/package.json @@ -73,7 +73,6 @@ "jshint": "^2.13.6", "konva": "9.2.0", "leaflet": "1.9.3", - "lodash": "4.17.21", "material-icons": "^1.13.1", "ngx-color-picker": "^14.0.0", "ngx-echarts": "^15.0.3", diff --git a/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts b/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts index b409d77d52..a86eaed620 100644 --- a/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts +++ b/ui/src/app/editor/components/pipeline-element-options/pipeline-element-options.component.ts @@ -44,7 +44,6 @@ import { import { EditorService } from '../../services/editor.service'; import { DialogService, PanelType } from '@streampipes/shared-ui'; import { CompatibleElementsComponent } from '../../dialog/compatible-elements/compatible-elements.component'; -import { cloneDeep } from 'lodash'; import { Subscription } from 'rxjs'; import { JsplumbFactoryService } from '../../services/jsplumb-factory.service'; @@ -160,7 +159,7 @@ export class PipelineElementOptionsComponent implements OnInit, OnDestroy { } initRecs(pipelineElementDomId) { - const clonedModel: PipelineElementConfig[] = cloneDeep( + const clonedModel: PipelineElementConfig[] = this.deepCopy( this.rawPipelineModel, ); const currentPipeline = this.objectProvider.makePipeline(clonedModel); @@ -168,13 +167,13 @@ export class PipelineElementOptionsComponent implements OnInit, OnDestroy { .recommendPipelineElement(currentPipeline, pipelineElementDomId) .subscribe(result => { if (result.success) { - this.possibleElements = cloneDeep( + this.possibleElements = this.deepCopy( this.pipelineElementRecommendationService.collectPossibleElements( this.allElements, result.possibleElements, ), ); - this.recommendedElements = cloneDeep( + this.recommendedElements = this.deepCopy( this.pipelineElementRecommendationService.populateRecommendedList( this.allElements, result.recommendedElements, @@ -225,4 +224,28 @@ export class PipelineElementOptionsComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.pipelineElementConfiguredObservable.unsubscribe(); } + + deepCopy(obj) { + let clone: any = {}; + if ( + obj === null || + typeof obj !== 'object' || + Array.isArray(obj) || + obj === undefined + ) { + return obj; + } + + if (Array.isArray(obj)) { + clone = obj.map(item => this.deepCopy(item)); + } + + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + clone[key] = this.deepCopy(obj[key]); + } + } + + return clone; + } } From 8d8054bc7c8c53b586360b59268e7d36cbc9deeb Mon Sep 17 00:00:00 2001 From: Tim <50115603+bossenti@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:31:58 +0100 Subject: [PATCH 19/20] fix: change Java version to 17 for maven-javadoc-plugin (#2229) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6e785d454d..81f79948d1 100644 --- a/pom.xml +++ b/pom.xml @@ -939,7 +939,7 @@ -Xdoclint:none none -Xdoclint:none - 11 + 17 From 34b33bdcf7afe75cbe5090517ca61b7ee0ad8555 Mon Sep 17 00:00:00 2001 From: Muyang Ye Date: Mon, 27 Nov 2023 06:32:27 -0800 Subject: [PATCH 20/20] fix(#2106) Serialize non-primitive types as string and store in InfluxDB (#2196) * Serialize non-primitive types and store in Influx * extract RawFieldSerializer * rename test * delete old --- .../commons/influx/InfluxStore.java | 62 ++++++++++------ .../influx/serializer/RawFieldSerializer.java | 53 ++++++++++++++ .../serializer/TestRawFieldSerializer.java | 70 +++++++++++++++++++ 3 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/RawFieldSerializer.java create mode 100644 streampipes-data-explorer-commons/src/test/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/TestRawFieldSerializer.java diff --git a/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/InfluxStore.java b/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/InfluxStore.java index 4adf3f153c..4abb33d679 100644 --- a/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/InfluxStore.java +++ b/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/InfluxStore.java @@ -20,10 +20,10 @@ import org.apache.streampipes.commons.environment.Environment; import org.apache.streampipes.commons.exceptions.SpRuntimeException; +import org.apache.streampipes.dataexplorer.commons.influx.serializer.RawFieldSerializer; import org.apache.streampipes.model.datalake.DataLakeMeasure; import org.apache.streampipes.model.runtime.Event; import org.apache.streampipes.model.runtime.field.PrimitiveField; -import org.apache.streampipes.model.schema.EventProperty; import org.apache.streampipes.model.schema.EventPropertyPrimitive; import org.apache.streampipes.model.schema.PropertyScope; import org.apache.streampipes.vocabulary.SO; @@ -31,7 +31,6 @@ import org.influxdb.InfluxDB; import org.influxdb.dto.Point; -import org.influxdb.dto.Pong; import org.influxdb.dto.Query; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -49,6 +48,8 @@ public class InfluxStore { Map sanitizedRuntimeNames = new HashMap<>(); private InfluxDB influxDb = null; + private RawFieldSerializer rawFieldSerializer = new RawFieldSerializer(); + public InfluxStore(DataLakeMeasure measure, InfluxConnectionSettings settings) { this.measure = measure; @@ -76,7 +77,7 @@ private void connect(InfluxConnectionSettings settings) throws SpRuntimeExceptio influxDb = InfluxClientProvider.getInfluxDBClient(settings); // Checking, if server is available - Pong response = influxDb.ping(); + var response = influxDb.ping(); if (response.getVersion().equalsIgnoreCase("unknown")) { throw new SpRuntimeException("Could not connect to InfluxDb Server: " + settings.getConnectionUrl()); } @@ -90,8 +91,8 @@ private void connect(InfluxConnectionSettings settings) throws SpRuntimeExceptio // setting up the database influxDb.setDatabase(databaseName); - int batchSize = 2000; - int flushDuration = 500; + var batchSize = 2000; + var flushDuration = 500; influxDb.enableBatch(batchSize, flushDuration, TimeUnit.MILLISECONDS); } @@ -122,28 +123,26 @@ public void onEvent(Event event) throws SpRuntimeException { } // sanitize event - for (String key : event.getRaw().keySet()) { + for (var key : event.getRaw().keySet()) { if (InfluxDbReservedKeywords.KEYWORD_LIST.stream().anyMatch(k -> k.equalsIgnoreCase(key))) { event.renameFieldByRuntimeName(key, key + "_"); } } - Long timestampValue = event.getFieldBySelector(measure.getTimestampField()).getAsPrimitive().getAsLong(); - Point.Builder point = + var timestampValue = event.getFieldBySelector(measure.getTimestampField()).getAsPrimitive().getAsLong(); + var point = Point.measurement(measure.getMeasureName()).time((long) timestampValue, TimeUnit.MILLISECONDS); - for (EventProperty ep : measure.getEventSchema().getEventProperties()) { - if (ep instanceof EventPropertyPrimitive) { - String runtimeName = ep.getRuntimeName(); - - // timestamp should not be added as a field - if (!measure.getTimestampField().endsWith(runtimeName)) { - String sanitizedRuntimeName = sanitizedRuntimeNames.get(runtimeName); - - try { - var field = event.getOptionalFieldByRuntimeName(runtimeName); + for (var ep : measure.getEventSchema().getEventProperties()) { + var runtimeName = ep.getRuntimeName(); + // timestamp should not be added as a field + if (!measure.getTimestampField().endsWith(runtimeName)) { + var sanitizedRuntimeName = sanitizedRuntimeNames.get(runtimeName); + var field = event.getOptionalFieldByRuntimeName(runtimeName); + try { + if (ep instanceof EventPropertyPrimitive) { if (field.isPresent()) { - PrimitiveField eventPropertyPrimitiveField = field.get().getAsPrimitive(); + var eventPropertyPrimitiveField = field.get().getAsPrimitive(); if (eventPropertyPrimitiveField.getRawValue() == null) { nullFields.add(sanitizedRuntimeName); } else { @@ -162,10 +161,18 @@ public void onEvent(Event event) throws SpRuntimeException { } else { missingFields.add(runtimeName); } - } catch (SpRuntimeException iae) { - LOG.warn("Runtime exception while extracting field value of field {} - this field will be ignored", - runtimeName, iae); + } else { + // Since InfluxDB can't store non-primitive types, store them as string + // and deserialize later in downstream processes + if (field.isPresent()) { + handleNonPrimitiveMeasurementProperty(point, event, sanitizedRuntimeName); + } else { + missingFields.add(runtimeName); + } } + } catch (SpRuntimeException iae) { + LOG.warn("Runtime exception while extracting field value of field {} - this field will be ignored", + runtimeName, iae); } } } @@ -189,7 +196,7 @@ private void handleMeasurementProperty(Point.Builder p, PrimitiveField eventPropertyPrimitiveField) { try { // Store property according to property type - String runtimeType = ep.getRuntimeType(); + var runtimeType = ep.getRuntimeType(); if (XSD.INTEGER.toString().equals(runtimeType)) { try { p.addField(preparedRuntimeName, eventPropertyPrimitiveField.getAsInt()); @@ -218,6 +225,15 @@ private void handleMeasurementProperty(Point.Builder p, } } + private void handleNonPrimitiveMeasurementProperty(Point.Builder p, Event event, String preparedRuntimeName) { + try { + var json = rawFieldSerializer.serialize(event.getRaw().get(preparedRuntimeName)); + p.addField(preparedRuntimeName, json); + } catch (SpRuntimeException e) { + LOG.warn("Failed to serialize field {}, ignoring.", preparedRuntimeName); + } + } + /** * Shuts down the connection to the InfluxDB server */ diff --git a/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/RawFieldSerializer.java b/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/RawFieldSerializer.java new file mode 100644 index 0000000000..05380df10c --- /dev/null +++ b/streampipes-data-explorer-commons/src/main/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/RawFieldSerializer.java @@ -0,0 +1,53 @@ +/* + * 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.dataexplorer.commons.influx.serializer; + +import org.apache.streampipes.commons.exceptions.SpRuntimeException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; + +public class RawFieldSerializer { + protected ObjectMapper objectMapper; + + public RawFieldSerializer() { + this.objectMapper = new ObjectMapper().activateDefaultTyping( + BasicPolymorphicTypeValidator.builder() + .allowIfBaseType(Object.class) + .build(), + ObjectMapper.DefaultTyping.EVERYTHING); + } + + public String serialize(Object object) { + try { + return objectMapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new SpRuntimeException(e.getCause()); + } + } + + public Object deserialize(String json) { + try { + return objectMapper.readValue(json, Object.class); + } catch (JsonProcessingException e) { + throw new SpRuntimeException(e.getCause()); + } + } +} diff --git a/streampipes-data-explorer-commons/src/test/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/TestRawFieldSerializer.java b/streampipes-data-explorer-commons/src/test/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/TestRawFieldSerializer.java new file mode 100644 index 0000000000..c9b85695d1 --- /dev/null +++ b/streampipes-data-explorer-commons/src/test/java/org/apache/streampipes/dataexplorer/commons/influx/serializer/TestRawFieldSerializer.java @@ -0,0 +1,70 @@ +/* + * 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.dataexplorer.commons.influx.serializer; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class TestRawFieldSerializer { + private RawFieldSerializer rawFieldSerializer = new RawFieldSerializer(); + private Map primitives = new HashMap(); + + public TestRawFieldSerializer() { + primitives.put("Integer", 1); + primitives.put("Long", 1L); + primitives.put("Float", 1.0f); + primitives.put("Double", 1.0d); + primitives.put("Boolean", true); + primitives.put("String", "1"); + } + + // Test able to deserialize back the original data + @Test + public void testRawFieldSerializerListInMap() { + var rawListField = new ArrayList(); + rawListField.addAll(primitives.values()); + + var rawNestedField = new HashMap(); + rawNestedField.putAll(primitives); + rawNestedField.put("List", rawListField); + + var json = rawFieldSerializer.serialize(rawNestedField); + + assertEquals(rawNestedField, rawFieldSerializer.deserialize(json)); + } + + @Test + public void testRawFieldSerializerMapInList() { + var rawNestedField = new HashMap(); + rawNestedField.putAll(primitives); + + var rawListField = new ArrayList(); + rawListField.addAll(primitives.values()); + rawListField.add(rawNestedField); + + var json = rawFieldSerializer.serialize(rawListField); + + assertEquals(rawListField, rawFieldSerializer.deserialize(json)); + } +}