From 195f2029903e160eb16b9fddd2044b2b06a47219 Mon Sep 17 00:00:00 2001 From: Petr John Date: Wed, 31 Jul 2024 20:25:11 +0200 Subject: [PATCH 01/26] Feature #1: Start saving to Influx by duplicating the messages from MQTT preprocessor to another queue (cherry picked from commit 6b0d33e596b8e5cd4c0312029387450860b01a2b) --- backend/backend-core/src/isc/setup.go | 11 +- .../src/sharedConstants/sharedConstants.go | 2 + .../commons/src/sharedModel/time-series.go | 171 ++++++++++++++++ .../sharedUtils/getEnvironmentParameter.go | 30 +++ backend/mqtt-preprocessor/go.mod | 1 + backend/mqtt-preprocessor/go.sum | 2 + backend/mqtt-preprocessor/src/main.go | 16 ++ backend/time-series-store/cmd/main.go | 119 +++++++++++ backend/time-series-store/go.mod | 22 +++ backend/time-series-store/go.sum | 31 +++ .../internal/Influx2Client.go | 184 ++++++++++++++++++ .../internal/TimeSeriesStoreEnvironment.go | 9 + 12 files changed, 597 insertions(+), 1 deletion(-) create mode 100644 backend/commons/src/sharedModel/time-series.go create mode 100644 backend/commons/src/sharedUtils/getEnvironmentParameter.go create mode 100644 backend/time-series-store/cmd/main.go create mode 100644 backend/time-series-store/go.mod create mode 100644 backend/time-series-store/go.sum create mode 100644 backend/time-series-store/internal/Influx2Client.go create mode 100644 backend/time-series-store/internal/TimeSeriesStoreEnvironment.go diff --git a/backend/backend-core/src/isc/setup.go b/backend/backend-core/src/isc/setup.go index d2bd81a..af8830a 100644 --- a/backend/backend-core/src/isc/setup.go +++ b/backend/backend-core/src/isc/setup.go @@ -12,7 +12,16 @@ func getQueueDeclarationErrorMessage(queueName string) string { } func SetupRabbitMQInfrastructureForISC(rabbitMQClient rabbitmq.Client) { - namesOfQueuesToDeclare := sharedUtils.SliceOf[string](sharedConstants.KPIFulfillmentCheckResultsQueueName, sharedConstants.KPIFulfillmentCheckRequestsQueueName, sharedConstants.SDInstanceRegistrationRequestsQueueName, sharedConstants.SetOfSDTypesUpdatesQueueName, sharedConstants.SetOfSDInstancesUpdatesQueueName, sharedConstants.MessageProcessingUnitConnectionNotificationsQueueName) + namesOfQueuesToDeclare := sharedUtils.SliceOf[string]( + sharedConstants.KPIFulfillmentCheckResultsQueueName, + sharedConstants.KPIFulfillmentCheckRequestsQueueName, + sharedConstants.SDInstanceRegistrationRequestsQueueName, + sharedConstants.SetOfSDTypesUpdatesQueueName, + sharedConstants.SetOfSDInstancesUpdatesQueueName, + sharedConstants.MessageProcessingUnitConnectionNotificationsQueueName, + sharedConstants.TimeSeriesStoreDataQueueName, + sharedConstants.TimeSeriesReadRequestQueueName, + ) sharedUtils.ForEach(namesOfQueuesToDeclare, func(nameOfQueuesToDeclare string) { sharedUtils.TerminateOnError(rabbitMQClient.DeclareQueue(nameOfQueuesToDeclare), getQueueDeclarationErrorMessage(nameOfQueuesToDeclare)) }) diff --git a/backend/commons/src/sharedConstants/sharedConstants.go b/backend/commons/src/sharedConstants/sharedConstants.go index ac14c27..b522bd0 100644 --- a/backend/commons/src/sharedConstants/sharedConstants.go +++ b/backend/commons/src/sharedConstants/sharedConstants.go @@ -8,4 +8,6 @@ const ( SDInstanceRegistrationRequestsQueueName = "sd-instance-registration-requests" SetOfSDInstancesUpdatesQueueName = "set-of-sd-instances-updates" SetOfSDTypesUpdatesQueueName = "set-of-sd-types-updates" + TimeSeriesStoreDataQueueName = "time-series-store-data" + TimeSeriesReadRequestQueueName = "time-series-read-request" ) diff --git a/backend/commons/src/sharedModel/time-series.go b/backend/commons/src/sharedModel/time-series.go new file mode 100644 index 0000000..2bb848c --- /dev/null +++ b/backend/commons/src/sharedModel/time-series.go @@ -0,0 +1,171 @@ +package sharedModel + +import ( + "encoding/json" + "fmt" + "time" +) + +// Sensors represents the sensors to be queried. +type Sensors interface{} + +// SimpleSensors represents a simple definition of sensors. +type SimpleSensors []string + +// SensorsWithFields represents sensors with specific fields. +type SensorsWithFields map[string][]string + +// AreSimpleSensors checks if the sensors are of type SimpleSensors. +func AreSimpleSensors(sensors interface{}) bool { + _, ok := sensors.(SimpleSensors) + return ok +} + +// Operation represents aggregation operations. +type Operation string + +// InputData represents a single input data point. +type InputData KPIFulfillmentCheckRequestISCMessage + +// OutputData represents a single data point retrieved from InfluxDB. +type OutputData struct { + Result string `json:"result"` // Result metadata + Table int64 `json:"table"` // Table number metadata + Time time.Time `json:"time"` // Time of the current data sample + DeviceID string `json:"deviceId"` // Device identifier + DeviceType string `json:"deviceType"` // Device type + Other map[string]interface{} `json:"-"` +} + +// ReadRequestBody represents the request body for the statistics endpoint. +type ReadRequestBody struct { + Sensors Sensors `json:"sensors"` + Operation Operation `json:"operation"` + Timezone string `json:"timezone,omitempty"` + From *time.Time `json:"from,omitempty"` + To *time.Time `json:"to,omitempty"` + AggregateMinutes int `json:"aggregateMinutes,omitempty"` +} + +// UnmarshalJSON unmarshals JSON to the OutputData struct. +func (outputData *OutputData) UnmarshalJSON(data []byte) error { + type Alias OutputData + aux := &struct { + Time string `json:"time"` + *Alias + }{ + Alias: (*Alias)(outputData), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + parsedTime, err := time.Parse(time.RFC3339, aux.Time) + if err != nil { + return err + } + outputData.Time = parsedTime + return nil +} + +// UnmarshalJSON unmarshals JSON to the ReadRequestBody struct. +func (r *ReadRequestBody) UnmarshalJSON(data []byte) error { + type Alias ReadRequestBody + aux := &struct { + Sensors interface{} `json:"sensors"` + Alias + }{ + Alias: (Alias)(*r), + } + + var z map[string]interface{} + + if err := json.Unmarshal(data, &z); err != nil { + fmt.Println(string(data[:])) + panic(err) + } + + convertedFrom, _ := time.Parse(time.RFC3339, z["from"].(string)) + convertedTo, _ := time.Parse(time.RFC3339, z["to"].(string)) + + r.From = &convertedFrom + r.To = &convertedTo + r.AggregateMinutes = int(z["aggregateMinutes"].(float64)) + r.Timezone = z["timezone"].(string) + r.Operation = Operation(z["operation"].(string)) + + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + switch v := aux.Sensors.(type) { + case map[string]interface{}: + sensorsWithFields := SensorsWithFields{} + for key, value := range v { + if values, ok := value.([]interface{}); ok { + fields := make([]string, len(values)) + for i, val := range values { + if field, ok := val.(string); ok { + fields[i] = field + } else { + return fmt.Errorf("unexpected field type: %T", val) + } + } + sensorsWithFields[key] = fields + } else { + return fmt.Errorf("unexpected sensor type: %T", value) + } + } + r.Sensors = sensorsWithFields + case []interface{}: + simpleSensors := make([]string, len(v)) + for i, sensor := range v { + if s, ok := sensor.(string); ok { + simpleSensors[i] = s + } else { + return fmt.Errorf("unexpected sensor type: %T", sensor) + } + } + r.Sensors = SimpleSensors(simpleSensors) + default: + return fmt.Errorf("unsupported sensors type: %T", aux.Sensors) + } + return nil +} + +// MarshalJSON marshals ReadRequestBody to JSON. +func (r *ReadRequestBody) MarshalJSON() ([]byte, error) { + type Alias ReadRequestBody + if r.Sensors == nil { + // If Sensors field is nil, omit it from the JSON output + r.Sensors = []string{} + } + return json.Marshal(&struct{ Alias }{Alias: (Alias)(*r)}) +} + +// MarshalJSON marshals the OutputData struct to JSON. +func (outputData OutputData) MarshalJSON() ([]byte, error) { + data := make(map[string]interface{}, len(outputData.Other)+5) + + // copy status fields + for k, v := range outputData.Other { + data[k] = v + } + // add known keys + data["time"] = outputData.Time.Format(time.RFC3339) + data["deviceId"] = outputData.DeviceID + data["deviceType"] = outputData.DeviceType + data["result"] = outputData.Result + data["table"] = outputData.Table + + return json.Marshal(data) +} + +// MarshalJSON marshals SimpleSensors to JSON. +func (simpleSensors SimpleSensors) MarshalJSON() ([]byte, error) { + return json.Marshal([]string(simpleSensors)) +} + +// MarshalJSON marshals SensorsWithFields to JSON. +func (sensorsWithFields SensorsWithFields) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string][]string(sensorsWithFields)) +} diff --git a/backend/commons/src/sharedUtils/getEnvironmentParameter.go b/backend/commons/src/sharedUtils/getEnvironmentParameter.go new file mode 100644 index 0000000..57d04c9 --- /dev/null +++ b/backend/commons/src/sharedUtils/getEnvironmentParameter.go @@ -0,0 +1,30 @@ +package sharedUtils + +import ( + "errors" + "flag" + "os" +) + +// GetEnvironmentParameter returns the value of an environment parameter. +// If the parameter is provided as a command-line flag, it takes precedence. +// If neither the flag nor the environment variable is provided, it returns an error. +// Note: This function assumes that flag.Parse() has already been called to parse command-line flags. +func GetEnvironmentParameter(name, description string) (string, error) { + // Check if command-line flag is provided + flagValue := flag.String(name, "", description) + + // If command-line flag is provided, return its value + if *flagValue != "" { + return *flagValue, nil + } + + // If no flag is provided, check environment variable + envValue := os.Getenv(name) + if envValue != "" { + return envValue, nil + } + + // If neither flag nor environment variable is provided, return an error + return "", errors.New("Neither flag nor environment variable provided for " + name) +} diff --git a/backend/mqtt-preprocessor/go.mod b/backend/mqtt-preprocessor/go.mod index 0571edf..012093b 100644 --- a/backend/mqtt-preprocessor/go.mod +++ b/backend/mqtt-preprocessor/go.mod @@ -5,6 +5,7 @@ go 1.22 require ( github.com/MichalBures-OG/bp-bures-RIoT-commons v0.0.0-00010101000000-000000000000 github.com/eclipse/paho.mqtt.golang v1.4.3 + github.com/google/uuid v1.6.0 ) replace github.com/MichalBures-OG/bp-bures-RIoT-commons => ./../commons diff --git a/backend/mqtt-preprocessor/go.sum b/backend/mqtt-preprocessor/go.sum index bcb6203..6b4606e 100644 --- a/backend/mqtt-preprocessor/go.sum +++ b/backend/mqtt-preprocessor/go.sum @@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik= github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= diff --git a/backend/mqtt-preprocessor/src/main.go b/backend/mqtt-preprocessor/src/main.go index bff2c36..b40d055 100644 --- a/backend/mqtt-preprocessor/src/main.go +++ b/backend/mqtt-preprocessor/src/main.go @@ -133,6 +133,22 @@ func processMQTTMessagePayload(mqttMessagePayload []byte, rabbitMQClient rabbitm } messagePayloadObject := jsonDeserializationResult.GetPayload() sd := messagePayloadObject.Data.SDArray[0] + + inputData := sharedModel.InputData{ + Timestamp: messagePayloadObject.Notification.Timestamp, + SDInstanceUID: sd.UID, + SDTypeSpecification: sd.Type, + Parameters: sd.Parameters, + } + jsonSerializationResult := sharedUtils.SerializeToJSON(inputData) + log.Println(inputData) + + err := rabbitMQClient.PublishJSONMessage(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesStoreDataQueueName), jsonSerializationResult.GetPayload()) + if err != nil { + log.Println("Failed to publish a time series store request message") + return + } + if !mqttMessageSDTypeCorrespondsToSDTypeDefinitions(sd.Type) { return } diff --git a/backend/time-series-store/cmd/main.go b/backend/time-series-store/cmd/main.go new file mode 100644 index 0000000..d7f5972 --- /dev/null +++ b/backend/time-series-store/cmd/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "flag" + "fmt" + "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/rabbitmq" + "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedConstants" + "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedModel" + "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" + "github.com/goccy/go-json" + amqp "github.com/rabbitmq/amqp091-go" + "github.com/xjohnp00/jiap/backend/shared/time-series-store/internal" + "os" +) + +func main() { + hasError, environment := parseParameters() + + if hasError { + os.Exit(1) + } + + influx, influxErrors, _ := internal.NewInflux2Client(environment.InfluxUrl, environment.InfluxToken, environment.InfluxOrg, environment.InfluxBucket) + + rabbitMQClient := rabbitmq.NewClient() + defer rabbitMQClient.Dispose() + defer influx.Close() + + sharedUtils.WaitForAll( + func() { + err := consumeInputMessages(rabbitMQClient, influx) + + if err != nil { + fmt.Println(err.Error()) + } + }, + func() { + err := consumeReadRequests(rabbitMQClient, influx) + + if err != nil { + fmt.Println(err.Error()) + } + }, + ) + + go func() { + for err := range influxErrors { + fmt.Println(err) + } + }() +} + +func consumeInputMessages(rabbitMQClient rabbitmq.Client, influx internal.Influx2Client) error { + err := rabbitmq.ConsumeJSONMessages[sharedModel.InputData](rabbitMQClient, sharedConstants.TimeSeriesStoreDataQueueName, func(messagePayload sharedModel.InputData) error { + influx.Write(messagePayload) + return nil + }) + return err +} + +func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2Client) error { + err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[sharedModel.ReadRequestBody](rabbitMQClient, sharedConstants.TimeSeriesReadRequestQueueName, func(readRequestBody sharedModel.ReadRequestBody, delivery amqp.Delivery) error { + data, retrieveDataError := influx.Query(readRequestBody) + + jsonData, _ := json.Marshal(data) + if retrieveDataError != nil { + fmt.Println(retrieveDataError.Error()) + err := rabbitMQClient.PublishJSONMessageRPC(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), jsonData, delivery.ReplyTo) + + if err != nil { + return err + } + return retrieveDataError + } + return nil + }) + return err +} + +func parseParameters() (bool, internal.TimeSeriesStoreEnvironment) { + flag.Parse() + token, tokenError := sharedUtils.GetEnvironmentParameter("INFLUX_TOKEN", "InfluxDB Token") + url, urlError := sharedUtils.GetEnvironmentParameter("INFLUX_URL", "InfluxDB URL") + org, orgError := sharedUtils.GetEnvironmentParameter("INFLUX_ORGANIZATION", "InfluxDB Organization") + bucket, bucketError := sharedUtils.GetEnvironmentParameter("INFLUX_BUCKET", "InfluxDB Bucket") + ampqUrl, ampqUrlError := sharedUtils.GetEnvironmentParameter("RABBITMQ_URL", "RABBITMQ Broker URL") + + hasError := tokenError != nil || urlError != nil || orgError != nil || bucketError != nil || ampqUrlError != nil + + if tokenError != nil { + fmt.Println(fmt.Errorf(tokenError.Error())) + } + + if urlError != nil { + fmt.Println(fmt.Errorf(urlError.Error())) + } + + if orgError != nil { + fmt.Println(fmt.Errorf(orgError.Error())) + } + + if bucketError != nil { + fmt.Println(fmt.Errorf(bucketError.Error())) + } + + if ampqUrlError != nil { + fmt.Println(fmt.Errorf(ampqUrlError.Error())) + } + + environment := internal.TimeSeriesStoreEnvironment{ + InfluxToken: token, + InfluxUrl: url, + InfluxOrg: org, + InfluxBucket: bucket, + AmqpURLValue: ampqUrl, + } + + return hasError, environment +} diff --git a/backend/time-series-store/go.mod b/backend/time-series-store/go.mod new file mode 100644 index 0000000..b9a8f2f --- /dev/null +++ b/backend/time-series-store/go.mod @@ -0,0 +1,22 @@ +module github.com/xjohnp00/jiap/backend/shared/time-series-store + +go 1.22 + +toolchain go1.22.4 + +require ( + github.com/MichalBures-OG/bp-bures-RIoT-commons v0.0.0-00010101000000-000000000000 + github.com/influxdata/influxdb-client-go/v2 v2.13.0 +) + +require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect + github.com/oapi-codegen/runtime v1.0.0 // indirect + github.com/rabbitmq/amqp091-go v1.10.0 // indirect + golang.org/x/net v0.17.0 // indirect +) + +replace github.com/MichalBures-OG/bp-bures-RIoT-commons => ./../commons diff --git a/backend/time-series-store/go.sum b/backend/time-series-store/go.sum new file mode 100644 index 0000000..6dff3c4 --- /dev/null +++ b/backend/time-series-store/go.sum @@ -0,0 +1,31 @@ +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM= +github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo= +github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= +github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/backend/time-series-store/internal/Influx2Client.go b/backend/time-series-store/internal/Influx2Client.go new file mode 100644 index 0000000..d0aaeff --- /dev/null +++ b/backend/time-series-store/internal/Influx2Client.go @@ -0,0 +1,184 @@ +package internal + +import ( + "context" + "fmt" + sharedModel "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedModel" + influxdb2 "github.com/influxdata/influxdb-client-go/v2" + "github.com/influxdata/influxdb-client-go/v2/api" + "strings" + "time" +) + +type Influx2Client struct { + endpoint string + organization string + bucket string + client influxdb2.Client + writeApi api.WriteAPI + queryApi api.QueryAPI +} + +func NewInflux2Client(endpoint string, token string, organization string, bucket string) (Influx2Client, <-chan error, error) { + client := influxdb2.NewClientWithOptions(endpoint, token, influxdb2.DefaultOptions().SetBatchSize(20)) + + influx2Client := Influx2Client{ + endpoint: endpoint, + organization: organization, + bucket: bucket, + client: client, + writeApi: client.WriteAPI(organization, bucket), + queryApi: client.QueryAPI(organization), + } + return influx2Client, influx2Client.writeApi.Errors(), nil +} + +func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) ([]sharedModel.OutputData, error) { + aggregation := createAggregation(body) + timeRange := convertTimeToQueryTimePart(body) + filter := createFilter(body) + + query := fmt.Sprintf("from(bucket: \"%s\")\n"+ + " %s\n"+ // time range + " %s\n"+ // filter + " %s\n"+ // aggregation + " |> drop(columns: [\"_start\", \"_stop\"])\n"+ + " |> pivot(columnKey: [\"_field\"], rowKey: [\"_measurement\", \"_time\"], valueColumn: \"_value\")\n"+ + " |> rename(columns: {_time: \"time\", _measurement: \"deviceId\"})\n"+ + " |> group(columns: [\"measurement\"], mode: \"by\")", influx2Client.bucket, timeRange, filter, aggregation) + + fmt.Println(query) + + result, err := influx2Client.queryApi.Query(context.Background(), query) + + if err != nil { + return nil, err + } + + var outputData []sharedModel.OutputData + + if result.Err() != nil { + return nil, result.Err() + } + + for result.Next() { + outputData = append(outputData, mapToOutputData(result.Record().Values())) + } + + return outputData, nil +} + +func (influx2Client Influx2Client) Write(data sharedModel.InputData) { + if parameters, ok := data.Parameters.(map[string]interface{}); ok { + point := influxdb2.NewPoint(data.SDInstanceUID, map[string]string{"deviceType": data.SDTypeSpecification}, parameters, time.Unix(int64(data.Timestamp), 0)) + fmt.Println(point) + influx2Client.writeApi.WritePoint(point) + } else { + fmt.Println("parameterAsAny is not a map[string]interface{}") + } + + influx2Client.writeApi.Flush() +} + +func (influx2Client Influx2Client) Close() { + influx2Client.client.Close() +} + +func convertTimeToQueryTimePart(body sharedModel.ReadRequestBody) string { + if body.From != nil && body.To == nil { + currentDate := body.From.AddDate(0, 0, 30) + + currentDateString := currentDate.Format(time.RFC3339) + + return fmt.Sprintf("|> range(start: %s, stop: %s)", body.From, currentDateString) + } + + if body.From == nil && body.To != nil { + thirtyDaysAgo := body.To.AddDate(0, 0, -30) + + thirtyDaysAgoString := thirtyDaysAgo.Format(time.RFC3339) + + return fmt.Sprintf("|> range(start: %s, stop: %s)", thirtyDaysAgoString, body.To) + } + + if body.From == nil && body.To == nil { + currentDate := time.Now() + thirtyDaysAgo := currentDate.AddDate(0, 0, -30) + + currentDateString := currentDate.Format(time.RFC3339) + thirtyDaysAgoString := thirtyDaysAgo.Format(time.RFC3339) + + return fmt.Sprintf("|> range(start: %s, stop: %s)", thirtyDaysAgoString, currentDateString) + } + + return fmt.Sprintf("|> range(start: %s, stop: %s)", body.From.Format(time.RFC3339), body.To.Format(time.RFC3339)) +} + +func createFilter(body sharedModel.ReadRequestBody) string { + var filterStrings []string + + if sharedModel.AreSimpleSensors(body.Sensors) { + simpleSensors := body.Sensors.(sharedModel.SimpleSensors) + for _, sensor := range simpleSensors { + filterStrings = append(filterStrings, fmt.Sprintf(`r["_measurement"] == "%s"`, sensor)) + } + } else { + sensorsWithFields := body.Sensors.(sharedModel.SensorsWithFields) + for sensor, fields := range sensorsWithFields { + var fieldConditions []string + for _, field := range fields { + fieldConditions = append(fieldConditions, fmt.Sprintf(`r["_field"] == "%s"`, field)) + } + fieldCondition := strings.Join(fieldConditions, " or ") + if fieldCondition != "" { + fieldCondition = " and (" + fieldCondition + ")" + } + filterStrings = append(filterStrings, fmt.Sprintf(`(r["_measurement"] == "%s"%s)`, sensor, fieldCondition)) + } + } + + filter := strings.Join(filterStrings, " or ") + + return fmt.Sprintf("|> filter(fn: (r) => %s)", filter) +} + +func createAggregation(body sharedModel.ReadRequestBody) string { + aggregation := "" + + if body.Operation != "" { + zone := "" + + if body.AggregateMinutes == 0 { + body.AggregateMinutes = 10 + } + + if body.Timezone != "" { + zone = fmt.Sprintf(", location: timezone.location(name: \"%s\")", body.Timezone) + } + + aggregation = fmt.Sprintf("|> aggregateWindow(every: %xm, fn: %s, createEmpty: false%s)", body.AggregateMinutes, body.Operation, zone) + } + + return aggregation +} + +// mapToOutputData converts a map[string]interface{} to an OutputData struct. +func mapToOutputData(influxOutput map[string]interface{}) sharedModel.OutputData { + outputData := sharedModel.OutputData{ + Result: influxOutput["result"].(string), + Table: influxOutput["table"].(int64), + Time: influxOutput["time"].(time.Time), + DeviceID: influxOutput["deviceId"].(string), + DeviceType: influxOutput["deviceType"].(string), + } + + delete(influxOutput, "result") + delete(influxOutput, "table") + delete(influxOutput, "time") + delete(influxOutput, "deviceId") + delete(influxOutput, "deviceType") + + outputData.Other = influxOutput + + return outputData +} diff --git a/backend/time-series-store/internal/TimeSeriesStoreEnvironment.go b/backend/time-series-store/internal/TimeSeriesStoreEnvironment.go new file mode 100644 index 0000000..d7cdc68 --- /dev/null +++ b/backend/time-series-store/internal/TimeSeriesStoreEnvironment.go @@ -0,0 +1,9 @@ +package internal + +type TimeSeriesStoreEnvironment struct { + InfluxToken string + InfluxUrl string + InfluxOrg string + InfluxBucket string + AmqpURLValue string +} From ead489b04a522a597c93cfb70f817ea2a65cb5e2 Mon Sep 17 00:00:00 2001 From: Petr John Date: Wed, 31 Jul 2024 20:28:23 +0200 Subject: [PATCH 02/26] Feature #1: Add publish json message rpc (cherry picked from commit e0a7791dc6618855e4e870623baa2c838d2e41dd) --- backend/commons/src/rabbitmq/client.go | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/backend/commons/src/rabbitmq/client.go b/backend/commons/src/rabbitmq/client.go index 6d753b3..ed046a7 100644 --- a/backend/commons/src/rabbitmq/client.go +++ b/backend/commons/src/rabbitmq/client.go @@ -55,6 +55,7 @@ func (c *connectionManager) release() { type Client interface { GetChannel() *amqp.Channel PublishJSONMessage(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte) error + PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte, correlationId string) error DeclareQueue(queueName string) error SetupMessageConsumption(queueName string, messageConsumerFunction func(message amqp.Delivery) error) error Dispose() @@ -87,6 +88,19 @@ func (c *ClientImpl) PublishJSONMessage(exchangeNameOptional sharedUtils.Optiona }) } +func (c *ClientImpl) PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte, correlationId string) error { + ctx, cancelFunction := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFunction() + exchangeName := exchangeNameOptional.GetPayloadOrDefault("") + routingKey := routingKeyOptional.GetPayloadOrDefault("") + return c.channel.PublishWithContext(ctx, exchangeName, routingKey, false, false, amqp.Publishing{ + ContentType: "application/json", + Body: messagePayload, + CorrelationId: correlationId, + ReplyTo: routingKey, + }) +} + func (c *ClientImpl) DeclareQueue(queueName string) error { _, err := c.channel.QueueDeclare(queueName, false, false, false, false, nil) return err @@ -130,6 +144,20 @@ func ConsumeJSONMessages[T any](client Client, queueName string, messagePayloadC }) } +func ConsumeJSONMessagesWithAccessToDelivery[T any](client Client, queueName string, messagePayloadConsumerFunction func(messagePayload T, delivery amqp.Delivery) error) error { + return client.SetupMessageConsumption(queueName, func(message amqp.Delivery) error { + messageContentType := message.ContentType + if messageContentType != "application/json" { + return fmt.Errorf("incorrect message content type: %s", messageContentType) + } + jsonDeserializationResult := sharedUtils.DeserializeFromJSON[T](message.Body) + if jsonDeserializationResult.IsFailure() { + return jsonDeserializationResult.GetError() + } + return messagePayloadConsumerFunction(jsonDeserializationResult.GetPayload(), message) + }) +} + func ConsumeJSONMessagesFromFanoutExchange[T any](client Client, fanoutExchangeName string, messagePayloadConsumerFunction func(messagePayload T) error) error { queue, err := client.GetChannel().QueueDeclare("", false, false, true, false, nil) if err != nil { From 152cd4de3071d3ae7226267957d89f1c33e01d4d Mon Sep 17 00:00:00 2001 From: Petr John Date: Sat, 10 Aug 2024 20:12:53 +0200 Subject: [PATCH 03/26] Feature #1: Start adding time series and commands support (cherry picked from commit a5e7813dbff7cc8fc4e26f958671742ea5178a5e) --- backend/backend-core/Dockerfile | 2 - .../backend-core/src/api/graphql/gsc/gsc.go | 1609 ++++++++++++++++- .../src/api/graphql/schema.graphqls | 121 ++ .../src/api/graphql/schema.resolvers.go | 9 + .../src/domainLogicLayer/statistics.go | 55 + .../backend-core/src/model/dbModel/model.go | 60 + .../src/model/graphQLModel/graphQLModel.go | 142 ++ backend/commons/src/rabbitmq/client.go | 36 +- .../{time-series.go => timeSeries.go} | 6 +- backend/time-series-store/Dockerfile | 19 + .../internal/Influx2Client.go | 2 +- .../time-series-store/{cmd => src}/main.go | 2 +- docker-compose.yml | 29 + 13 files changed, 1999 insertions(+), 93 deletions(-) create mode 100644 backend/backend-core/src/domainLogicLayer/statistics.go rename backend/commons/src/sharedModel/{time-series.go => timeSeries.go} (97%) create mode 100644 backend/time-series-store/Dockerfile rename backend/time-series-store/{cmd => src}/main.go (96%) diff --git a/backend/backend-core/Dockerfile b/backend/backend-core/Dockerfile index 94e883b..eaca90e 100644 --- a/backend/backend-core/Dockerfile +++ b/backend/backend-core/Dockerfile @@ -13,8 +13,6 @@ WORKDIR /app/backend-core # Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed RUN go mod tidy RUN go mod download -# Run unit tests -RUN go test ./... # Build the Go app RUN CGO_ENABLED=0 GOOS=linux go build -o RIoT-backend-core src/main.go # Run the executable diff --git a/backend/backend-core/src/api/graphql/gsc/gsc.go b/backend/backend-core/src/api/graphql/gsc/gsc.go index b95ed86..83c085a 100644 --- a/backend/backend-core/src/api/graphql/gsc/gsc.go +++ b/backend/backend-core/src/api/graphql/gsc/gsc.go @@ -60,6 +60,7 @@ type MutationResolver interface { CreateSDInstanceGroup(ctx context.Context, input graphQLModel.SDInstanceGroupInput) (graphQLModel.SDInstanceGroup, error) UpdateSDInstanceGroup(ctx context.Context, id uint32, input graphQLModel.SDInstanceGroupInput) (graphQLModel.SDInstanceGroup, error) DeleteSDInstanceGroup(ctx context.Context, id uint32) (bool, error) + StatisticsMutate(ctx context.Context, inputData graphQLModel.InputData) (bool, error) } type QueryResolver interface { SdType(ctx context.Context, id uint32) (graphQLModel.SDType, error) @@ -70,6 +71,7 @@ type QueryResolver interface { KpiFulfillmentCheckResults(ctx context.Context) ([]graphQLModel.KPIFulfillmentCheckResult, error) SdInstanceGroup(ctx context.Context, id uint32) (graphQLModel.SDInstanceGroup, error) SdInstanceGroups(ctx context.Context) ([]graphQLModel.SDInstanceGroup, error) + StatisticsQuery(ctx context.Context, request graphQLModel.StatisticsInput) ([]graphQLModel.OutputData, error) } type SubscriptionResolver interface { OnSDInstanceRegistered(ctx context.Context) (<-chan graphQLModel.SDInstance, error) @@ -101,12 +103,17 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { rc := graphql.GetOperationContext(ctx) ec := executionContext{rc, e, 0, 0, make(chan graphql.DeferredResult)} inputUnmarshalMap := graphql.BuildUnmarshalerMap( + ec.unmarshalInputInputData, ec.unmarshalInputKPIDefinitionInput, ec.unmarshalInputKPINodeInput, ec.unmarshalInputSDInstanceGroupInput, ec.unmarshalInputSDInstanceUpdateInput, ec.unmarshalInputSDParameterInput, ec.unmarshalInputSDTypeInput, + ec.unmarshalInputSensorFieldInput, + ec.unmarshalInputSensorsInput, + ec.unmarshalInputStatisticsFieldInput, + ec.unmarshalInputStatisticsInput, ) first := true @@ -428,6 +435,125 @@ input SDInstanceGroupInput { sdInstanceIDs: [ID!]! } +# ----- Statistics + +scalar Date + +type SimpleSensors { + sensors: [String!]! +} + +type SensorsWithFields { + sensors: [SensorField!]! +} + +type SensorField { + key: String! + values: [String!]! +} + +enum StatisticsOperation { + MEAN + MIN + MAX + FIRST + SUM + LAST + NONE + COUNT + INTEGRAL + MEDIAN + MODE + QUANTILE + REDUCE + SKEW + SPREAD + STDDEV + TIMEWEIGHTEDAVG +} + +""" +Data used for querying the selected bucket +""" +input StatisticsInput { + """ + Sensors to be queried + """ + sensors: SensorsInput! + """ + Start of the querying window + """ + from: Date + """ + End of the querying window + """ + to: Date + """ + Amount of minutes to aggregate by + For example if the queried range has 1 hour and aggregateMinutes is set to 10 the aggregation will result in 6 points + """ + aggregateMinutes: Int + """ + Timezone override default UTC. + For more details why and how this affects queries see: https://www.influxdata.com/blog/time-zones-in-flux/. + In most cases you can ignore this and some edge aggregations can be influenced. + If you need a precise result or the aggregation uses high amount of minutes provide the target time zone. + """ + timezone: String + """ + Aggregation operator to use, if needed + """ + operation: StatisticsOperation +} + +""" +Sensors to be queried +""" +input SensorsInput { + """ + Simple definition, returns all available sensor fields + """ + simpleSensors: [String] + """ + Return only the requested sensor fields + """ + sensorsWithFields: [SensorFieldInput] +} + +""" +Return only the requested sensor fields +""" +input SensorFieldInput { + key: String! + fields: [String!]! +} + +scalar StatisticsParameterValue + +type StatisticsField { + key: String! + value: StatisticsParameterValue! +} + +type OutputData { + time: Date! + deviceId: String! + deviceType: String + data: StatisticsField! +} + +input StatisticsFieldInput { + key: String! + value: StatisticsParameterValue! +} + +input InputData { + time: Date! + deviceId: String! + deviceType: String + data: StatisticsFieldInput! +} + # ----- Queries, mutations and subscriptions ----- type Query { @@ -439,6 +565,7 @@ type Query { kpiFulfillmentCheckResults: [KPIFulfillmentCheckResult!]! sdInstanceGroup(id: ID!): SDInstanceGroup! sdInstanceGroups: [SDInstanceGroup!]! + statisticsQuery(request: StatisticsInput!): [OutputData!]! } type Mutation { @@ -451,6 +578,7 @@ type Mutation { createSDInstanceGroup(input: SDInstanceGroupInput!): SDInstanceGroup! updateSDInstanceGroup(id: ID!, input: SDInstanceGroupInput!): SDInstanceGroup! deleteSDInstanceGroup(id: ID!): Boolean! + statisticsMutate(inputData: InputData!): Boolean! } type Subscription { @@ -555,6 +683,21 @@ func (ec *executionContext) field_Mutation_deleteSDType_args(ctx context.Context return args, nil } +func (ec *executionContext) field_Mutation_statisticsMutate_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 graphQLModel.InputData + if tmp, ok := rawArgs["inputData"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("inputData")) + arg0, err = ec.unmarshalNInputData2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐInputData(ctx, tmp) + if err != nil { + return nil, err + } + } + args["inputData"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_updateKPIDefinition_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -687,6 +830,21 @@ func (ec *executionContext) field_Query_sdType_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query_statisticsQuery_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 graphQLModel.StatisticsInput + if tmp, ok := rawArgs["request"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("request")) + arg0, err = ec.unmarshalNStatisticsInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["request"] = arg0 + return args, nil +} + func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2214,6 +2372,61 @@ func (ec *executionContext) fieldContext_Mutation_deleteSDInstanceGroup(ctx cont return fc, nil } +func (ec *executionContext) _Mutation_statisticsMutate(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_statisticsMutate(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().StatisticsMutate(rctx, fc.Args["inputData"].(graphQLModel.InputData)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_statisticsMutate(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_statisticsMutate_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _NumericEQAtomKPINode_id(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.NumericEQAtomKPINode) (ret graphql.Marshaler) { fc, err := ec.fieldContext_NumericEQAtomKPINode_id(ctx, field) if err != nil { @@ -3519,6 +3732,185 @@ func (ec *executionContext) fieldContext_NumericLTAtomKPINode_numericReferenceVa return fc, nil } +func (ec *executionContext) _OutputData_time(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.OutputData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_OutputData_time(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Time, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNDate2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_OutputData_time(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "OutputData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Date does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _OutputData_deviceId(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.OutputData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_OutputData_deviceId(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DeviceID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_OutputData_deviceId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "OutputData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _OutputData_deviceType(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.OutputData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_OutputData_deviceType(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DeviceType, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_OutputData_deviceType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "OutputData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _OutputData_data(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.OutputData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_OutputData_data(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Data, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(graphQLModel.StatisticsField) + fc.Result = res + return ec.marshalNStatisticsField2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsField(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_OutputData_data(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "OutputData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "key": + return ec.fieldContext_StatisticsField_key(ctx, field) + case "value": + return ec.fieldContext_StatisticsField_value(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type StatisticsField", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _Query_sdType(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_sdType(ctx, field) if err != nil { @@ -3988,8 +4380,8 @@ func (ec *executionContext) fieldContext_Query_sdInstanceGroups(_ context.Contex return fc, nil } -func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query___type(ctx, field) +func (ec *executionContext) _Query_statisticsQuery(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_statisticsQuery(ctx, field) if err != nil { return graphql.Null } @@ -4002,50 +4394,41 @@ func (ec *executionContext) _Query___type(ctx context.Context, field graphql.Col }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.introspectType(fc.Args["name"].(string)) + return ec.resolvers.Query().StatisticsQuery(rctx, fc.Args["request"].(graphQLModel.StatisticsInput)) }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*introspection.Type) + res := resTmp.([]graphQLModel.OutputData) fc.Result = res - return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) + return ec.marshalNOutputData2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐOutputDataᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Query___type(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Query_statisticsQuery(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Query", Field: field, IsMethod: true, - IsResolver: false, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "kind": - return ec.fieldContext___Type_kind(ctx, field) - case "name": - return ec.fieldContext___Type_name(ctx, field) - case "description": - return ec.fieldContext___Type_description(ctx, field) - case "fields": - return ec.fieldContext___Type_fields(ctx, field) - case "interfaces": - return ec.fieldContext___Type_interfaces(ctx, field) - case "possibleTypes": - return ec.fieldContext___Type_possibleTypes(ctx, field) - case "enumValues": - return ec.fieldContext___Type_enumValues(ctx, field) - case "inputFields": - return ec.fieldContext___Type_inputFields(ctx, field) - case "ofType": - return ec.fieldContext___Type_ofType(ctx, field) - case "specifiedByURL": - return ec.fieldContext___Type_specifiedByURL(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + case "time": + return ec.fieldContext_OutputData_time(ctx, field) + case "deviceId": + return ec.fieldContext_OutputData_deviceId(ctx, field) + case "deviceType": + return ec.fieldContext_OutputData_deviceType(ctx, field) + case "data": + return ec.fieldContext_OutputData_data(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type OutputData", field.Name) }, } defer func() { @@ -4055,15 +4438,89 @@ func (ec *executionContext) fieldContext_Query___type(ctx context.Context, field } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query___type_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Query_statisticsQuery_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } return fc, nil } -func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query___schema(ctx, field) +func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query___type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.introspectType(fc.Args["name"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*introspection.Type) + fc.Result = res + return ec.marshalO__Type2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query___type(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "kind": + return ec.fieldContext___Type_kind(ctx, field) + case "name": + return ec.fieldContext___Type_name(ctx, field) + case "description": + return ec.fieldContext___Type_description(ctx, field) + case "fields": + return ec.fieldContext___Type_fields(ctx, field) + case "interfaces": + return ec.fieldContext___Type_interfaces(ctx, field) + case "possibleTypes": + return ec.fieldContext___Type_possibleTypes(ctx, field) + case "enumValues": + return ec.fieldContext___Type_enumValues(ctx, field) + case "inputFields": + return ec.fieldContext___Type_inputFields(ctx, field) + case "ofType": + return ec.fieldContext___Type_ofType(ctx, field) + case "specifiedByURL": + return ec.fieldContext___Type_specifiedByURL(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type __Type", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query___type_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query___schema(ctx, field) if err != nil { return graphql.Null } @@ -4749,6 +5206,276 @@ func (ec *executionContext) fieldContext_SDType_parameters(_ context.Context, fi return fc, nil } +func (ec *executionContext) _SensorField_key(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.SensorField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SensorField_key(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Key, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_SensorField_key(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SensorField", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _SensorField_values(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.SensorField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SensorField_values(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Values, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]string) + fc.Result = res + return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_SensorField_values(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SensorField", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _SensorsWithFields_sensors(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.SensorsWithFields) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SensorsWithFields_sensors(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Sensors, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]graphQLModel.SensorField) + fc.Result = res + return ec.marshalNSensorField2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_SensorsWithFields_sensors(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SensorsWithFields", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "key": + return ec.fieldContext_SensorField_key(ctx, field) + case "values": + return ec.fieldContext_SensorField_values(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type SensorField", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _SimpleSensors_sensors(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.SimpleSensors) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SimpleSensors_sensors(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Sensors, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]string) + fc.Result = res + return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_SimpleSensors_sensors(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SimpleSensors", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _StatisticsField_key(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StatisticsField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_StatisticsField_key(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Key, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_StatisticsField_key(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "StatisticsField", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _StatisticsField_value(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StatisticsField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_StatisticsField_value(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Value, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNStatisticsParameterValue2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_StatisticsField_value(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "StatisticsField", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type StatisticsParameterValue does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _StringEQAtomKPINode_id(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { fc, err := ec.fieldContext_StringEQAtomKPINode_id(ctx, field) if err != nil { @@ -6915,6 +7642,54 @@ func (ec *executionContext) fieldContext___Type_specifiedByURL(_ context.Context // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputInputData(ctx context.Context, obj interface{}) (graphQLModel.InputData, error) { + var it graphQLModel.InputData + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"time", "deviceId", "deviceType", "data"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "time": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("time")) + data, err := ec.unmarshalNDate2string(ctx, v) + if err != nil { + return it, err + } + it.Time = data + case "deviceId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("deviceId")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.DeviceID = data + case "deviceType": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("deviceType")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.DeviceType = data + case "data": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("data")) + data, err := ec.unmarshalNStatisticsFieldInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsFieldInput(ctx, v) + if err != nil { + return it, err + } + it.Data = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputKPIDefinitionInput(ctx context.Context, obj interface{}) (graphQLModel.KPIDefinitionInput, error) { var it graphQLModel.KPIDefinitionInput asMap := map[string]interface{}{} @@ -7121,75 +7896,239 @@ func (ec *executionContext) unmarshalInputSDInstanceUpdateInput(ctx context.Cont if err != nil { return it, err } - it.ConfirmedByUser = data + it.ConfirmedByUser = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputSDParameterInput(ctx context.Context, obj interface{}) (graphQLModel.SDParameterInput, error) { + var it graphQLModel.SDParameterInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"denotation", "type"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "denotation": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("denotation")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Denotation = data + case "type": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("type")) + data, err := ec.unmarshalNSDParameterType2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSDParameterType(ctx, v) + if err != nil { + return it, err + } + it.Type = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputSDTypeInput(ctx context.Context, obj interface{}) (graphQLModel.SDTypeInput, error) { + var it graphQLModel.SDTypeInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"denotation", "parameters"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "denotation": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("denotation")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Denotation = data + case "parameters": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("parameters")) + data, err := ec.unmarshalNSDParameterInput2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSDParameterInputᚄ(ctx, v) + if err != nil { + return it, err + } + it.Parameters = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputSensorFieldInput(ctx context.Context, obj interface{}) (graphQLModel.SensorFieldInput, error) { + var it graphQLModel.SensorFieldInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"key", "fields"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "key": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("key")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.Key = data + case "fields": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fields")) + data, err := ec.unmarshalNString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + it.Fields = data + } + } + + return it, nil +} + +func (ec *executionContext) unmarshalInputSensorsInput(ctx context.Context, obj interface{}) (graphQLModel.SensorsInput, error) { + var it graphQLModel.SensorsInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"simpleSensors", "sensorsWithFields"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "simpleSensors": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("simpleSensors")) + data, err := ec.unmarshalOString2ᚕᚖstring(ctx, v) + if err != nil { + return it, err + } + it.SimpleSensors = data + case "sensorsWithFields": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sensorsWithFields")) + data, err := ec.unmarshalOSensorFieldInput2ᚕᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldInput(ctx, v) + if err != nil { + return it, err + } + it.SensorsWithFields = data } } return it, nil } -func (ec *executionContext) unmarshalInputSDParameterInput(ctx context.Context, obj interface{}) (graphQLModel.SDParameterInput, error) { - var it graphQLModel.SDParameterInput +func (ec *executionContext) unmarshalInputStatisticsFieldInput(ctx context.Context, obj interface{}) (graphQLModel.StatisticsFieldInput, error) { + var it graphQLModel.StatisticsFieldInput asMap := map[string]interface{}{} for k, v := range obj.(map[string]interface{}) { asMap[k] = v } - fieldsInOrder := [...]string{"denotation", "type"} + fieldsInOrder := [...]string{"key", "value"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { - case "denotation": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("denotation")) + case "key": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("key")) data, err := ec.unmarshalNString2string(ctx, v) if err != nil { return it, err } - it.Denotation = data - case "type": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("type")) - data, err := ec.unmarshalNSDParameterType2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSDParameterType(ctx, v) + it.Key = data + case "value": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("value")) + data, err := ec.unmarshalNStatisticsParameterValue2string(ctx, v) if err != nil { return it, err } - it.Type = data + it.Value = data } } return it, nil } -func (ec *executionContext) unmarshalInputSDTypeInput(ctx context.Context, obj interface{}) (graphQLModel.SDTypeInput, error) { - var it graphQLModel.SDTypeInput +func (ec *executionContext) unmarshalInputStatisticsInput(ctx context.Context, obj interface{}) (graphQLModel.StatisticsInput, error) { + var it graphQLModel.StatisticsInput asMap := map[string]interface{}{} for k, v := range obj.(map[string]interface{}) { asMap[k] = v } - fieldsInOrder := [...]string{"denotation", "parameters"} + fieldsInOrder := [...]string{"sensors", "from", "to", "aggregateMinutes", "timezone", "operation"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { - case "denotation": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("denotation")) - data, err := ec.unmarshalNString2string(ctx, v) + case "sensors": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sensors")) + data, err := ec.unmarshalNSensorsInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorsInput(ctx, v) if err != nil { return it, err } - it.Denotation = data - case "parameters": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("parameters")) - data, err := ec.unmarshalNSDParameterInput2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSDParameterInputᚄ(ctx, v) + it.Sensors = data + case "from": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("from")) + data, err := ec.unmarshalODate2ᚖstring(ctx, v) if err != nil { return it, err } - it.Parameters = data + it.From = data + case "to": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("to")) + data, err := ec.unmarshalODate2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.To = data + case "aggregateMinutes": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("aggregateMinutes")) + data, err := ec.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + it.AggregateMinutes = data + case "timezone": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("timezone")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Timezone = data + case "operation": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("operation")) + data, err := ec.unmarshalOStatisticsOperation2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsOperation(ctx, v) + if err != nil { + return it, err + } + it.Operation = data } } @@ -7683,6 +8622,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "statisticsMutate": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_statisticsMutate(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -8011,6 +8957,57 @@ func (ec *executionContext) _NumericLTAtomKPINode(ctx context.Context, sel ast.S return out } +var outputDataImplementors = []string{"OutputData"} + +func (ec *executionContext) _OutputData(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.OutputData) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, outputDataImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("OutputData") + case "time": + out.Values[i] = ec._OutputData_time(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "deviceId": + out.Values[i] = ec._OutputData_deviceId(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "deviceType": + out.Values[i] = ec._OutputData_deviceType(ctx, field, obj) + case "data": + out.Values[i] = ec._OutputData_data(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var queryImplementors = []string{"Query"} func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { @@ -8205,6 +9202,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "statisticsQuery": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_statisticsQuery(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "__type": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { @@ -8345,29 +9364,200 @@ func (ec *executionContext) _SDInstanceGroup(ctx context.Context, sel ast.Select return out } -var sDParameterImplementors = []string{"SDParameter"} +var sDParameterImplementors = []string{"SDParameter"} + +func (ec *executionContext) _SDParameter(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDParameter) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, sDParameterImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("SDParameter") + case "id": + out.Values[i] = ec._SDParameter_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "denotation": + out.Values[i] = ec._SDParameter_denotation(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "type": + out.Values[i] = ec._SDParameter_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var sDTypeImplementors = []string{"SDType"} + +func (ec *executionContext) _SDType(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDType) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, sDTypeImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("SDType") + case "id": + out.Values[i] = ec._SDType_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "denotation": + out.Values[i] = ec._SDType_denotation(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "parameters": + out.Values[i] = ec._SDType_parameters(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var sensorFieldImplementors = []string{"SensorField"} + +func (ec *executionContext) _SensorField(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SensorField) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, sensorFieldImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("SensorField") + case "key": + out.Values[i] = ec._SensorField_key(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "values": + out.Values[i] = ec._SensorField_values(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var sensorsWithFieldsImplementors = []string{"SensorsWithFields"} + +func (ec *executionContext) _SensorsWithFields(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SensorsWithFields) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, sensorsWithFieldsImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("SensorsWithFields") + case "sensors": + out.Values[i] = ec._SensorsWithFields_sensors(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var simpleSensorsImplementors = []string{"SimpleSensors"} -func (ec *executionContext) _SDParameter(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDParameter) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, sDParameterImplementors) +func (ec *executionContext) _SimpleSensors(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SimpleSensors) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, simpleSensorsImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("SDParameter") - case "id": - out.Values[i] = ec._SDParameter_id(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "denotation": - out.Values[i] = ec._SDParameter_denotation(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "type": - out.Values[i] = ec._SDParameter_type(ctx, field, obj) + out.Values[i] = graphql.MarshalString("SimpleSensors") + case "sensors": + out.Values[i] = ec._SimpleSensors_sensors(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } @@ -8394,29 +9584,24 @@ func (ec *executionContext) _SDParameter(ctx context.Context, sel ast.SelectionS return out } -var sDTypeImplementors = []string{"SDType"} +var statisticsFieldImplementors = []string{"StatisticsField"} -func (ec *executionContext) _SDType(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDType) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, sDTypeImplementors) +func (ec *executionContext) _StatisticsField(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.StatisticsField) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, statisticsFieldImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("SDType") - case "id": - out.Values[i] = ec._SDType_id(ctx, field, obj) + out.Values[i] = graphql.MarshalString("StatisticsField") + case "key": + out.Values[i] = ec._StatisticsField_key(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "denotation": - out.Values[i] = ec._SDType_denotation(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "parameters": - out.Values[i] = ec._SDType_parameters(ctx, field, obj) + case "value": + out.Values[i] = ec._StatisticsField_value(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } @@ -8867,6 +10052,21 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) unmarshalNDate2string(ctx context.Context, v interface{}) (string, error) { + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNDate2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalString(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + func (ec *executionContext) unmarshalNFloat2float64(ctx context.Context, v interface{}) (float64, error) { res, err := graphql.UnmarshalFloatContext(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -8929,6 +10129,11 @@ func (ec *executionContext) marshalNID2ᚕuint32ᚄ(ctx context.Context, sel ast return ret } +func (ec *executionContext) unmarshalNInputData2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐInputData(ctx context.Context, v interface{}) (graphQLModel.InputData, error) { + res, err := ec.unmarshalInputInputData(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalNKPIDefinition2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐKPIDefinition(ctx context.Context, sel ast.SelectionSet, v graphQLModel.KPIDefinition) graphql.Marshaler { return ec._KPIDefinition(ctx, sel, &v) } @@ -9130,6 +10335,54 @@ func (ec *executionContext) marshalNLogicalOperationType2githubᚗcomᚋMichalBu return v } +func (ec *executionContext) marshalNOutputData2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐOutputData(ctx context.Context, sel ast.SelectionSet, v graphQLModel.OutputData) graphql.Marshaler { + return ec._OutputData(ctx, sel, &v) +} + +func (ec *executionContext) marshalNOutputData2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐOutputDataᚄ(ctx context.Context, sel ast.SelectionSet, v []graphQLModel.OutputData) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNOutputData2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐOutputData(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) marshalNSDInstance2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSDInstance(ctx context.Context, sel ast.SelectionSet, v graphQLModel.SDInstance) graphql.Marshaler { return ec._SDInstance(ctx, sel, &v) } @@ -9379,6 +10632,88 @@ func (ec *executionContext) unmarshalNSDTypeInput2githubᚗcomᚋMichalBuresᚑO return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) marshalNSensorField2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorField(ctx context.Context, sel ast.SelectionSet, v graphQLModel.SensorField) graphql.Marshaler { + return ec._SensorField(ctx, sel, &v) +} + +func (ec *executionContext) marshalNSensorField2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldᚄ(ctx context.Context, sel ast.SelectionSet, v []graphQLModel.SensorField) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNSensorField2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorField(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) unmarshalNSensorsInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorsInput(ctx context.Context, v interface{}) (graphQLModel.SensorsInput, error) { + res, err := ec.unmarshalInputSensorsInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNStatisticsField2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsField(ctx context.Context, sel ast.SelectionSet, v graphQLModel.StatisticsField) graphql.Marshaler { + return ec._StatisticsField(ctx, sel, &v) +} + +func (ec *executionContext) unmarshalNStatisticsFieldInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsFieldInput(ctx context.Context, v interface{}) (graphQLModel.StatisticsFieldInput, error) { + res, err := ec.unmarshalInputStatisticsFieldInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) unmarshalNStatisticsInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsInput(ctx context.Context, v interface{}) (graphQLModel.StatisticsInput, error) { + res, err := ec.unmarshalInputStatisticsInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) unmarshalNStatisticsParameterValue2string(ctx context.Context, v interface{}) (string, error) { + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNStatisticsParameterValue2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalString(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) @@ -9705,6 +11040,22 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return res } +func (ec *executionContext) unmarshalODate2ᚖstring(ctx context.Context, v interface{}) (*string, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalString(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalODate2ᚖstring(ctx context.Context, sel ast.SelectionSet, v *string) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalString(*v) + return res +} + func (ec *executionContext) unmarshalOFloat2ᚖfloat64(ctx context.Context, v interface{}) (*float64, error) { if v == nil { return nil, nil @@ -9737,6 +11088,22 @@ func (ec *executionContext) marshalOID2ᚖuint32(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) unmarshalOInt2ᚖint(ctx context.Context, v interface{}) (*int, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalInt(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.SelectionSet, v *int) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalInt(*v) + return res +} + func (ec *executionContext) unmarshalOLogicalOperationType2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐLogicalOperationType(ctx context.Context, v interface{}) (*graphQLModel.LogicalOperationType, error) { if v == nil { return nil, nil @@ -9753,6 +11120,82 @@ func (ec *executionContext) marshalOLogicalOperationType2ᚖgithubᚗcomᚋMicha return v } +func (ec *executionContext) unmarshalOSensorFieldInput2ᚕᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldInput(ctx context.Context, v interface{}) ([]*graphQLModel.SensorFieldInput, error) { + if v == nil { + return nil, nil + } + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]*graphQLModel.SensorFieldInput, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalOSensorFieldInput2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldInput(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) unmarshalOSensorFieldInput2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldInput(ctx context.Context, v interface{}) (*graphQLModel.SensorFieldInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputSensorFieldInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) unmarshalOStatisticsOperation2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsOperation(ctx context.Context, v interface{}) (*graphQLModel.StatisticsOperation, error) { + if v == nil { + return nil, nil + } + var res = new(graphQLModel.StatisticsOperation) + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOStatisticsOperation2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsOperation(ctx context.Context, sel ast.SelectionSet, v *graphQLModel.StatisticsOperation) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return v +} + +func (ec *executionContext) unmarshalOString2ᚕᚖstring(ctx context.Context, v interface{}) ([]*string, error) { + if v == nil { + return nil, nil + } + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]*string, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalOString2ᚖstring(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalOString2ᚕᚖstring(ctx context.Context, sel ast.SelectionSet, v []*string) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalOString2ᚖstring(ctx, sel, v[i]) + } + + return ret +} + func (ec *executionContext) unmarshalOString2ᚖstring(ctx context.Context, v interface{}) (*string, error) { if v == nil { return nil, nil diff --git a/backend/backend-core/src/api/graphql/schema.graphqls b/backend/backend-core/src/api/graphql/schema.graphqls index 8b7db18..6f90255 100644 --- a/backend/backend-core/src/api/graphql/schema.graphqls +++ b/backend/backend-core/src/api/graphql/schema.graphqls @@ -205,6 +205,125 @@ input SDInstanceGroupInput { sdInstanceIDs: [ID!]! } +# ----- Statistics + +scalar Date + +type SimpleSensors { + sensors: [String!]! +} + +type SensorsWithFields { + sensors: [SensorField!]! +} + +type SensorField { + key: String! + values: [String!]! +} + +enum StatisticsOperation { + MEAN + MIN + MAX + FIRST + SUM + LAST + NONE + COUNT + INTEGRAL + MEDIAN + MODE + QUANTILE + REDUCE + SKEW + SPREAD + STDDEV + TIMEWEIGHTEDAVG +} + +""" +Data used for querying the selected bucket +""" +input StatisticsInput { + """ + Sensors to be queried + """ + sensors: SensorsInput! + """ + Start of the querying window + """ + from: Date + """ + End of the querying window + """ + to: Date + """ + Amount of minutes to aggregate by + For example if the queried range has 1 hour and aggregateMinutes is set to 10 the aggregation will result in 6 points + """ + aggregateMinutes: Int + """ + Timezone override default UTC. + For more details why and how this affects queries see: https://www.influxdata.com/blog/time-zones-in-flux/. + In most cases you can ignore this and some edge aggregations can be influenced. + If you need a precise result or the aggregation uses high amount of minutes provide the target time zone. + """ + timezone: String + """ + Aggregation operator to use, if needed + """ + operation: StatisticsOperation +} + +""" +Sensors to be queried +""" +input SensorsInput { + """ + Simple definition, returns all available sensor fields + """ + simpleSensors: [String] + """ + Return only the requested sensor fields + """ + sensorsWithFields: [SensorFieldInput] +} + +""" +Return only the requested sensor fields +""" +input SensorFieldInput { + key: String! + fields: [String!]! +} + +scalar StatisticsParameterValue + +type StatisticsField { + key: String! + value: StatisticsParameterValue! +} + +type OutputData { + time: Date! + deviceId: String! + deviceType: String + data: StatisticsField! +} + +input StatisticsFieldInput { + key: String! + value: StatisticsParameterValue! +} + +input InputData { + time: Date! + deviceId: String! + deviceType: String + data: StatisticsFieldInput! +} + # ----- Queries, mutations and subscriptions ----- type Query { @@ -216,6 +335,7 @@ type Query { kpiFulfillmentCheckResults: [KPIFulfillmentCheckResult!]! sdInstanceGroup(id: ID!): SDInstanceGroup! sdInstanceGroups: [SDInstanceGroup!]! + statisticsQuery(request: StatisticsInput!): [OutputData!]! } type Mutation { @@ -228,6 +348,7 @@ type Mutation { createSDInstanceGroup(input: SDInstanceGroupInput!): SDInstanceGroup! updateSDInstanceGroup(id: ID!, input: SDInstanceGroupInput!): SDInstanceGroup! deleteSDInstanceGroup(id: ID!): Boolean! + statisticsMutate(inputData: InputData!): Boolean! } type Subscription { diff --git a/backend/backend-core/src/api/graphql/schema.resolvers.go b/backend/backend-core/src/api/graphql/schema.resolvers.go index b895dc6..c45bc52 100644 --- a/backend/backend-core/src/api/graphql/schema.resolvers.go +++ b/backend/backend-core/src/api/graphql/schema.resolvers.go @@ -81,6 +81,10 @@ func (r *mutationResolver) DeleteSDInstanceGroup(ctx context.Context, id uint32) return true, nil } +func (r *mutationResolver) StatisticsMutate(ctx context.Context, inputData graphQLModel.InputData) (bool, error) { + return domainLogicLayer.Save(inputData).Unwrap() +} + func (r *queryResolver) SdType(ctx context.Context, id uint32) (graphQLModel.SDType, error) { getSDTypeResult := domainLogicLayer.GetSDType(id) if getSDTypeResult.IsFailure() { @@ -145,6 +149,11 @@ func (r *queryResolver) SdInstanceGroups(ctx context.Context) ([]graphQLModel.SD return getSDInstanceGroupsResult.Unwrap() } +func (r *queryResolver) StatisticsQuery(ctx context.Context, request graphQLModel.StatisticsInput) ([]graphQLModel.OutputData, error) { + data := domainLogicLayer.Query(request) + return data.Unwrap() +} + func (r *subscriptionResolver) OnSDInstanceRegistered(ctx context.Context) (<-chan graphQLModel.SDInstance, error) { return SDInstanceGraphQLSubscriptionChannel, nil } diff --git a/backend/backend-core/src/domainLogicLayer/statistics.go b/backend/backend-core/src/domainLogicLayer/statistics.go new file mode 100644 index 0000000..732d7d9 --- /dev/null +++ b/backend/backend-core/src/domainLogicLayer/statistics.go @@ -0,0 +1,55 @@ +package domainLogicLayer + +import ( + "github.com/MichalBures-OG/bp-bures-RIoT-backend-core/src/model/graphQLModel" + "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/rabbitmq" + "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedConstants" + "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" + amqp "github.com/rabbitmq/amqp091-go" + "math/rand" + "time" +) + +func randomString(l int) string { + rand.Seed(time.Now().UnixNano()) + bytes := make([]byte, l) + for i := 0; i < l; i++ { + bytes[i] = byte(randInt(65, 90)) + } + return string(bytes) +} + +func randInt(min int, max int) int { + return min + rand.Intn(max-min) +} + +func Query(input graphQLModel.StatisticsInput) sharedUtils.Result[[]graphQLModel.OutputData] { + rabbitMQClient := getDLLRabbitMQClient() + + correlationId := randomString(32) + + var output sharedUtils.Result[[]graphQLModel.OutputData] + + err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[[]graphQLModel.OutputData](rabbitMQClient, sharedConstants.TimeSeriesReadRequestQueueName, "", func(outputData []graphQLModel.OutputData, delivery amqp.Delivery) error { + output = sharedUtils.NewSuccessResult[[]graphQLModel.OutputData](outputData) + return nil + }) + + if err != nil { + sharedUtils.NewFailureResult[graphQLModel.KPIDefinition](err) + } + + err = rabbitMQClient.PublishJSONMessageRPC(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), sharedUtils.SerializeToJSON(input).GetPayload(), correlationId) + + if err != nil { + sharedUtils.NewFailureResult[graphQLModel.KPIDefinition](err) + } + + return output +} + +func Save(input graphQLModel.InputData) sharedUtils.Result[bool] { + rabbitMQClient := getDLLRabbitMQClient() + rabbitMQClient.PublishJSONMessage(sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesStoreDataQueueName), sharedUtils.NewOptionalOf(""), sharedUtils.SerializeToJSON(input).GetPayload()) + return sharedUtils.NewSuccessResult[bool](true) +} diff --git a/backend/backend-core/src/model/dbModel/model.go b/backend/backend-core/src/model/dbModel/model.go index d4af206..9184a8a 100644 --- a/backend/backend-core/src/model/dbModel/model.go +++ b/backend/backend-core/src/model/dbModel/model.go @@ -1,5 +1,10 @@ package dbModel +import ( + "gorm.io/gorm" + "time" +) + type KPIDefinitionEntity struct { ID uint32 `gorm:"column:id;primaryKey"` UserIdentifier string `gorm:"column:user_identifier;not null"` @@ -55,6 +60,7 @@ type SDTypeEntity struct { ID uint32 `gorm:"column:id;primaryKey;not null"` Denotation string `gorm:"column:denotation;not null;index"` // Denotation is a separately indexed field Parameters []SDParameterEntity `gorm:"foreignKey:SDTypeID;constraint:OnDelete:CASCADE"` + Commands []SDCommandEntity `gorm:"foreignKey:SDTypeID;constraint:OnDelete:CASCADE"` } func (SDTypeEntity) TableName() string { @@ -122,3 +128,57 @@ type SDInstanceKPIDefinitionRelationshipEntity struct { SDInstanceID uint32 `gorm:"column:sd_instance_id;primaryKey;not null"` SDInstanceUID string `gorm:"column:sd_instance_uid;not null"` } + +// UserEntity represents a user of the application who can log in using various OAuth providers. +type UserEntity struct { + ID uint `gorm:"primaryKey"` // Primary key for the user + CreatedAt time.Time // Timestamp of creation + UpdatedAt time.Time // Timestamp of the last update + DeletedAt gorm.DeletedAt `gorm:"index"` // Soft delete field + + // Basic User Information + Username string `gorm:"uniqueIndex;size:100"` // Unique username for the user + Email string `gorm:"uniqueIndex;size:255"` // User's email address (unique) + Name string `gorm:"size:255"` // Full name of the user + ProfileImage string `gorm:"size:500"` // URL to the user's profile image + + // OAuth Information + Provider string `gorm:"size:50"` // OAuth provider name (e.g., google, github) + ProviderID string `gorm:"size:255;index"` // Unique ID provided by the OAuth provider + OAuthToken string `gorm:"size:500"` // OAuth access token + RefreshToken string `gorm:"size:500"` // OAuth refresh token, if available + TokenExpiry time.Time // Expiration time of the OAuth token + + // Additional Metadata + LastLoginAt time.Time // Timestamp of the last login + IsActive bool `gorm:"default:true"` // Whether the user's account is active + Invocations []SDCommandInvocationEntity `gorm:"foreignKey:UserId;constraint:OnDelete:CASCADE"` +} + +func (UserEntity) TableName() string { + return "user" +} + +type SDCommandEntity struct { + ID uint32 `gorm:"column:id;primaryKey;not null"` + SDTypeID uint32 `gorm:"column:sd_type_id;not null"` + Denotation string `gorm:"column:denotation;not null"` + Type string `gorm:"column:type;not null"` + Payload string `gorm:"column:payload;not null"` + Invocations []SDCommandInvocationEntity `gorm:"foreignKey:SDCommandID;constraint:OnDelete:CASCADE"` +} + +func (SDCommandEntity) TableName() string { + return "command" +} + +type SDCommandInvocationEntity struct { + ID uint32 `gorm:"column:id;primaryKey;not null"` + InvocationTime time.Time + Payload string `gorm:"column:payload;not null"` + UserId uint32 `gorm:"column:user_id"` +} + +func (SDCommandInvocationEntity) TableName() string { + return "command_invocation" +} diff --git a/backend/backend-core/src/model/graphQLModel/graphQLModel.go b/backend/backend-core/src/model/graphQLModel/graphQLModel.go index 785e9ae..c654d2c 100644 --- a/backend/backend-core/src/model/graphQLModel/graphQLModel.go +++ b/backend/backend-core/src/model/graphQLModel/graphQLModel.go @@ -43,6 +43,13 @@ func (this BooleanEQAtomKPINode) GetSdParameterSpecification() string { return this.SdParameterSpecification } +type InputData struct { + Time string `json:"time"` + DeviceID string `json:"deviceId"` + DeviceType *string `json:"deviceType,omitempty"` + Data StatisticsFieldInput `json:"data"` +} + type KPIDefinition struct { ID uint32 `json:"id"` SdTypeID uint32 `json:"sdTypeID"` @@ -204,6 +211,13 @@ func (this NumericLTAtomKPINode) GetSdParameterSpecification() string { return this.SdParameterSpecification } +type OutputData struct { + Time string `json:"time"` + DeviceID string `json:"deviceId"` + DeviceType *string `json:"deviceType,omitempty"` + Data StatisticsField `json:"data"` +} + type Query struct { } @@ -253,6 +267,63 @@ type SDTypeInput struct { Parameters []SDParameterInput `json:"parameters"` } +type SensorField struct { + Key string `json:"key"` + Values []string `json:"values"` +} + +// Return only the requested sensor fields +type SensorFieldInput struct { + Key string `json:"key"` + Fields []string `json:"fields"` +} + +// Sensors to be queried +type SensorsInput struct { + // Simple definition, returns all available sensor fields + SimpleSensors []*string `json:"simpleSensors,omitempty"` + // Return only the requested sensor fields + SensorsWithFields []*SensorFieldInput `json:"sensorsWithFields,omitempty"` +} + +type SensorsWithFields struct { + Sensors []SensorField `json:"sensors"` +} + +type SimpleSensors struct { + Sensors []string `json:"sensors"` +} + +type StatisticsField struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type StatisticsFieldInput struct { + Key string `json:"key"` + Value string `json:"value"` +} + +// Data used for querying the selected bucket +type StatisticsInput struct { + // Sensors to be queried + Sensors SensorsInput `json:"sensors"` + // Start of the querying window + From *string `json:"from,omitempty"` + // End of the querying window + To *string `json:"to,omitempty"` + // Amount of minutes to aggregate by + // For example if the queried range has 1 hour and aggregateMinutes is set to 10 the aggregation will result in 6 points + AggregateMinutes *int `json:"aggregateMinutes,omitempty"` + // Timezone override default UTC. + // For more details why and how this affects queries see: https://www.influxdata.com/blog/time-zones-in-flux/. + // In most cases you can ignore this and some edge aggregations can be influenced. + // If you need a precise result or the aggregation uses high amount of minutes provide the target time zone. + Timezone *string `json:"timezone,omitempty"` + // Aggregation operator to use, if needed + Operation *StatisticsOperation `json:"operation,omitempty"` +} + type StringEQAtomKPINode struct { ID uint32 `json:"id"` ParentNodeID *uint32 `json:"parentNodeID,omitempty"` @@ -456,3 +527,74 @@ func (e *SDParameterType) UnmarshalGQL(v interface{}) error { func (e SDParameterType) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } + +type StatisticsOperation string + +const ( + StatisticsOperationMean StatisticsOperation = "MEAN" + StatisticsOperationMin StatisticsOperation = "MIN" + StatisticsOperationMax StatisticsOperation = "MAX" + StatisticsOperationFirst StatisticsOperation = "FIRST" + StatisticsOperationSum StatisticsOperation = "SUM" + StatisticsOperationLast StatisticsOperation = "LAST" + StatisticsOperationNone StatisticsOperation = "NONE" + StatisticsOperationCount StatisticsOperation = "COUNT" + StatisticsOperationIntegral StatisticsOperation = "INTEGRAL" + StatisticsOperationMedian StatisticsOperation = "MEDIAN" + StatisticsOperationMode StatisticsOperation = "MODE" + StatisticsOperationQuantile StatisticsOperation = "QUANTILE" + StatisticsOperationReduce StatisticsOperation = "REDUCE" + StatisticsOperationSkew StatisticsOperation = "SKEW" + StatisticsOperationSpread StatisticsOperation = "SPREAD" + StatisticsOperationStddev StatisticsOperation = "STDDEV" + StatisticsOperationTimeweightedavg StatisticsOperation = "TIMEWEIGHTEDAVG" +) + +var AllStatisticsOperation = []StatisticsOperation{ + StatisticsOperationMean, + StatisticsOperationMin, + StatisticsOperationMax, + StatisticsOperationFirst, + StatisticsOperationSum, + StatisticsOperationLast, + StatisticsOperationNone, + StatisticsOperationCount, + StatisticsOperationIntegral, + StatisticsOperationMedian, + StatisticsOperationMode, + StatisticsOperationQuantile, + StatisticsOperationReduce, + StatisticsOperationSkew, + StatisticsOperationSpread, + StatisticsOperationStddev, + StatisticsOperationTimeweightedavg, +} + +func (e StatisticsOperation) IsValid() bool { + switch e { + case StatisticsOperationMean, StatisticsOperationMin, StatisticsOperationMax, StatisticsOperationFirst, StatisticsOperationSum, StatisticsOperationLast, StatisticsOperationNone, StatisticsOperationCount, StatisticsOperationIntegral, StatisticsOperationMedian, StatisticsOperationMode, StatisticsOperationQuantile, StatisticsOperationReduce, StatisticsOperationSkew, StatisticsOperationSpread, StatisticsOperationStddev, StatisticsOperationTimeweightedavg: + return true + } + return false +} + +func (e StatisticsOperation) String() string { + return string(e) +} + +func (e *StatisticsOperation) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = StatisticsOperation(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid StatisticsOperation", str) + } + return nil +} + +func (e StatisticsOperation) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/backend/commons/src/rabbitmq/client.go b/backend/commons/src/rabbitmq/client.go index ed046a7..7028600 100644 --- a/backend/commons/src/rabbitmq/client.go +++ b/backend/commons/src/rabbitmq/client.go @@ -58,6 +58,7 @@ type Client interface { PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte, correlationId string) error DeclareQueue(queueName string) error SetupMessageConsumption(queueName string, messageConsumerFunction func(message amqp.Delivery) error) error + SetupMessageConsumptionWithCorrelationId(queueName string, correlationId string, messageConsumerFunction func(message amqp.Delivery) error) error Dispose() } @@ -125,6 +126,29 @@ func (c *ClientImpl) SetupMessageConsumption(queueName string, messageConsumerFu return nil } +func (c *ClientImpl) SetupMessageConsumptionWithCorrelationId(queueName string, correlationId string, messageConsumerFunction func(message amqp.Delivery) error) error { + if err := c.channel.Qos(1, 0, false); err != nil { + return err + } + messageChannel, err := c.channel.Consume(queueName, "", false, false, false, false, nil) + if err != nil { + return err + } + for message := range messageChannel { + if message.CorrelationId != correlationId { + continue + } + + if err := messageConsumerFunction(message); err != nil { + return err + } + if err := message.Ack(false); err != nil { + return err + } + } + return nil +} + func (c *ClientImpl) Dispose() { sharedUtils.LogPossibleErrorThenProceed(c.channel.Close(), "[RabbitMQ client] Failed to close a channel") getConnectionManagerInstance().release() @@ -144,8 +168,8 @@ func ConsumeJSONMessages[T any](client Client, queueName string, messagePayloadC }) } -func ConsumeJSONMessagesWithAccessToDelivery[T any](client Client, queueName string, messagePayloadConsumerFunction func(messagePayload T, delivery amqp.Delivery) error) error { - return client.SetupMessageConsumption(queueName, func(message amqp.Delivery) error { +func ConsumeJSONMessagesWithAccessToDelivery[T any](client Client, queueName string, correlationId string, messagePayloadConsumerFunction func(messagePayload T, delivery amqp.Delivery) error) error { + messageConsumerFunction := func(message amqp.Delivery) error { messageContentType := message.ContentType if messageContentType != "application/json" { return fmt.Errorf("incorrect message content type: %s", messageContentType) @@ -155,7 +179,13 @@ func ConsumeJSONMessagesWithAccessToDelivery[T any](client Client, queueName str return jsonDeserializationResult.GetError() } return messagePayloadConsumerFunction(jsonDeserializationResult.GetPayload(), message) - }) + } + + if correlationId == "" { + return client.SetupMessageConsumption(queueName, messageConsumerFunction) + } else { + return client.SetupMessageConsumptionWithCorrelationId(queueName, correlationId, messageConsumerFunction) + } } func ConsumeJSONMessagesFromFanoutExchange[T any](client Client, fanoutExchangeName string, messagePayloadConsumerFunction func(messagePayload T) error) error { diff --git a/backend/commons/src/sharedModel/time-series.go b/backend/commons/src/sharedModel/timeSeries.go similarity index 97% rename from backend/commons/src/sharedModel/time-series.go rename to backend/commons/src/sharedModel/timeSeries.go index 2bb848c..290392d 100644 --- a/backend/commons/src/sharedModel/time-series.go +++ b/backend/commons/src/sharedModel/timeSeries.go @@ -34,7 +34,7 @@ type OutputData struct { Time time.Time `json:"time"` // Time of the current data sample DeviceID string `json:"deviceId"` // Device identifier DeviceType string `json:"deviceType"` // Device type - Other map[string]interface{} `json:"-"` + Data map[string]interface{} `json:"data"` } // ReadRequestBody represents the request body for the statistics endpoint. @@ -144,10 +144,10 @@ func (r *ReadRequestBody) MarshalJSON() ([]byte, error) { // MarshalJSON marshals the OutputData struct to JSON. func (outputData OutputData) MarshalJSON() ([]byte, error) { - data := make(map[string]interface{}, len(outputData.Other)+5) + data := make(map[string]interface{}, len(outputData.Data)+5) // copy status fields - for k, v := range outputData.Other { + for k, v := range outputData.Data { data[k] = v } // add known keys diff --git a/backend/time-series-store/Dockerfile b/backend/time-series-store/Dockerfile new file mode 100644 index 0000000..7c2703c --- /dev/null +++ b/backend/time-series-store/Dockerfile @@ -0,0 +1,19 @@ +# Use an official Go runtime as a parent image +FROM golang:1.22-alpine +# Set the working directory inside the container +WORKDIR /app +# Copy the go.mod file, go.sum file, and src directory from time-series-store +COPY ./time-series-store/go.mod ./time-series-store/go.sum /app/time-series-store/ +COPY ./time-series-store/src /app/time-series-store/src +# Copy the go.mod file, go.sum file, and src directory from commons +COPY ./commons/go.mod ./commons/go.sum /app/commons/ +COPY ./commons/src /app/commons/src +# Change working directory to where the Go app's main module is located +WORKDIR /app/time-series-store +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN go mod tidy +RUN go mod download +# Build the Go app +RUN CGO_ENABLED=0 GOOS=linux go build -o RIoT-time-series-store src/main.go +# Run the executable +CMD ["/app/time-series-store/RIoT-time-series-store"] diff --git a/backend/time-series-store/internal/Influx2Client.go b/backend/time-series-store/internal/Influx2Client.go index d0aaeff..e0cb5cc 100644 --- a/backend/time-series-store/internal/Influx2Client.go +++ b/backend/time-series-store/internal/Influx2Client.go @@ -178,7 +178,7 @@ func mapToOutputData(influxOutput map[string]interface{}) sharedModel.OutputData delete(influxOutput, "deviceId") delete(influxOutput, "deviceType") - outputData.Other = influxOutput + outputData.Data = influxOutput return outputData } diff --git a/backend/time-series-store/cmd/main.go b/backend/time-series-store/src/main.go similarity index 96% rename from backend/time-series-store/cmd/main.go rename to backend/time-series-store/src/main.go index d7f5972..08d30d7 100644 --- a/backend/time-series-store/cmd/main.go +++ b/backend/time-series-store/src/main.go @@ -59,7 +59,7 @@ func consumeInputMessages(rabbitMQClient rabbitmq.Client, influx internal.Influx } func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2Client) error { - err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[sharedModel.ReadRequestBody](rabbitMQClient, sharedConstants.TimeSeriesReadRequestQueueName, func(readRequestBody sharedModel.ReadRequestBody, delivery amqp.Delivery) error { + err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[sharedModel.ReadRequestBody](rabbitMQClient, sharedConstants.TimeSeriesReadRequestQueueName, "", func(readRequestBody sharedModel.ReadRequestBody, delivery amqp.Delivery) error { data, retrieveDataError := influx.Query(readRequestBody) jsonData, _ := json.Marshal(data) diff --git a/docker-compose.yml b/docker-compose.yml index 5bca127..2c0d8cf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -89,6 +89,35 @@ services: ports: - "8081:80" + influxdb: + image: influxdb:2.7-alpine + restart: unless-stopped + ports: + - "8086:8086" + environment: + - DOCKER_INFLUXDB_INIT_MODE=setup + - DOCKER_INFLUXDB_INIT_USERNAME=user + - DOCKER_INFLUXDB_INIT_PASSWORD=password + - DOCKER_INFLUXDB_INIT_ORG=jiap + - DOCKER_INFLUXDB_INIT_BUCKET=jiap-time-series + - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=Fgp2ozMxmkYnUBkzwLpkx6ydOVXyQqF4-ZPctGjv8-xkirYPYRvoBtrpAHMCr_joYoJMOqZjl8djjuyOx-MR_A== + healthcheck: + test: [ "CMD", "influx", "ping" ] + interval: 5s + timeout: 5s + retries: 5 + + riot-time-series-store: + build: + context: ./backend + dockerfile: time-series-store/Dockerfile + environment: + BACKEND_CORE_URL: ${BACKEND_CORE_URL} + RABBITMQ_URL: ${RABBITMQ_URL} + depends_on: + - rabbitmq + - riot-backend-core # 'Backend-core' has to set up the 'RabbitMQ infrastructure' + rabbitmq: image: rabbitmq:3-management ports: From 338eaf673dd67753523d1d8e9a27cf002a028b37 Mon Sep 17 00:00:00 2001 From: Petr John Date: Fri, 13 Sep 2024 13:12:10 +0200 Subject: [PATCH 04/26] Feature #1: Move internal into src to fix docker build (cherry picked from commit d3aee80aa08ccde7867956528ca966c205ccb47f) --- backend/time-series-store/go.mod | 6 ++++-- backend/time-series-store/go.sum | 2 ++ .../time-series-store/{ => src}/internal/Influx2Client.go | 0 .../{ => src}/internal/TimeSeriesStoreEnvironment.go | 0 backend/time-series-store/src/main.go | 2 +- 5 files changed, 7 insertions(+), 3 deletions(-) rename backend/time-series-store/{ => src}/internal/Influx2Client.go (100%) rename backend/time-series-store/{ => src}/internal/TimeSeriesStoreEnvironment.go (100%) diff --git a/backend/time-series-store/go.mod b/backend/time-series-store/go.mod index b9a8f2f..6219fda 100644 --- a/backend/time-series-store/go.mod +++ b/backend/time-series-store/go.mod @@ -6,17 +6,19 @@ toolchain go1.22.4 require ( github.com/MichalBures-OG/bp-bures-RIoT-commons v0.0.0-00010101000000-000000000000 + github.com/goccy/go-json v0.10.2 github.com/influxdata/influxdb-client-go/v2 v2.13.0 + github.com/rabbitmq/amqp091-go v1.10.0 ) +replace github.com/MichalBures-OG/bp-bures-RIoT-commons => ./../commons + require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/oapi-codegen/runtime v1.0.0 // indirect - github.com/rabbitmq/amqp091-go v1.10.0 // indirect golang.org/x/net v0.17.0 // indirect ) -replace github.com/MichalBures-OG/bp-bures-RIoT-commons => ./../commons diff --git a/backend/time-series-store/go.sum b/backend/time-series-store/go.sum index 6dff3c4..ac0d0c7 100644 --- a/backend/time-series-store/go.sum +++ b/backend/time-series-store/go.sum @@ -5,6 +5,8 @@ github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvF github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM= diff --git a/backend/time-series-store/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go similarity index 100% rename from backend/time-series-store/internal/Influx2Client.go rename to backend/time-series-store/src/internal/Influx2Client.go diff --git a/backend/time-series-store/internal/TimeSeriesStoreEnvironment.go b/backend/time-series-store/src/internal/TimeSeriesStoreEnvironment.go similarity index 100% rename from backend/time-series-store/internal/TimeSeriesStoreEnvironment.go rename to backend/time-series-store/src/internal/TimeSeriesStoreEnvironment.go diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index 08d30d7..a3e595e 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -9,7 +9,7 @@ import ( "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" "github.com/goccy/go-json" amqp "github.com/rabbitmq/amqp091-go" - "github.com/xjohnp00/jiap/backend/shared/time-series-store/internal" + internal "github.com/xjohnp00/jiap/backend/shared/time-series-store/src/internal" "os" ) From bcb1965b888f4cbd635fdddf37d7492144080343 Mon Sep 17 00:00:00 2001 From: Petr John Date: Sat, 15 Feb 2025 18:02:13 +0100 Subject: [PATCH 05/26] Hotfix #1: Fix non-existing field being used (cherry picked from commit 12d862a7a8ee8d0de92dca4958fd80ed5ff45fe3) --- backend/backend-core/src/model/dbModel/model.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/backend-core/src/model/dbModel/model.go b/backend/backend-core/src/model/dbModel/model.go index 9184a8a..7ea8727 100644 --- a/backend/backend-core/src/model/dbModel/model.go +++ b/backend/backend-core/src/model/dbModel/model.go @@ -165,7 +165,7 @@ type SDCommandEntity struct { Denotation string `gorm:"column:denotation;not null"` Type string `gorm:"column:type;not null"` Payload string `gorm:"column:payload;not null"` - Invocations []SDCommandInvocationEntity `gorm:"foreignKey:SDCommandID;constraint:OnDelete:CASCADE"` + Invocations []SDCommandInvocationEntity `gorm:"foreignKey:ID;constraint:OnDelete:CASCADE"` } func (SDCommandEntity) TableName() string { From 7edeb3d0e83a875fa0f83aaa2877eebecbcbce7d Mon Sep 17 00:00:00 2001 From: Petr John Date: Mon, 17 Feb 2025 13:36:07 +0100 Subject: [PATCH 06/26] Hotfix: Fix postgres version on 16 (cherry picked from commit 6fd29fd43e640645e55e085eb7e7023752facb77) --- docker-compose.yml | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2c0d8cf..f35be72 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,7 +71,7 @@ services: - ./docker/mosquitto-log:/mosquitto/log postgres: - image: postgres:latest + image: postgres:16 environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} @@ -124,32 +124,3 @@ services: - "5672:5672" # Main (amqp) - "15672:15672" # Management - "15692:15692" # Prometheus - - grafana: - image: grafana/grafana-enterprise - volumes: - - ./docker/grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards - - ./docker/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources - - ./docker/grafana/dashboards:/var/lib/grafana/dashboards - ports: - - "3000:3000" - environment: - GF_SECURITY_ADMIN_USER: admin - GF_SECURITY_ADMIN_PASSWORD: password - - prometheus: - image: prom/prometheus - volumes: - - "./docker/prometheus.yml:/etc/prometheus/prometheus.yml" - ports: - - "9091:9090" - - mqtt-prometheus-exporter: - image: kpetrem/mqtt-exporter - ports: - - "9000:9000" - environment: - MQTT_ADDRESS: mosquitto - MQTT_TOPIC: topic - MQTT_USERNAME: admin - MQTT_PASSWORD: password From 41d216a241006c5f24bdf0364ba68dd146d6ff7d Mon Sep 17 00:00:00 2001 From: Petr John Date: Mon, 17 Feb 2025 13:40:46 +0100 Subject: [PATCH 07/26] Hotfix: Add influx info (cherry picked from commit 2f59678a45839cd3ed95e6595abfa63daa153b4b) --- docker-compose.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index f35be72..534f7d9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -114,6 +114,10 @@ services: environment: BACKEND_CORE_URL: ${BACKEND_CORE_URL} RABBITMQ_URL: ${RABBITMQ_URL} + INFLUX_ORGANIZATION: home + INFLUX_BUCKET: bucket + INFLUX_URL: http://influx:8086 + INFLUX_TOKEN: Fgp2ozMxmkYnUBkzwLpkx6ydOVXyQqF4-ZPctGjv8-xkirYPYRvoBtrpAHMCr_joYoJMOqZjl8djjuyOx-MR_A== depends_on: - rabbitmq - riot-backend-core # 'Backend-core' has to set up the 'RabbitMQ infrastructure' From 194da2f1354f57ee89dd254b8b169791ab17b2a7 Mon Sep 17 00:00:00 2001 From: Petr John Date: Thu, 27 Feb 2025 20:43:34 +0100 Subject: [PATCH 08/26] WIP Hotfix #1: Start fixing time series (cherry picked from commit 6e218541c140650405a174e4cbf69860029bb517) --- .../src/domainLogicLayer/statistics.go | 30 +++-- backend/backend-core/src/main.go | 2 +- backend/commons/src/rabbitmq/client.go | 1 + backend/commons/src/sharedModel/timeSeries.go | 86 ++++++------- .../src/internal/Influx2Client.go | 2 + backend/time-series-store/src/main.go | 21 +++- frontend/src/generated/graphql.ts | 114 ++++++++++++++++++ 7 files changed, 194 insertions(+), 62 deletions(-) diff --git a/backend/backend-core/src/domainLogicLayer/statistics.go b/backend/backend-core/src/domainLogicLayer/statistics.go index 732d7d9..69cad02 100644 --- a/backend/backend-core/src/domainLogicLayer/statistics.go +++ b/backend/backend-core/src/domainLogicLayer/statistics.go @@ -1,6 +1,7 @@ package domainLogicLayer import ( + "fmt" "github.com/MichalBures-OG/bp-bures-RIoT-backend-core/src/model/graphQLModel" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/rabbitmq" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedConstants" @@ -24,27 +25,40 @@ func randInt(min int, max int) int { } func Query(input graphQLModel.StatisticsInput) sharedUtils.Result[[]graphQLModel.OutputData] { + fmt.Println("Received input in statistics: ", input) + + request := sharedUtils.SerializeToJSON(input) + fmt.Println("Received input in request: ", input) + fmt.Printf("%s\n", request.GetPayload()) + rabbitMQClient := getDLLRabbitMQClient() correlationId := randomString(32) var output sharedUtils.Result[[]graphQLModel.OutputData] + done := make(chan struct{}) - err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[[]graphQLModel.OutputData](rabbitMQClient, sharedConstants.TimeSeriesReadRequestQueueName, "", func(outputData []graphQLModel.OutputData, delivery amqp.Delivery) error { - output = sharedUtils.NewSuccessResult[[]graphQLModel.OutputData](outputData) - return nil - }) + go func() { + err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[[]graphQLModel.OutputData](rabbitMQClient, sharedConstants.TimeSeriesReadRequestQueueName, correlationId, func(outputData []graphQLModel.OutputData, delivery amqp.Delivery) error { + output = sharedUtils.NewSuccessResult[[]graphQLModel.OutputData](outputData) + close(done) + return nil + }) - if err != nil { - sharedUtils.NewFailureResult[graphQLModel.KPIDefinition](err) - } + if err != nil { + close(done) + sharedUtils.NewFailureResult[graphQLModel.KPIDefinition](err) + } + }() - err = rabbitMQClient.PublishJSONMessageRPC(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), sharedUtils.SerializeToJSON(input).GetPayload(), correlationId) + err := rabbitMQClient.PublishJSONMessageRPC(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), request.GetPayload(), correlationId) if err != nil { sharedUtils.NewFailureResult[graphQLModel.KPIDefinition](err) } + <-done + return output } diff --git a/backend/backend-core/src/main.go b/backend/backend-core/src/main.go index d554b82..364044a 100644 --- a/backend/backend-core/src/main.go +++ b/backend/backend-core/src/main.go @@ -40,7 +40,7 @@ func main() { log.Println("Waiting for dependencies...") waitForDependencies() log.Println("Dependencies ready...") - sharedUtils.StartLoggingProfilingInformationPeriodically(time.Minute) + //sharedUtils.StartLoggingProfilingInformationPeriodically(time.Minute) kickstartISC() graphql.SetupGraphQLServer() } diff --git a/backend/commons/src/rabbitmq/client.go b/backend/commons/src/rabbitmq/client.go index 7028600..1d73eca 100644 --- a/backend/commons/src/rabbitmq/client.go +++ b/backend/commons/src/rabbitmq/client.go @@ -94,6 +94,7 @@ func (c *ClientImpl) PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Opti defer cancelFunction() exchangeName := exchangeNameOptional.GetPayloadOrDefault("") routingKey := routingKeyOptional.GetPayloadOrDefault("") + fmt.Printf("Publishing message with RPC: %s", messagePayload) return c.channel.PublishWithContext(ctx, exchangeName, routingKey, false, false, amqp.Publishing{ ContentType: "application/json", Body: messagePayload, diff --git a/backend/commons/src/sharedModel/timeSeries.go b/backend/commons/src/sharedModel/timeSeries.go index 290392d..67b5f7e 100644 --- a/backend/commons/src/sharedModel/timeSeries.go +++ b/backend/commons/src/sharedModel/timeSeries.go @@ -40,7 +40,7 @@ type OutputData struct { // ReadRequestBody represents the request body for the statistics endpoint. type ReadRequestBody struct { Sensors Sensors `json:"sensors"` - Operation Operation `json:"operation"` + Operation Operation `json:"operation,omitempty"` Timezone string `json:"timezone,omitempty"` From *time.Time `json:"from,omitempty"` To *time.Time `json:"to,omitempty"` @@ -67,69 +67,61 @@ func (outputData *OutputData) UnmarshalJSON(data []byte) error { return nil } -// UnmarshalJSON unmarshals JSON to the ReadRequestBody struct. +// UnmarshalJSON customizes the JSON unmarshaling for ReadRequestBody. func (r *ReadRequestBody) UnmarshalJSON(data []byte) error { type Alias ReadRequestBody aux := &struct { - Sensors interface{} `json:"sensors"` + From string `json:"from"` + To string `json:"to"` + Sensors json.RawMessage `json:"sensors"` // Delay parsing sensors Alias }{ Alias: (Alias)(*r), } - var z map[string]interface{} + // Unmarshal into aux + if err := json.Unmarshal(data, &aux); err != nil { + return err + } - if err := json.Unmarshal(data, &z); err != nil { - fmt.Println(string(data[:])) - panic(err) + // Parse time fields safely + if aux.From != "" { + fromTime, err := time.Parse(time.RFC3339, aux.From) + if err != nil { + return fmt.Errorf("invalid 'from' time format: %s", aux.From) + } + r.From = &fromTime } - convertedFrom, _ := time.Parse(time.RFC3339, z["from"].(string)) - convertedTo, _ := time.Parse(time.RFC3339, z["to"].(string)) + if aux.To != "" { + toTime, err := time.Parse(time.RFC3339, aux.To) + if err != nil { + return fmt.Errorf("invalid 'to' time format: %s", aux.To) + } + r.To = &toTime + } - r.From = &convertedFrom - r.To = &convertedTo - r.AggregateMinutes = int(z["aggregateMinutes"].(float64)) - r.Timezone = z["timezone"].(string) - r.Operation = Operation(z["operation"].(string)) + // Assign other fields + r.Operation = aux.Operation + r.Timezone = aux.Timezone + r.AggregateMinutes = aux.AggregateMinutes - if err := json.Unmarshal(data, &aux); err != nil { - return err + // Try to parse Sensors as SimpleSensors + var simpleSensors SimpleSensors + if err := json.Unmarshal(aux.Sensors, &simpleSensors); err == nil { + r.Sensors = simpleSensors + return nil } - switch v := aux.Sensors.(type) { - case map[string]interface{}: - sensorsWithFields := SensorsWithFields{} - for key, value := range v { - if values, ok := value.([]interface{}); ok { - fields := make([]string, len(values)) - for i, val := range values { - if field, ok := val.(string); ok { - fields[i] = field - } else { - return fmt.Errorf("unexpected field type: %T", val) - } - } - sensorsWithFields[key] = fields - } else { - return fmt.Errorf("unexpected sensor type: %T", value) - } - } + // Try to parse Sensors as SensorsWithFields + var sensorsWithFields SensorsWithFields + if err := json.Unmarshal(aux.Sensors, &sensorsWithFields); err == nil { r.Sensors = sensorsWithFields - case []interface{}: - simpleSensors := make([]string, len(v)) - for i, sensor := range v { - if s, ok := sensor.(string); ok { - simpleSensors[i] = s - } else { - return fmt.Errorf("unexpected sensor type: %T", sensor) - } - } - r.Sensors = SimpleSensors(simpleSensors) - default: - return fmt.Errorf("unsupported sensors type: %T", aux.Sensors) + return nil } - return nil + + // If neither format works, return an error + return fmt.Errorf("unsupported sensors format: %s", string(aux.Sensors)) } // MarshalJSON marshals ReadRequestBody to JSON. diff --git a/backend/time-series-store/src/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go index e0cb5cc..63524f7 100644 --- a/backend/time-series-store/src/internal/Influx2Client.go +++ b/backend/time-series-store/src/internal/Influx2Client.go @@ -65,6 +65,8 @@ func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) ([]sh outputData = append(outputData, mapToOutputData(result.Record().Values())) } + fmt.Printf("%s\n", outputData) + return outputData, nil } diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index a3e595e..4cbc07e 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -20,12 +20,16 @@ func main() { os.Exit(1) } + fmt.Println("Time Series Store Starting") + influx, influxErrors, _ := internal.NewInflux2Client(environment.InfluxUrl, environment.InfluxToken, environment.InfluxOrg, environment.InfluxBucket) rabbitMQClient := rabbitmq.NewClient() defer rabbitMQClient.Dispose() defer influx.Close() + fmt.Println("Time Series Store Ready") + sharedUtils.WaitForAll( func() { err := consumeInputMessages(rabbitMQClient, influx) @@ -61,17 +65,22 @@ func consumeInputMessages(rabbitMQClient rabbitmq.Client, influx internal.Influx func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2Client) error { err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[sharedModel.ReadRequestBody](rabbitMQClient, sharedConstants.TimeSeriesReadRequestQueueName, "", func(readRequestBody sharedModel.ReadRequestBody, delivery amqp.Delivery) error { data, retrieveDataError := influx.Query(readRequestBody) + fmt.Printf("NotMarshalled data %s", data) - jsonData, _ := json.Marshal(data) if retrieveDataError != nil { fmt.Println(retrieveDataError.Error()) - err := rabbitMQClient.PublishJSONMessageRPC(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), jsonData, delivery.ReplyTo) - - if err != nil { - return err - } return retrieveDataError } + + jsonData, _ := json.Marshal(data) + fmt.Printf("Marshalled data %s", jsonData) + + err := rabbitMQClient.PublishJSONMessageRPC(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), jsonData, delivery.ReplyTo) + + if err != nil { + fmt.Printf("Error: %s", err) + return err + } return nil }) return err diff --git a/frontend/src/generated/graphql.ts b/frontend/src/generated/graphql.ts index 5e41dde..e2014af 100644 --- a/frontend/src/generated/graphql.ts +++ b/frontend/src/generated/graphql.ts @@ -12,6 +12,8 @@ export type Scalars = { Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } + Date: { input: any; output: any; } + StatisticsParameterValue: { input: any; output: any; } }; export type AtomKpiNode = { @@ -32,6 +34,13 @@ export type BooleanEqAtomKpiNode = AtomKpiNode & KpiNode & { sdParameterSpecification: Scalars['String']['output']; }; +export type InputData = { + data: StatisticsFieldInput; + deviceId: Scalars['String']['input']; + deviceType?: InputMaybe; + time: Scalars['Date']['input']; +}; + export type KpiDefinition = { __typename?: 'KPIDefinition'; id: Scalars['ID']['output']; @@ -115,6 +124,7 @@ export type Mutation = { deleteKPIDefinition: Scalars['Boolean']['output']; deleteSDInstanceGroup: Scalars['Boolean']['output']; deleteSDType: Scalars['Boolean']['output']; + statisticsMutate: Scalars['Boolean']['output']; updateKPIDefinition: KpiDefinition; updateSDInstance: SdInstance; updateSDInstanceGroup: SdInstanceGroup; @@ -151,6 +161,11 @@ export type MutationDeleteSdTypeArgs = { }; +export type MutationStatisticsMutateArgs = { + inputData: InputData; +}; + + export type MutationUpdateKpiDefinitionArgs = { id: Scalars['ID']['input']; input: KpiDefinitionInput; @@ -218,6 +233,14 @@ export type NumericLtAtomKpiNode = AtomKpiNode & KpiNode & { sdParameterSpecification: Scalars['String']['output']; }; +export type OutputData = { + __typename?: 'OutputData'; + data: StatisticsField; + deviceId: Scalars['String']['output']; + deviceType?: Maybe; + time: Scalars['Date']['output']; +}; + export type Query = { __typename?: 'Query'; kpiDefinition: KpiDefinition; @@ -228,6 +251,7 @@ export type Query = { sdInstances: Array; sdType: SdType; sdTypes: Array; + statisticsQuery: Array; }; @@ -245,6 +269,11 @@ export type QuerySdTypeArgs = { id: Scalars['ID']['input']; }; + +export type QueryStatisticsQueryArgs = { + request: StatisticsInput; +}; + export type SdInstance = { __typename?: 'SDInstance'; confirmedByUser: Scalars['Boolean']['output']; @@ -306,6 +335,91 @@ export type SdTypeInput = { parameters: Array; }; +export type SensorField = { + __typename?: 'SensorField'; + key: Scalars['String']['output']; + values: Array; +}; + +/** Return only the requested sensor fields */ +export type SensorFieldInput = { + fields: Array; + key: Scalars['String']['input']; +}; + +/** Sensors to be queried */ +export type SensorsInput = { + /** Return only the requested sensor fields */ + sensorsWithFields?: InputMaybe>>; + /** Simple definition, returns all available sensor fields */ + simpleSensors?: InputMaybe>>; +}; + +export type SensorsWithFields = { + __typename?: 'SensorsWithFields'; + sensors: Array; +}; + +export type SimpleSensors = { + __typename?: 'SimpleSensors'; + sensors: Array; +}; + +export type StatisticsField = { + __typename?: 'StatisticsField'; + key: Scalars['String']['output']; + value: Scalars['StatisticsParameterValue']['output']; +}; + +export type StatisticsFieldInput = { + key: Scalars['String']['input']; + value: Scalars['StatisticsParameterValue']['input']; +}; + +/** Data used for querying the selected bucket */ +export type StatisticsInput = { + /** + * Amount of minutes to aggregate by + * For example if the queried range has 1 hour and aggregateMinutes is set to 10 the aggregation will result in 6 points + */ + aggregateMinutes?: InputMaybe; + /** Start of the querying window */ + from?: InputMaybe; + /** Aggregation operator to use, if needed */ + operation?: InputMaybe; + /** Sensors to be queried */ + sensors: SensorsInput; + /** + * Timezone override default UTC. + * For more details why and how this affects queries see: https://www.influxdata.com/blog/time-zones-in-flux/. + * In most cases you can ignore this and some edge aggregations can be influenced. + * If you need a precise result or the aggregation uses high amount of minutes provide the target time zone. + */ + timezone?: InputMaybe; + /** End of the querying window */ + to?: InputMaybe; +}; + +export enum StatisticsOperation { + Count = 'COUNT', + First = 'FIRST', + Integral = 'INTEGRAL', + Last = 'LAST', + Max = 'MAX', + Mean = 'MEAN', + Median = 'MEDIAN', + Min = 'MIN', + Mode = 'MODE', + None = 'NONE', + Quantile = 'QUANTILE', + Reduce = 'REDUCE', + Skew = 'SKEW', + Spread = 'SPREAD', + Stddev = 'STDDEV', + Sum = 'SUM', + Timeweightedavg = 'TIMEWEIGHTEDAVG' +} + export type StringEqAtomKpiNode = AtomKpiNode & KpiNode & { __typename?: 'StringEQAtomKPINode'; id: Scalars['ID']['output']; From 65136dda4bf00495fcf035361e89f7942b9dfd53 Mon Sep 17 00:00:00 2001 From: Petr John Date: Fri, 28 Feb 2025 09:42:22 +0100 Subject: [PATCH 09/26] WIP Hotfix #1: Remove custom marshall / unmarshall for OutputData (cherry picked from commit 100ede6d6a68b90683610de7f44f00e51820832f) --- backend/commons/src/sharedModel/timeSeries.go | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/backend/commons/src/sharedModel/timeSeries.go b/backend/commons/src/sharedModel/timeSeries.go index 67b5f7e..3121bf8 100644 --- a/backend/commons/src/sharedModel/timeSeries.go +++ b/backend/commons/src/sharedModel/timeSeries.go @@ -47,26 +47,6 @@ type ReadRequestBody struct { AggregateMinutes int `json:"aggregateMinutes,omitempty"` } -// UnmarshalJSON unmarshals JSON to the OutputData struct. -func (outputData *OutputData) UnmarshalJSON(data []byte) error { - type Alias OutputData - aux := &struct { - Time string `json:"time"` - *Alias - }{ - Alias: (*Alias)(outputData), - } - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - parsedTime, err := time.Parse(time.RFC3339, aux.Time) - if err != nil { - return err - } - outputData.Time = parsedTime - return nil -} - // UnmarshalJSON customizes the JSON unmarshaling for ReadRequestBody. func (r *ReadRequestBody) UnmarshalJSON(data []byte) error { type Alias ReadRequestBody @@ -134,24 +114,6 @@ func (r *ReadRequestBody) MarshalJSON() ([]byte, error) { return json.Marshal(&struct{ Alias }{Alias: (Alias)(*r)}) } -// MarshalJSON marshals the OutputData struct to JSON. -func (outputData OutputData) MarshalJSON() ([]byte, error) { - data := make(map[string]interface{}, len(outputData.Data)+5) - - // copy status fields - for k, v := range outputData.Data { - data[k] = v - } - // add known keys - data["time"] = outputData.Time.Format(time.RFC3339) - data["deviceId"] = outputData.DeviceID - data["deviceType"] = outputData.DeviceType - data["result"] = outputData.Result - data["table"] = outputData.Table - - return json.Marshal(data) -} - // MarshalJSON marshals SimpleSensors to JSON. func (simpleSensors SimpleSensors) MarshalJSON() ([]byte, error) { return json.Marshal([]string(simpleSensors)) From 6efbd727d094c128d3cbb6c1ebe5ab642bebc979 Mon Sep 17 00:00:00 2001 From: Petr John Date: Fri, 28 Feb 2025 09:42:54 +0100 Subject: [PATCH 10/26] WIP Hotfix #1: Declare empty slice using a literal (cherry picked from commit 89a92cf5fbb7f669e8fc3abe0d6c882059c4e5b9) --- backend/time-series-store/src/internal/Influx2Client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/time-series-store/src/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go index 63524f7..36bbf37 100644 --- a/backend/time-series-store/src/internal/Influx2Client.go +++ b/backend/time-series-store/src/internal/Influx2Client.go @@ -55,7 +55,7 @@ func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) ([]sh return nil, err } - var outputData []sharedModel.OutputData + outputData := []sharedModel.OutputData{} if result.Err() != nil { return nil, result.Err() From bd0551054cfa1f789bbc1c51b0076316de942f2f Mon Sep 17 00:00:00 2001 From: Petr John Date: Fri, 28 Feb 2025 09:46:39 +0100 Subject: [PATCH 11/26] WIP Hotfix #1: Start debugging rpc not being correctly replied to (cherry picked from commit 8a4008f3c82aaa7865b869789ceb4847d883840c) --- backend/commons/src/rabbitmq/client.go | 3 ++- backend/time-series-store/go.mod | 1 - backend/time-series-store/src/main.go | 17 +++++++++++------ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/backend/commons/src/rabbitmq/client.go b/backend/commons/src/rabbitmq/client.go index 1d73eca..80f4e16 100644 --- a/backend/commons/src/rabbitmq/client.go +++ b/backend/commons/src/rabbitmq/client.go @@ -90,11 +90,12 @@ func (c *ClientImpl) PublishJSONMessage(exchangeNameOptional sharedUtils.Optiona } func (c *ClientImpl) PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte, correlationId string) error { + fmt.Printf("Publishing JSON RPC message %s\n", messagePayload) ctx, cancelFunction := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFunction() exchangeName := exchangeNameOptional.GetPayloadOrDefault("") routingKey := routingKeyOptional.GetPayloadOrDefault("") - fmt.Printf("Publishing message with RPC: %s", messagePayload) + fmt.Printf("Publishing message with RPC: %s\n", messagePayload) return c.channel.PublishWithContext(ctx, exchangeName, routingKey, false, false, amqp.Publishing{ ContentType: "application/json", Body: messagePayload, diff --git a/backend/time-series-store/go.mod b/backend/time-series-store/go.mod index 6219fda..f702561 100644 --- a/backend/time-series-store/go.mod +++ b/backend/time-series-store/go.mod @@ -6,7 +6,6 @@ toolchain go1.22.4 require ( github.com/MichalBures-OG/bp-bures-RIoT-commons v0.0.0-00010101000000-000000000000 - github.com/goccy/go-json v0.10.2 github.com/influxdata/influxdb-client-go/v2 v2.13.0 github.com/rabbitmq/amqp091-go v1.10.0 ) diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index 4cbc07e..5e5fb4c 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -1,15 +1,15 @@ package main import ( + "encoding/json" "flag" "fmt" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/rabbitmq" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedConstants" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedModel" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" - "github.com/goccy/go-json" amqp "github.com/rabbitmq/amqp091-go" - internal "github.com/xjohnp00/jiap/backend/shared/time-series-store/src/internal" + "github.com/xjohnp00/jiap/backend/shared/time-series-store/src/internal" "os" ) @@ -65,17 +65,22 @@ func consumeInputMessages(rabbitMQClient rabbitmq.Client, influx internal.Influx func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2Client) error { err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[sharedModel.ReadRequestBody](rabbitMQClient, sharedConstants.TimeSeriesReadRequestQueueName, "", func(readRequestBody sharedModel.ReadRequestBody, delivery amqp.Delivery) error { data, retrieveDataError := influx.Query(readRequestBody) - fmt.Printf("NotMarshalled data %s", data) + fmt.Printf("NotMarshalled data %s\n", data) if retrieveDataError != nil { fmt.Println(retrieveDataError.Error()) return retrieveDataError } - jsonData, _ := json.Marshal(data) - fmt.Printf("Marshalled data %s", jsonData) + jsonData, err := json.Marshal(data) + fmt.Printf("Marshalled data %s\n", jsonData) - err := rabbitMQClient.PublishJSONMessageRPC(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), jsonData, delivery.ReplyTo) + if err != nil { + fmt.Printf("Error During Marshall: %s", err) + return nil + } + + err = rabbitMQClient.PublishJSONMessageRPC(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), jsonData, delivery.CorrelationId) if err != nil { fmt.Printf("Error: %s", err) From 4adab8b3c3301ffefd1c3205f36f7b24a49259b0 Mon Sep 17 00:00:00 2001 From: Petr John Date: Fri, 28 Feb 2025 19:01:33 +0100 Subject: [PATCH 12/26] Feature #1: Fix incorrect time series store setup (cherry picked from commit 9b777a626893dffdfbfff239accfcf0eb7aae9e8) --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 534f7d9..3af6391 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -114,9 +114,9 @@ services: environment: BACKEND_CORE_URL: ${BACKEND_CORE_URL} RABBITMQ_URL: ${RABBITMQ_URL} - INFLUX_ORGANIZATION: home - INFLUX_BUCKET: bucket - INFLUX_URL: http://influx:8086 + INFLUX_ORGANIZATION: jiap + INFLUX_BUCKET: jiap-time-series + INFLUX_URL: http://influxdb:8086 INFLUX_TOKEN: Fgp2ozMxmkYnUBkzwLpkx6ydOVXyQqF4-ZPctGjv8-xkirYPYRvoBtrpAHMCr_joYoJMOqZjl8djjuyOx-MR_A== depends_on: - rabbitmq From 272c83d259992a47f3327c3dc3768b78b15ef5b3 Mon Sep 17 00:00:00 2001 From: Petr John Date: Fri, 28 Feb 2025 19:09:54 +0100 Subject: [PATCH 13/26] Feature #1: Fix RPC (cherry picked from commit 2fbb662989ddc8168e02904962a5f2c988ecd4f0) --- .../src/domainLogicLayer/statistics.go | 26 +++++---- backend/backend-core/src/isc/setup.go | 1 + backend/commons/src/rabbitmq/client.go | 23 ++++++-- .../src/sharedConstants/sharedConstants.go | 1 + backend/time-series-store/src/main.go | 53 +++++++++++-------- 5 files changed, 70 insertions(+), 34 deletions(-) diff --git a/backend/backend-core/src/domainLogicLayer/statistics.go b/backend/backend-core/src/domainLogicLayer/statistics.go index 69cad02..964046e 100644 --- a/backend/backend-core/src/domainLogicLayer/statistics.go +++ b/backend/backend-core/src/domainLogicLayer/statistics.go @@ -25,10 +25,7 @@ func randInt(min int, max int) int { } func Query(input graphQLModel.StatisticsInput) sharedUtils.Result[[]graphQLModel.OutputData] { - fmt.Println("Received input in statistics: ", input) - request := sharedUtils.SerializeToJSON(input) - fmt.Println("Received input in request: ", input) fmt.Printf("%s\n", request.GetPayload()) rabbitMQClient := getDLLRabbitMQClient() @@ -39,11 +36,16 @@ func Query(input graphQLModel.StatisticsInput) sharedUtils.Result[[]graphQLModel done := make(chan struct{}) go func() { - err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[[]graphQLModel.OutputData](rabbitMQClient, sharedConstants.TimeSeriesReadRequestQueueName, correlationId, func(outputData []graphQLModel.OutputData, delivery amqp.Delivery) error { - output = sharedUtils.NewSuccessResult[[]graphQLModel.OutputData](outputData) - close(done) - return nil - }) + err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[[]graphQLModel.OutputData]( + rabbitMQClient, + sharedConstants.TimeSeriesReadRequestBackendCoreResponseQueueName, + correlationId, + func(outputData []graphQLModel.OutputData, delivery amqp.Delivery) error { + output = sharedUtils.NewSuccessResult[[]graphQLModel.OutputData](outputData) + close(done) + return nil + }, + ) if err != nil { close(done) @@ -51,7 +53,13 @@ func Query(input graphQLModel.StatisticsInput) sharedUtils.Result[[]graphQLModel } }() - err := rabbitMQClient.PublishJSONMessageRPC(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), request.GetPayload(), correlationId) + err := rabbitMQClient.PublishJSONMessageRPC( + sharedUtils.NewEmptyOptional[string](), + sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), + request.GetPayload(), + correlationId, + sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestBackendCoreResponseQueueName), + ) if err != nil { sharedUtils.NewFailureResult[graphQLModel.KPIDefinition](err) diff --git a/backend/backend-core/src/isc/setup.go b/backend/backend-core/src/isc/setup.go index af8830a..363f433 100644 --- a/backend/backend-core/src/isc/setup.go +++ b/backend/backend-core/src/isc/setup.go @@ -21,6 +21,7 @@ func SetupRabbitMQInfrastructureForISC(rabbitMQClient rabbitmq.Client) { sharedConstants.MessageProcessingUnitConnectionNotificationsQueueName, sharedConstants.TimeSeriesStoreDataQueueName, sharedConstants.TimeSeriesReadRequestQueueName, + sharedConstants.TimeSeriesReadRequestBackendCoreResponseQueueName, ) sharedUtils.ForEach(namesOfQueuesToDeclare, func(nameOfQueuesToDeclare string) { sharedUtils.TerminateOnError(rabbitMQClient.DeclareQueue(nameOfQueuesToDeclare), getQueueDeclarationErrorMessage(nameOfQueuesToDeclare)) diff --git a/backend/commons/src/rabbitmq/client.go b/backend/commons/src/rabbitmq/client.go index 80f4e16..83f635c 100644 --- a/backend/commons/src/rabbitmq/client.go +++ b/backend/commons/src/rabbitmq/client.go @@ -55,7 +55,7 @@ func (c *connectionManager) release() { type Client interface { GetChannel() *amqp.Channel PublishJSONMessage(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte) error - PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte, correlationId string) error + PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte, correlationId string, replyTo sharedUtils.Optional[string]) error DeclareQueue(queueName string) error SetupMessageConsumption(queueName string, messageConsumerFunction func(message amqp.Delivery) error) error SetupMessageConsumptionWithCorrelationId(queueName string, correlationId string, messageConsumerFunction func(message amqp.Delivery) error) error @@ -89,18 +89,20 @@ func (c *ClientImpl) PublishJSONMessage(exchangeNameOptional sharedUtils.Optiona }) } -func (c *ClientImpl) PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte, correlationId string) error { +func (c *ClientImpl) PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte, correlationId string, replyToOptional sharedUtils.Optional[string]) error { fmt.Printf("Publishing JSON RPC message %s\n", messagePayload) ctx, cancelFunction := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFunction() exchangeName := exchangeNameOptional.GetPayloadOrDefault("") routingKey := routingKeyOptional.GetPayloadOrDefault("") + replyTo := replyToOptional.GetPayloadOrDefault("") + fmt.Printf("Publishing message with RPC: %s\n", messagePayload) return c.channel.PublishWithContext(ctx, exchangeName, routingKey, false, false, amqp.Publishing{ ContentType: "application/json", Body: messagePayload, CorrelationId: correlationId, - ReplyTo: routingKey, + ReplyTo: replyTo, }) } @@ -129,15 +131,23 @@ func (c *ClientImpl) SetupMessageConsumption(queueName string, messageConsumerFu } func (c *ClientImpl) SetupMessageConsumptionWithCorrelationId(queueName string, correlationId string, messageConsumerFunction func(message amqp.Delivery) error) error { + fmt.Printf("Waiting for message with correlationId %s\n", correlationId) + consumerName := fmt.Sprintf("%s-consumer", correlationId) if err := c.channel.Qos(1, 0, false); err != nil { return err } - messageChannel, err := c.channel.Consume(queueName, "", false, false, false, false, nil) + messageChannel, err := c.channel.Consume(queueName, consumerName, false, false, false, false, nil) if err != nil { return err } for message := range messageChannel { + fmt.Printf("Seeing message %s correlationId %s from a consumer %s\n", message.Body, message.CorrelationId, consumerName) if message.CorrelationId != correlationId { + // Return the message back to the queue if it has a wrong correlation ID + if err := message.Nack(false, true); err != nil { + return err + } + fmt.Println("Skipping") continue } @@ -147,6 +157,11 @@ func (c *ClientImpl) SetupMessageConsumptionWithCorrelationId(queueName string, if err := message.Ack(false); err != nil { return err } + + err := c.channel.Cancel(consumerName, true) + if err != nil { + return err + } } return nil } diff --git a/backend/commons/src/sharedConstants/sharedConstants.go b/backend/commons/src/sharedConstants/sharedConstants.go index b522bd0..4b6f788 100644 --- a/backend/commons/src/sharedConstants/sharedConstants.go +++ b/backend/commons/src/sharedConstants/sharedConstants.go @@ -10,4 +10,5 @@ const ( SetOfSDTypesUpdatesQueueName = "set-of-sd-types-updates" TimeSeriesStoreDataQueueName = "time-series-store-data" TimeSeriesReadRequestQueueName = "time-series-read-request" + TimeSeriesReadRequestBackendCoreResponseQueueName = "time-series-read-request-backend-core-response" ) diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index 5e5fb4c..e723721 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -63,31 +63,42 @@ func consumeInputMessages(rabbitMQClient rabbitmq.Client, influx internal.Influx } func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2Client) error { - err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[sharedModel.ReadRequestBody](rabbitMQClient, sharedConstants.TimeSeriesReadRequestQueueName, "", func(readRequestBody sharedModel.ReadRequestBody, delivery amqp.Delivery) error { - data, retrieveDataError := influx.Query(readRequestBody) - fmt.Printf("NotMarshalled data %s\n", data) - - if retrieveDataError != nil { - fmt.Println(retrieveDataError.Error()) - return retrieveDataError - } + err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[sharedModel.ReadRequestBody]( + rabbitMQClient, + sharedConstants.TimeSeriesReadRequestQueueName, + "", + func(readRequestBody sharedModel.ReadRequestBody, delivery amqp.Delivery) error { + data, retrieveDataError := influx.Query(readRequestBody) + fmt.Printf("NotMarshalled data %s\n", data) + + if retrieveDataError != nil { + fmt.Println(retrieveDataError.Error()) + return retrieveDataError + } - jsonData, err := json.Marshal(data) - fmt.Printf("Marshalled data %s\n", jsonData) + jsonData, err := json.Marshal(data) + fmt.Printf("Marshalled data %s\n", jsonData) - if err != nil { - fmt.Printf("Error During Marshall: %s", err) - return nil - } + if err != nil { + fmt.Printf("Error During Marshall: %s", err) + return nil + } - err = rabbitMQClient.PublishJSONMessageRPC(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesReadRequestQueueName), jsonData, delivery.CorrelationId) + err = rabbitMQClient.PublishJSONMessageRPC( + sharedUtils.NewEmptyOptional[string](), + sharedUtils.NewOptionalOf(delivery.ReplyTo), + jsonData, + delivery.CorrelationId, + sharedUtils.NewEmptyOptional[string](), + ) - if err != nil { - fmt.Printf("Error: %s", err) - return err - } - return nil - }) + if err != nil { + fmt.Printf("Error: %s", err) + return err + } + return nil + }, + ) return err } From 95bab1d511f338301493170654dcaa1555417eb0 Mon Sep 17 00:00:00 2001 From: Petr John Date: Fri, 28 Feb 2025 23:06:24 +0100 Subject: [PATCH 14/26] Feature #1: Fix data conversion (cherry picked from commit 5dfc9d68a741a8bf22fedbbd0c5ae681a7fd36f0) --- .../backend-core/src/api/graphql/gsc/gsc.go | 1197 +++++------------ .../src/api/graphql/schema.graphqls | 50 +- .../src/api/graphql/schema.resolvers.go | 11 +- .../src/domainLogicLayer/statistics.go | 132 +- .../src/model/graphQLModel/graphQLModel.go | 42 +- backend/commons/src/rabbitmq/client.go | 15 +- backend/commons/src/sharedModel/timeSeries.go | 10 +- .../src/internal/Influx2Client.go | 15 +- backend/time-series-store/src/main.go | 2 - frontend/src/generated/graphql.ts | 37 +- 10 files changed, 548 insertions(+), 963 deletions(-) diff --git a/backend/backend-core/src/api/graphql/gsc/gsc.go b/backend/backend-core/src/api/graphql/gsc/gsc.go index 83c085a..8f58108 100644 --- a/backend/backend-core/src/api/graphql/gsc/gsc.go +++ b/backend/backend-core/src/api/graphql/gsc/gsc.go @@ -71,7 +71,8 @@ type QueryResolver interface { KpiFulfillmentCheckResults(ctx context.Context) ([]graphQLModel.KPIFulfillmentCheckResult, error) SdInstanceGroup(ctx context.Context, id uint32) (graphQLModel.SDInstanceGroup, error) SdInstanceGroups(ctx context.Context) ([]graphQLModel.SDInstanceGroup, error) - StatisticsQuery(ctx context.Context, request graphQLModel.StatisticsInput) ([]graphQLModel.OutputData, error) + StatisticsQuerySimpleSensors(ctx context.Context, request *graphQLModel.StatisticsInput, sensors graphQLModel.SimpleSensors) ([]graphQLModel.OutputData, error) + StatisticsQuerySensorsWithFields(ctx context.Context, request *graphQLModel.StatisticsInput, sensors graphQLModel.SensorsWithFields) ([]graphQLModel.OutputData, error) } type SubscriptionResolver interface { OnSDInstanceRegistered(ctx context.Context) (<-chan graphQLModel.SDInstance, error) @@ -110,9 +111,9 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputSDInstanceUpdateInput, ec.unmarshalInputSDParameterInput, ec.unmarshalInputSDTypeInput, - ec.unmarshalInputSensorFieldInput, - ec.unmarshalInputSensorsInput, - ec.unmarshalInputStatisticsFieldInput, + ec.unmarshalInputSensorField, + ec.unmarshalInputSensorsWithFields, + ec.unmarshalInputSimpleSensors, ec.unmarshalInputStatisticsInput, ) first := true @@ -439,15 +440,15 @@ input SDInstanceGroupInput { scalar Date -type SimpleSensors { +input SimpleSensors { sensors: [String!]! } -type SensorsWithFields { +input SensorsWithFields { sensors: [SensorField!]! } -type SensorField { +input SensorField { key: String! values: [String!]! } @@ -476,10 +477,6 @@ enum StatisticsOperation { Data used for querying the selected bucket """ input StatisticsInput { - """ - Sensors to be queried - """ - sensors: SensorsInput! """ Start of the querying window """ @@ -506,52 +503,21 @@ input StatisticsInput { operation: StatisticsOperation } -""" -Sensors to be queried -""" -input SensorsInput { - """ - Simple definition, returns all available sensor fields - """ - simpleSensors: [String] - """ - Return only the requested sensor fields - """ - sensorsWithFields: [SensorFieldInput] -} - -""" -Return only the requested sensor fields -""" -input SensorFieldInput { - key: String! - fields: [String!]! -} - -scalar StatisticsParameterValue -type StatisticsField { - key: String! - value: StatisticsParameterValue! -} +scalar JSON type OutputData { time: Date! deviceId: String! deviceType: String - data: StatisticsField! -} - -input StatisticsFieldInput { - key: String! - value: StatisticsParameterValue! + data: JSON! } input InputData { time: Date! deviceId: String! deviceType: String - data: StatisticsFieldInput! + data: JSON! } # ----- Queries, mutations and subscriptions ----- @@ -565,7 +531,8 @@ type Query { kpiFulfillmentCheckResults: [KPIFulfillmentCheckResult!]! sdInstanceGroup(id: ID!): SDInstanceGroup! sdInstanceGroups: [SDInstanceGroup!]! - statisticsQuery(request: StatisticsInput!): [OutputData!]! + statisticsQuerySimpleSensors(request: StatisticsInput sensors: SimpleSensors!): [OutputData!]! + statisticsQuerySensorsWithFields(request: StatisticsInput sensors: SensorsWithFields!): [OutputData!]! } type Mutation { @@ -830,18 +797,51 @@ func (ec *executionContext) field_Query_sdType_args(ctx context.Context, rawArgs return args, nil } -func (ec *executionContext) field_Query_statisticsQuery_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { +func (ec *executionContext) field_Query_statisticsQuerySensorsWithFields_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *graphQLModel.StatisticsInput + if tmp, ok := rawArgs["request"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("request")) + arg0, err = ec.unmarshalOStatisticsInput2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["request"] = arg0 + var arg1 graphQLModel.SensorsWithFields + if tmp, ok := rawArgs["sensors"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sensors")) + arg1, err = ec.unmarshalNSensorsWithFields2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorsWithFields(ctx, tmp) + if err != nil { + return nil, err + } + } + args["sensors"] = arg1 + return args, nil +} + +func (ec *executionContext) field_Query_statisticsQuerySimpleSensors_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} - var arg0 graphQLModel.StatisticsInput + var arg0 *graphQLModel.StatisticsInput if tmp, ok := rawArgs["request"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("request")) - arg0, err = ec.unmarshalNStatisticsInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsInput(ctx, tmp) + arg0, err = ec.unmarshalOStatisticsInput2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsInput(ctx, tmp) if err != nil { return nil, err } } args["request"] = arg0 + var arg1 graphQLModel.SimpleSensors + if tmp, ok := rawArgs["sensors"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sensors")) + arg1, err = ec.unmarshalNSimpleSensors2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSimpleSensors(ctx, tmp) + if err != nil { + return nil, err + } + } + args["sensors"] = arg1 return args, nil } @@ -3887,9 +3887,9 @@ func (ec *executionContext) _OutputData_data(ctx context.Context, field graphql. } return graphql.Null } - res := resTmp.(graphQLModel.StatisticsField) + res := resTmp.(string) fc.Result = res - return ec.marshalNStatisticsField2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsField(ctx, field.Selections, res) + return ec.marshalNJSON2string(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_OutputData_data(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -3899,13 +3899,7 @@ func (ec *executionContext) fieldContext_OutputData_data(_ context.Context, fiel IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "key": - return ec.fieldContext_StatisticsField_key(ctx, field) - case "value": - return ec.fieldContext_StatisticsField_value(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type StatisticsField", field.Name) + return nil, errors.New("field of type JSON does not have child fields") }, } return fc, nil @@ -4380,8 +4374,73 @@ func (ec *executionContext) fieldContext_Query_sdInstanceGroups(_ context.Contex return fc, nil } -func (ec *executionContext) _Query_statisticsQuery(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query_statisticsQuery(ctx, field) +func (ec *executionContext) _Query_statisticsQuerySimpleSensors(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_statisticsQuerySimpleSensors(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().StatisticsQuerySimpleSensors(rctx, fc.Args["request"].(*graphQLModel.StatisticsInput), fc.Args["sensors"].(graphQLModel.SimpleSensors)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]graphQLModel.OutputData) + fc.Result = res + return ec.marshalNOutputData2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐOutputDataᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_statisticsQuerySimpleSensors(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "time": + return ec.fieldContext_OutputData_time(ctx, field) + case "deviceId": + return ec.fieldContext_OutputData_deviceId(ctx, field) + case "deviceType": + return ec.fieldContext_OutputData_deviceType(ctx, field) + case "data": + return ec.fieldContext_OutputData_data(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type OutputData", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_statisticsQuerySimpleSensors_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query_statisticsQuerySensorsWithFields(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_statisticsQuerySensorsWithFields(ctx, field) if err != nil { return graphql.Null } @@ -4394,7 +4453,7 @@ func (ec *executionContext) _Query_statisticsQuery(ctx context.Context, field gr }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().StatisticsQuery(rctx, fc.Args["request"].(graphQLModel.StatisticsInput)) + return ec.resolvers.Query().StatisticsQuerySensorsWithFields(rctx, fc.Args["request"].(*graphQLModel.StatisticsInput), fc.Args["sensors"].(graphQLModel.SensorsWithFields)) }) if err != nil { ec.Error(ctx, err) @@ -4411,7 +4470,7 @@ func (ec *executionContext) _Query_statisticsQuery(ctx context.Context, field gr return ec.marshalNOutputData2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐOutputDataᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Query_statisticsQuery(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Query_statisticsQuerySensorsWithFields(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Query", Field: field, @@ -4438,7 +4497,7 @@ func (ec *executionContext) fieldContext_Query_statisticsQuery(ctx context.Conte } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_statisticsQuery_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Query_statisticsQuerySensorsWithFields_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } @@ -5206,8 +5265,8 @@ func (ec *executionContext) fieldContext_SDType_parameters(_ context.Context, fi return fc, nil } -func (ec *executionContext) _SensorField_key(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.SensorField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_SensorField_key(ctx, field) +func (ec *executionContext) _StringEQAtomKPINode_id(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_StringEQAtomKPINode_id(ctx, field) if err != nil { return graphql.Null } @@ -5220,7 +5279,7 @@ func (ec *executionContext) _SensorField_key(ctx context.Context, field graphql. }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Key, nil + return obj.ID, nil }) if err != nil { ec.Error(ctx, err) @@ -5232,26 +5291,26 @@ func (ec *executionContext) _SensorField_key(ctx context.Context, field graphql. } return graphql.Null } - res := resTmp.(string) + res := resTmp.(uint32) fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) + return ec.marshalNID2uint32(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_SensorField_key(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_StringEQAtomKPINode_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "SensorField", + Object: "StringEQAtomKPINode", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type ID does not have child fields") }, } return fc, nil } -func (ec *executionContext) _SensorField_values(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.SensorField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_SensorField_values(ctx, field) +func (ec *executionContext) _StringEQAtomKPINode_parentNodeID(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_StringEQAtomKPINode_parentNodeID(ctx, field) if err != nil { return graphql.Null } @@ -5264,38 +5323,35 @@ func (ec *executionContext) _SensorField_values(ctx context.Context, field graph }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Values, nil + return obj.ParentNodeID, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.([]string) + res := resTmp.(*uint32) fc.Result = res - return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) + return ec.marshalOID2ᚖuint32(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_SensorField_values(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_StringEQAtomKPINode_parentNodeID(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "SensorField", + Object: "StringEQAtomKPINode", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type ID does not have child fields") }, } return fc, nil } -func (ec *executionContext) _SensorsWithFields_sensors(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.SensorsWithFields) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_SensorsWithFields_sensors(ctx, field) +func (ec *executionContext) _StringEQAtomKPINode_nodeType(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_StringEQAtomKPINode_nodeType(ctx, field) if err != nil { return graphql.Null } @@ -5308,7 +5364,7 @@ func (ec *executionContext) _SensorsWithFields_sensors(ctx context.Context, fiel }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Sensors, nil + return obj.NodeType, nil }) if err != nil { ec.Error(ctx, err) @@ -5320,32 +5376,26 @@ func (ec *executionContext) _SensorsWithFields_sensors(ctx context.Context, fiel } return graphql.Null } - res := resTmp.([]graphQLModel.SensorField) + res := resTmp.(graphQLModel.KPINodeType) fc.Result = res - return ec.marshalNSensorField2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldᚄ(ctx, field.Selections, res) + return ec.marshalNKPINodeType2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐKPINodeType(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_SensorsWithFields_sensors(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_StringEQAtomKPINode_nodeType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "SensorsWithFields", + Object: "StringEQAtomKPINode", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "key": - return ec.fieldContext_SensorField_key(ctx, field) - case "values": - return ec.fieldContext_SensorField_values(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type SensorField", field.Name) + return nil, errors.New("field of type KPINodeType does not have child fields") }, } return fc, nil } -func (ec *executionContext) _SimpleSensors_sensors(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.SimpleSensors) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_SimpleSensors_sensors(ctx, field) +func (ec *executionContext) _StringEQAtomKPINode_sdParameterID(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_StringEQAtomKPINode_sdParameterID(ctx, field) if err != nil { return graphql.Null } @@ -5358,7 +5408,7 @@ func (ec *executionContext) _SimpleSensors_sensors(ctx context.Context, field gr }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Sensors, nil + return obj.SdParameterID, nil }) if err != nil { ec.Error(ctx, err) @@ -5370,26 +5420,26 @@ func (ec *executionContext) _SimpleSensors_sensors(ctx context.Context, field gr } return graphql.Null } - res := resTmp.([]string) + res := resTmp.(uint32) fc.Result = res - return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) + return ec.marshalNID2uint32(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_SimpleSensors_sensors(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_StringEQAtomKPINode_sdParameterID(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "SimpleSensors", + Object: "StringEQAtomKPINode", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type ID does not have child fields") }, } return fc, nil } -func (ec *executionContext) _StatisticsField_key(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StatisticsField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_StatisticsField_key(ctx, field) +func (ec *executionContext) _StringEQAtomKPINode_sdParameterSpecification(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_StringEQAtomKPINode_sdParameterSpecification(ctx, field) if err != nil { return graphql.Null } @@ -5402,7 +5452,7 @@ func (ec *executionContext) _StatisticsField_key(ctx context.Context, field grap }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Key, nil + return obj.SdParameterSpecification, nil }) if err != nil { ec.Error(ctx, err) @@ -5419,9 +5469,9 @@ func (ec *executionContext) _StatisticsField_key(ctx context.Context, field grap return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_StatisticsField_key(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_StringEQAtomKPINode_sdParameterSpecification(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "StatisticsField", + Object: "StringEQAtomKPINode", Field: field, IsMethod: false, IsResolver: false, @@ -5432,8 +5482,8 @@ func (ec *executionContext) fieldContext_StatisticsField_key(_ context.Context, return fc, nil } -func (ec *executionContext) _StatisticsField_value(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StatisticsField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_StatisticsField_value(ctx, field) +func (ec *executionContext) _StringEQAtomKPINode_stringReferenceValue(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_StringEQAtomKPINode_stringReferenceValue(ctx, field) if err != nil { return graphql.Null } @@ -5446,7 +5496,7 @@ func (ec *executionContext) _StatisticsField_value(ctx context.Context, field gr }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Value, nil + return obj.StringReferenceValue, nil }) if err != nil { ec.Error(ctx, err) @@ -5460,399 +5510,138 @@ func (ec *executionContext) _StatisticsField_value(ctx context.Context, field gr } res := resTmp.(string) fc.Result = res - return ec.marshalNStatisticsParameterValue2string(ctx, field.Selections, res) + return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_StatisticsField_value(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_StringEQAtomKPINode_stringReferenceValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "StatisticsField", + Object: "StringEQAtomKPINode", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type StatisticsParameterValue does not have child fields") + return nil, errors.New("field of type String does not have child fields") }, } return fc, nil } -func (ec *executionContext) _StringEQAtomKPINode_id(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_StringEQAtomKPINode_id(ctx, field) +func (ec *executionContext) _Subscription_onSDInstanceRegistered(ctx context.Context, field graphql.CollectedField) (ret func(ctx context.Context) graphql.Marshaler) { + fc, err := ec.fieldContext_Subscription_onSDInstanceRegistered(ctx, field) if err != nil { - return graphql.Null + return nil } ctx = graphql.WithFieldContext(ctx, fc) defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null + ret = nil } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.ID, nil + return ec.resolvers.Subscription().OnSDInstanceRegistered(rctx) }) if err != nil { ec.Error(ctx, err) - return graphql.Null + return nil } if resTmp == nil { if !graphql.HasFieldError(ctx, fc) { ec.Errorf(ctx, "must not be null") } - return graphql.Null + return nil + } + return func(ctx context.Context) graphql.Marshaler { + select { + case res, ok := <-resTmp.(<-chan graphQLModel.SDInstance): + if !ok { + return nil + } + return graphql.WriterFunc(func(w io.Writer) { + w.Write([]byte{'{'}) + graphql.MarshalString(field.Alias).MarshalGQL(w) + w.Write([]byte{':'}) + ec.marshalNSDInstance2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSDInstance(ctx, field.Selections, res).MarshalGQL(w) + w.Write([]byte{'}'}) + }) + case <-ctx.Done(): + return nil + } } - res := resTmp.(uint32) - fc.Result = res - return ec.marshalNID2uint32(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_StringEQAtomKPINode_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Subscription_onSDInstanceRegistered(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "StringEQAtomKPINode", + Object: "Subscription", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type ID does not have child fields") + switch field.Name { + case "id": + return ec.fieldContext_SDInstance_id(ctx, field) + case "uid": + return ec.fieldContext_SDInstance_uid(ctx, field) + case "confirmedByUser": + return ec.fieldContext_SDInstance_confirmedByUser(ctx, field) + case "userIdentifier": + return ec.fieldContext_SDInstance_userIdentifier(ctx, field) + case "type": + return ec.fieldContext_SDInstance_type(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type SDInstance", field.Name) }, } return fc, nil } -func (ec *executionContext) _StringEQAtomKPINode_parentNodeID(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_StringEQAtomKPINode_parentNodeID(ctx, field) +func (ec *executionContext) _Subscription_onKPIFulfillmentChecked(ctx context.Context, field graphql.CollectedField) (ret func(ctx context.Context) graphql.Marshaler) { + fc, err := ec.fieldContext_Subscription_onKPIFulfillmentChecked(ctx, field) if err != nil { - return graphql.Null + return nil } ctx = graphql.WithFieldContext(ctx, fc) defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null + ret = nil } }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.ParentNodeID, nil + return ec.resolvers.Subscription().OnKPIFulfillmentChecked(rctx) }) if err != nil { ec.Error(ctx, err) - return graphql.Null + return nil } if resTmp == nil { - return graphql.Null + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return nil + } + return func(ctx context.Context) graphql.Marshaler { + select { + case res, ok := <-resTmp.(<-chan graphQLModel.KPIFulfillmentCheckResultTuple): + if !ok { + return nil + } + return graphql.WriterFunc(func(w io.Writer) { + w.Write([]byte{'{'}) + graphql.MarshalString(field.Alias).MarshalGQL(w) + w.Write([]byte{':'}) + ec.marshalNKPIFulfillmentCheckResultTuple2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐKPIFulfillmentCheckResultTuple(ctx, field.Selections, res).MarshalGQL(w) + w.Write([]byte{'}'}) + }) + case <-ctx.Done(): + return nil + } } - res := resTmp.(*uint32) - fc.Result = res - return ec.marshalOID2ᚖuint32(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_StringEQAtomKPINode_parentNodeID(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "StringEQAtomKPINode", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type ID does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _StringEQAtomKPINode_nodeType(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_StringEQAtomKPINode_nodeType(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.NodeType, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(graphQLModel.KPINodeType) - fc.Result = res - return ec.marshalNKPINodeType2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐKPINodeType(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_StringEQAtomKPINode_nodeType(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "StringEQAtomKPINode", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type KPINodeType does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _StringEQAtomKPINode_sdParameterID(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_StringEQAtomKPINode_sdParameterID(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.SdParameterID, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(uint32) - fc.Result = res - return ec.marshalNID2uint32(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_StringEQAtomKPINode_sdParameterID(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "StringEQAtomKPINode", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type ID does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _StringEQAtomKPINode_sdParameterSpecification(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_StringEQAtomKPINode_sdParameterSpecification(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.SdParameterSpecification, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_StringEQAtomKPINode_sdParameterSpecification(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "StringEQAtomKPINode", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _StringEQAtomKPINode_stringReferenceValue(ctx context.Context, field graphql.CollectedField, obj *graphQLModel.StringEQAtomKPINode) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_StringEQAtomKPINode_stringReferenceValue(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.StringReferenceValue, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_StringEQAtomKPINode_stringReferenceValue(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "StringEQAtomKPINode", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - -func (ec *executionContext) _Subscription_onSDInstanceRegistered(ctx context.Context, field graphql.CollectedField) (ret func(ctx context.Context) graphql.Marshaler) { - fc, err := ec.fieldContext_Subscription_onSDInstanceRegistered(ctx, field) - if err != nil { - return nil - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Subscription().OnSDInstanceRegistered(rctx) - }) - if err != nil { - ec.Error(ctx, err) - return nil - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return nil - } - return func(ctx context.Context) graphql.Marshaler { - select { - case res, ok := <-resTmp.(<-chan graphQLModel.SDInstance): - if !ok { - return nil - } - return graphql.WriterFunc(func(w io.Writer) { - w.Write([]byte{'{'}) - graphql.MarshalString(field.Alias).MarshalGQL(w) - w.Write([]byte{':'}) - ec.marshalNSDInstance2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSDInstance(ctx, field.Selections, res).MarshalGQL(w) - w.Write([]byte{'}'}) - }) - case <-ctx.Done(): - return nil - } - } -} - -func (ec *executionContext) fieldContext_Subscription_onSDInstanceRegistered(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Subscription", - Field: field, - IsMethod: true, - IsResolver: true, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "id": - return ec.fieldContext_SDInstance_id(ctx, field) - case "uid": - return ec.fieldContext_SDInstance_uid(ctx, field) - case "confirmedByUser": - return ec.fieldContext_SDInstance_confirmedByUser(ctx, field) - case "userIdentifier": - return ec.fieldContext_SDInstance_userIdentifier(ctx, field) - case "type": - return ec.fieldContext_SDInstance_type(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type SDInstance", field.Name) - }, - } - return fc, nil -} - -func (ec *executionContext) _Subscription_onKPIFulfillmentChecked(ctx context.Context, field graphql.CollectedField) (ret func(ctx context.Context) graphql.Marshaler) { - fc, err := ec.fieldContext_Subscription_onKPIFulfillmentChecked(ctx, field) - if err != nil { - return nil - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Subscription().OnKPIFulfillmentChecked(rctx) - }) - if err != nil { - ec.Error(ctx, err) - return nil - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return nil - } - return func(ctx context.Context) graphql.Marshaler { - select { - case res, ok := <-resTmp.(<-chan graphQLModel.KPIFulfillmentCheckResultTuple): - if !ok { - return nil - } - return graphql.WriterFunc(func(w io.Writer) { - w.Write([]byte{'{'}) - graphql.MarshalString(field.Alias).MarshalGQL(w) - w.Write([]byte{':'}) - ec.marshalNKPIFulfillmentCheckResultTuple2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐKPIFulfillmentCheckResultTuple(ctx, field.Selections, res).MarshalGQL(w) - w.Write([]byte{'}'}) - }) - case <-ctx.Done(): - return nil - } - } -} - -func (ec *executionContext) fieldContext_Subscription_onKPIFulfillmentChecked(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Subscription_onKPIFulfillmentChecked(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Subscription", Field: field, @@ -7679,7 +7468,7 @@ func (ec *executionContext) unmarshalInputInputData(ctx context.Context, obj int it.DeviceType = data case "data": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("data")) - data, err := ec.unmarshalNStatisticsFieldInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsFieldInput(ctx, v) + data, err := ec.unmarshalNJSON2string(ctx, v) if err != nil { return it, err } @@ -7971,14 +7760,14 @@ func (ec *executionContext) unmarshalInputSDTypeInput(ctx context.Context, obj i return it, nil } -func (ec *executionContext) unmarshalInputSensorFieldInput(ctx context.Context, obj interface{}) (graphQLModel.SensorFieldInput, error) { - var it graphQLModel.SensorFieldInput +func (ec *executionContext) unmarshalInputSensorField(ctx context.Context, obj interface{}) (graphQLModel.SensorField, error) { + var it graphQLModel.SensorField asMap := map[string]interface{}{} for k, v := range obj.(map[string]interface{}) { asMap[k] = v } - fieldsInOrder := [...]string{"key", "fields"} + fieldsInOrder := [...]string{"key", "values"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -7992,81 +7781,67 @@ func (ec *executionContext) unmarshalInputSensorFieldInput(ctx context.Context, return it, err } it.Key = data - case "fields": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fields")) + case "values": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("values")) data, err := ec.unmarshalNString2ᚕstringᚄ(ctx, v) if err != nil { return it, err } - it.Fields = data + it.Values = data } } return it, nil } -func (ec *executionContext) unmarshalInputSensorsInput(ctx context.Context, obj interface{}) (graphQLModel.SensorsInput, error) { - var it graphQLModel.SensorsInput +func (ec *executionContext) unmarshalInputSensorsWithFields(ctx context.Context, obj interface{}) (graphQLModel.SensorsWithFields, error) { + var it graphQLModel.SensorsWithFields asMap := map[string]interface{}{} for k, v := range obj.(map[string]interface{}) { asMap[k] = v } - fieldsInOrder := [...]string{"simpleSensors", "sensorsWithFields"} + fieldsInOrder := [...]string{"sensors"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { - case "simpleSensors": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("simpleSensors")) - data, err := ec.unmarshalOString2ᚕᚖstring(ctx, v) - if err != nil { - return it, err - } - it.SimpleSensors = data - case "sensorsWithFields": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sensorsWithFields")) - data, err := ec.unmarshalOSensorFieldInput2ᚕᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldInput(ctx, v) + case "sensors": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sensors")) + data, err := ec.unmarshalNSensorField2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldᚄ(ctx, v) if err != nil { return it, err } - it.SensorsWithFields = data + it.Sensors = data } } return it, nil } -func (ec *executionContext) unmarshalInputStatisticsFieldInput(ctx context.Context, obj interface{}) (graphQLModel.StatisticsFieldInput, error) { - var it graphQLModel.StatisticsFieldInput +func (ec *executionContext) unmarshalInputSimpleSensors(ctx context.Context, obj interface{}) (graphQLModel.SimpleSensors, error) { + var it graphQLModel.SimpleSensors asMap := map[string]interface{}{} for k, v := range obj.(map[string]interface{}) { asMap[k] = v } - fieldsInOrder := [...]string{"key", "value"} + fieldsInOrder := [...]string{"sensors"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { - case "key": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("key")) - data, err := ec.unmarshalNString2string(ctx, v) - if err != nil { - return it, err - } - it.Key = data - case "value": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("value")) - data, err := ec.unmarshalNStatisticsParameterValue2string(ctx, v) + case "sensors": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sensors")) + data, err := ec.unmarshalNString2ᚕstringᚄ(ctx, v) if err != nil { return it, err } - it.Value = data + it.Sensors = data } } @@ -8080,20 +7855,13 @@ func (ec *executionContext) unmarshalInputStatisticsInput(ctx context.Context, o asMap[k] = v } - fieldsInOrder := [...]string{"sensors", "from", "to", "aggregateMinutes", "timezone", "operation"} + fieldsInOrder := [...]string{"from", "to", "aggregateMinutes", "timezone", "operation"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { - case "sensors": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sensors")) - data, err := ec.unmarshalNSensorsInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorsInput(ctx, v) - if err != nil { - return it, err - } - it.Sensors = data case "from": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("from")) data, err := ec.unmarshalODate2ᚖstring(ctx, v) @@ -9203,7 +8971,29 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) - case "statisticsQuery": + case "statisticsQuerySimpleSensors": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_statisticsQuerySimpleSensors(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "statisticsQuerySensorsWithFields": field := field innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { @@ -9212,7 +9002,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_statisticsQuery(ctx, field) + res = ec._Query_statisticsQuerySensorsWithFields(ctx, field) if res == graphql.Null { atomic.AddUint32(&fs.Invalids, 1) } @@ -9230,117 +9020,9 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr return ec._Query___type(ctx, field) }) case "__schema": - out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { - return ec._Query___schema(ctx, field) - }) - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.deferred, int32(len(deferred))) - - for label, dfs := range deferred { - ec.processDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - -var sDInstanceImplementors = []string{"SDInstance"} - -func (ec *executionContext) _SDInstance(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDInstance) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, sDInstanceImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("SDInstance") - case "id": - out.Values[i] = ec._SDInstance_id(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "uid": - out.Values[i] = ec._SDInstance_uid(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "confirmedByUser": - out.Values[i] = ec._SDInstance_confirmedByUser(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "userIdentifier": - out.Values[i] = ec._SDInstance_userIdentifier(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "type": - out.Values[i] = ec._SDInstance_type(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.deferred, int32(len(deferred))) - - for label, dfs := range deferred { - ec.processDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - -var sDInstanceGroupImplementors = []string{"SDInstanceGroup"} - -func (ec *executionContext) _SDInstanceGroup(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDInstanceGroup) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, sDInstanceGroupImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("SDInstanceGroup") - case "id": - out.Values[i] = ec._SDInstanceGroup_id(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "userIdentifier": - out.Values[i] = ec._SDInstanceGroup_userIdentifier(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } - case "sdInstanceIDs": - out.Values[i] = ec._SDInstanceGroup_sdInstanceIDs(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Query___schema(ctx, field) + }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -9364,29 +9046,39 @@ func (ec *executionContext) _SDInstanceGroup(ctx context.Context, sel ast.Select return out } -var sDParameterImplementors = []string{"SDParameter"} +var sDInstanceImplementors = []string{"SDInstance"} -func (ec *executionContext) _SDParameter(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDParameter) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, sDParameterImplementors) +func (ec *executionContext) _SDInstance(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDInstance) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, sDInstanceImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("SDParameter") + out.Values[i] = graphql.MarshalString("SDInstance") case "id": - out.Values[i] = ec._SDParameter_id(ctx, field, obj) + out.Values[i] = ec._SDInstance_id(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "denotation": - out.Values[i] = ec._SDParameter_denotation(ctx, field, obj) + case "uid": + out.Values[i] = ec._SDInstance_uid(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "confirmedByUser": + out.Values[i] = ec._SDInstance_confirmedByUser(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "userIdentifier": + out.Values[i] = ec._SDInstance_userIdentifier(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } case "type": - out.Values[i] = ec._SDParameter_type(ctx, field, obj) + out.Values[i] = ec._SDInstance_type(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } @@ -9413,29 +9105,29 @@ func (ec *executionContext) _SDParameter(ctx context.Context, sel ast.SelectionS return out } -var sDTypeImplementors = []string{"SDType"} +var sDInstanceGroupImplementors = []string{"SDInstanceGroup"} -func (ec *executionContext) _SDType(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDType) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, sDTypeImplementors) +func (ec *executionContext) _SDInstanceGroup(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDInstanceGroup) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, sDInstanceGroupImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("SDType") + out.Values[i] = graphql.MarshalString("SDInstanceGroup") case "id": - out.Values[i] = ec._SDType_id(ctx, field, obj) + out.Values[i] = ec._SDInstanceGroup_id(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "denotation": - out.Values[i] = ec._SDType_denotation(ctx, field, obj) + case "userIdentifier": + out.Values[i] = ec._SDInstanceGroup_userIdentifier(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "parameters": - out.Values[i] = ec._SDType_parameters(ctx, field, obj) + case "sdInstanceIDs": + out.Values[i] = ec._SDInstanceGroup_sdInstanceIDs(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } @@ -9462,63 +9154,29 @@ func (ec *executionContext) _SDType(ctx context.Context, sel ast.SelectionSet, o return out } -var sensorFieldImplementors = []string{"SensorField"} +var sDParameterImplementors = []string{"SDParameter"} -func (ec *executionContext) _SensorField(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SensorField) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, sensorFieldImplementors) +func (ec *executionContext) _SDParameter(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDParameter) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, sDParameterImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("SensorField") - case "key": - out.Values[i] = ec._SensorField_key(ctx, field, obj) + out.Values[i] = graphql.MarshalString("SDParameter") + case "id": + out.Values[i] = ec._SDParameter_id(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "values": - out.Values[i] = ec._SensorField_values(ctx, field, obj) + case "denotation": + out.Values[i] = ec._SDParameter_denotation(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.deferred, int32(len(deferred))) - - for label, dfs := range deferred { - ec.processDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - -var sensorsWithFieldsImplementors = []string{"SensorsWithFields"} - -func (ec *executionContext) _SensorsWithFields(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SensorsWithFields) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, sensorsWithFieldsImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("SensorsWithFields") - case "sensors": - out.Values[i] = ec._SensorsWithFields_sensors(ctx, field, obj) + case "type": + out.Values[i] = ec._SDParameter_type(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } @@ -9545,63 +9203,29 @@ func (ec *executionContext) _SensorsWithFields(ctx context.Context, sel ast.Sele return out } -var simpleSensorsImplementors = []string{"SimpleSensors"} +var sDTypeImplementors = []string{"SDType"} -func (ec *executionContext) _SimpleSensors(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SimpleSensors) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, simpleSensorsImplementors) +func (ec *executionContext) _SDType(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.SDType) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, sDTypeImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("SimpleSensors") - case "sensors": - out.Values[i] = ec._SimpleSensors_sensors(ctx, field, obj) + out.Values[i] = graphql.MarshalString("SDType") + case "id": + out.Values[i] = ec._SDType_id(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - default: - panic("unknown field " + strconv.Quote(field.Name)) - } - } - out.Dispatch(ctx) - if out.Invalids > 0 { - return graphql.Null - } - - atomic.AddInt32(&ec.deferred, int32(len(deferred))) - - for label, dfs := range deferred { - ec.processDeferredGroup(graphql.DeferredGroup{ - Label: label, - Path: graphql.GetPath(ctx), - FieldSet: dfs, - Context: ctx, - }) - } - - return out -} - -var statisticsFieldImplementors = []string{"StatisticsField"} - -func (ec *executionContext) _StatisticsField(ctx context.Context, sel ast.SelectionSet, obj *graphQLModel.StatisticsField) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, statisticsFieldImplementors) - - out := graphql.NewFieldSet(fields) - deferred := make(map[string]*graphql.FieldSet) - for i, field := range fields { - switch field.Name { - case "__typename": - out.Values[i] = graphql.MarshalString("StatisticsField") - case "key": - out.Values[i] = ec._StatisticsField_key(ctx, field, obj) + case "denotation": + out.Values[i] = ec._SDType_denotation(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "value": - out.Values[i] = ec._StatisticsField_value(ctx, field, obj) + case "parameters": + out.Values[i] = ec._SDType_parameters(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } @@ -10134,6 +9758,21 @@ func (ec *executionContext) unmarshalNInputData2githubᚗcomᚋMichalBuresᚑOG return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalNJSON2string(ctx context.Context, v interface{}) (string, error) { + res, err := graphql.UnmarshalString(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNJSON2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { + res := graphql.MarshalString(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + func (ec *executionContext) marshalNKPIDefinition2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐKPIDefinition(ctx context.Context, sel ast.SelectionSet, v graphQLModel.KPIDefinition) graphql.Marshaler { return ec._KPIDefinition(ctx, sel, &v) } @@ -10632,88 +10271,38 @@ func (ec *executionContext) unmarshalNSDTypeInput2githubᚗcomᚋMichalBuresᚑO return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNSensorField2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorField(ctx context.Context, sel ast.SelectionSet, v graphQLModel.SensorField) graphql.Marshaler { - return ec._SensorField(ctx, sel, &v) +func (ec *executionContext) unmarshalNSensorField2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorField(ctx context.Context, v interface{}) (graphQLModel.SensorField, error) { + res, err := ec.unmarshalInputSensorField(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNSensorField2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldᚄ(ctx context.Context, sel ast.SelectionSet, v []graphQLModel.SensorField) graphql.Marshaler { - ret := make(graphql.Array, len(v)) - var wg sync.WaitGroup - isLen1 := len(v) == 1 - if !isLen1 { - wg.Add(len(v)) - } - for i := range v { - i := i - fc := &graphql.FieldContext{ - Index: &i, - Result: &v[i], - } - ctx := graphql.WithFieldContext(ctx, fc) - f := func(i int) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - if !isLen1 { - defer wg.Done() - } - ret[i] = ec.marshalNSensorField2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorField(ctx, sel, v[i]) - } - if isLen1 { - f(i) - } else { - go f(i) - } - +func (ec *executionContext) unmarshalNSensorField2ᚕgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldᚄ(ctx context.Context, v interface{}) ([]graphQLModel.SensorField, error) { + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) } - wg.Wait() - - for _, e := range ret { - if e == graphql.Null { - return graphql.Null + var err error + res := make([]graphQLModel.SensorField, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNSensorField2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorField(ctx, vSlice[i]) + if err != nil { + return nil, err } } - - return ret -} - -func (ec *executionContext) unmarshalNSensorsInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorsInput(ctx context.Context, v interface{}) (graphQLModel.SensorsInput, error) { - res, err := ec.unmarshalInputSensorsInput(ctx, v) - return res, graphql.ErrorOnPath(ctx, err) -} - -func (ec *executionContext) marshalNStatisticsField2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsField(ctx context.Context, sel ast.SelectionSet, v graphQLModel.StatisticsField) graphql.Marshaler { - return ec._StatisticsField(ctx, sel, &v) -} - -func (ec *executionContext) unmarshalNStatisticsFieldInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsFieldInput(ctx context.Context, v interface{}) (graphQLModel.StatisticsFieldInput, error) { - res, err := ec.unmarshalInputStatisticsFieldInput(ctx, v) - return res, graphql.ErrorOnPath(ctx, err) + return res, nil } -func (ec *executionContext) unmarshalNStatisticsInput2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsInput(ctx context.Context, v interface{}) (graphQLModel.StatisticsInput, error) { - res, err := ec.unmarshalInputStatisticsInput(ctx, v) +func (ec *executionContext) unmarshalNSensorsWithFields2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorsWithFields(ctx context.Context, v interface{}) (graphQLModel.SensorsWithFields, error) { + res, err := ec.unmarshalInputSensorsWithFields(ctx, v) return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalNStatisticsParameterValue2string(ctx context.Context, v interface{}) (string, error) { - res, err := graphql.UnmarshalString(v) +func (ec *executionContext) unmarshalNSimpleSensors2githubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSimpleSensors(ctx context.Context, v interface{}) (graphQLModel.SimpleSensors, error) { + res, err := ec.unmarshalInputSimpleSensors(ctx, v) return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNStatisticsParameterValue2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { - res := graphql.MarshalString(v) - if res == graphql.Null { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - ec.Errorf(ctx, "the requested element is null which the schema does not allow") - } - } - return res -} - func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) @@ -11120,31 +10709,11 @@ func (ec *executionContext) marshalOLogicalOperationType2ᚖgithubᚗcomᚋMicha return v } -func (ec *executionContext) unmarshalOSensorFieldInput2ᚕᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldInput(ctx context.Context, v interface{}) ([]*graphQLModel.SensorFieldInput, error) { - if v == nil { - return nil, nil - } - var vSlice []interface{} - if v != nil { - vSlice = graphql.CoerceList(v) - } - var err error - res := make([]*graphQLModel.SensorFieldInput, len(vSlice)) - for i := range vSlice { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) - res[i], err = ec.unmarshalOSensorFieldInput2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldInput(ctx, vSlice[i]) - if err != nil { - return nil, err - } - } - return res, nil -} - -func (ec *executionContext) unmarshalOSensorFieldInput2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐSensorFieldInput(ctx context.Context, v interface{}) (*graphQLModel.SensorFieldInput, error) { +func (ec *executionContext) unmarshalOStatisticsInput2ᚖgithubᚗcomᚋMichalBuresᚑOGᚋbpᚑburesᚑRIoTᚑbackendᚑcoreᚋsrcᚋmodelᚋgraphQLModelᚐStatisticsInput(ctx context.Context, v interface{}) (*graphQLModel.StatisticsInput, error) { if v == nil { return nil, nil } - res, err := ec.unmarshalInputSensorFieldInput(ctx, v) + res, err := ec.unmarshalInputStatisticsInput(ctx, v) return &res, graphql.ErrorOnPath(ctx, err) } @@ -11164,38 +10733,6 @@ func (ec *executionContext) marshalOStatisticsOperation2ᚖgithubᚗcomᚋMichal return v } -func (ec *executionContext) unmarshalOString2ᚕᚖstring(ctx context.Context, v interface{}) ([]*string, error) { - if v == nil { - return nil, nil - } - var vSlice []interface{} - if v != nil { - vSlice = graphql.CoerceList(v) - } - var err error - res := make([]*string, len(vSlice)) - for i := range vSlice { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) - res[i], err = ec.unmarshalOString2ᚖstring(ctx, vSlice[i]) - if err != nil { - return nil, err - } - } - return res, nil -} - -func (ec *executionContext) marshalOString2ᚕᚖstring(ctx context.Context, sel ast.SelectionSet, v []*string) graphql.Marshaler { - if v == nil { - return graphql.Null - } - ret := make(graphql.Array, len(v)) - for i := range v { - ret[i] = ec.marshalOString2ᚖstring(ctx, sel, v[i]) - } - - return ret -} - func (ec *executionContext) unmarshalOString2ᚖstring(ctx context.Context, v interface{}) (*string, error) { if v == nil { return nil, nil diff --git a/backend/backend-core/src/api/graphql/schema.graphqls b/backend/backend-core/src/api/graphql/schema.graphqls index 6f90255..fa0db76 100644 --- a/backend/backend-core/src/api/graphql/schema.graphqls +++ b/backend/backend-core/src/api/graphql/schema.graphqls @@ -209,15 +209,15 @@ input SDInstanceGroupInput { scalar Date -type SimpleSensors { +input SimpleSensors { sensors: [String!]! } -type SensorsWithFields { +input SensorsWithFields { sensors: [SensorField!]! } -type SensorField { +input SensorField { key: String! values: [String!]! } @@ -246,10 +246,6 @@ enum StatisticsOperation { Data used for querying the selected bucket """ input StatisticsInput { - """ - Sensors to be queried - """ - sensors: SensorsInput! """ Start of the querying window """ @@ -276,52 +272,21 @@ input StatisticsInput { operation: StatisticsOperation } -""" -Sensors to be queried -""" -input SensorsInput { - """ - Simple definition, returns all available sensor fields - """ - simpleSensors: [String] - """ - Return only the requested sensor fields - """ - sensorsWithFields: [SensorFieldInput] -} - -""" -Return only the requested sensor fields -""" -input SensorFieldInput { - key: String! - fields: [String!]! -} - -scalar StatisticsParameterValue -type StatisticsField { - key: String! - value: StatisticsParameterValue! -} +scalar JSON type OutputData { time: Date! deviceId: String! deviceType: String - data: StatisticsField! -} - -input StatisticsFieldInput { - key: String! - value: StatisticsParameterValue! + data: JSON! } input InputData { time: Date! deviceId: String! deviceType: String - data: StatisticsFieldInput! + data: JSON! } # ----- Queries, mutations and subscriptions ----- @@ -335,7 +300,8 @@ type Query { kpiFulfillmentCheckResults: [KPIFulfillmentCheckResult!]! sdInstanceGroup(id: ID!): SDInstanceGroup! sdInstanceGroups: [SDInstanceGroup!]! - statisticsQuery(request: StatisticsInput!): [OutputData!]! + statisticsQuerySimpleSensors(request: StatisticsInput sensors: SimpleSensors!): [OutputData!]! + statisticsQuerySensorsWithFields(request: StatisticsInput sensors: SensorsWithFields!): [OutputData!]! } type Mutation { diff --git a/backend/backend-core/src/api/graphql/schema.resolvers.go b/backend/backend-core/src/api/graphql/schema.resolvers.go index c45bc52..0fce5df 100644 --- a/backend/backend-core/src/api/graphql/schema.resolvers.go +++ b/backend/backend-core/src/api/graphql/schema.resolvers.go @@ -149,8 +149,15 @@ func (r *queryResolver) SdInstanceGroups(ctx context.Context) ([]graphQLModel.SD return getSDInstanceGroupsResult.Unwrap() } -func (r *queryResolver) StatisticsQuery(ctx context.Context, request graphQLModel.StatisticsInput) ([]graphQLModel.OutputData, error) { - data := domainLogicLayer.Query(request) +func (r *queryResolver) StatisticsQuerySimpleSensors(ctx context.Context, request *graphQLModel.StatisticsInput, sensors graphQLModel.SimpleSensors) ([]graphQLModel.OutputData, error) { + convertedRequest, _ := domainLogicLayer.MapStatisticsInputToReadRequestBody(request, &sensors, nil) + data := domainLogicLayer.Query(*convertedRequest) + return data.Unwrap() +} + +func (r *queryResolver) StatisticsQuerySensorsWithFields(ctx context.Context, request *graphQLModel.StatisticsInput, sensors graphQLModel.SensorsWithFields) ([]graphQLModel.OutputData, error) { + convertedRequest, _ := domainLogicLayer.MapStatisticsInputToReadRequestBody(request, nil, &sensors) + data := domainLogicLayer.Query(*convertedRequest) return data.Unwrap() } diff --git a/backend/backend-core/src/domainLogicLayer/statistics.go b/backend/backend-core/src/domainLogicLayer/statistics.go index 964046e..a875b23 100644 --- a/backend/backend-core/src/domainLogicLayer/statistics.go +++ b/backend/backend-core/src/domainLogicLayer/statistics.go @@ -1,10 +1,12 @@ package domainLogicLayer import ( + "encoding/json" "fmt" "github.com/MichalBures-OG/bp-bures-RIoT-backend-core/src/model/graphQLModel" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/rabbitmq" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedConstants" + "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedModel" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" amqp "github.com/rabbitmq/amqp091-go" "math/rand" @@ -24,32 +26,32 @@ func randInt(min int, max int) int { return min + rand.Intn(max-min) } -func Query(input graphQLModel.StatisticsInput) sharedUtils.Result[[]graphQLModel.OutputData] { +func Query(input sharedModel.ReadRequestBody) sharedUtils.Result[[]graphQLModel.OutputData] { request := sharedUtils.SerializeToJSON(input) - fmt.Printf("%s\n", request.GetPayload()) rabbitMQClient := getDLLRabbitMQClient() correlationId := randomString(32) - var output sharedUtils.Result[[]graphQLModel.OutputData] - done := make(chan struct{}) + outputChannel := make(chan sharedUtils.Result[[]sharedModel.OutputData]) go func() { - err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[[]graphQLModel.OutputData]( + err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[[]sharedModel.OutputData]( rabbitMQClient, sharedConstants.TimeSeriesReadRequestBackendCoreResponseQueueName, correlationId, - func(outputData []graphQLModel.OutputData, delivery amqp.Delivery) error { - output = sharedUtils.NewSuccessResult[[]graphQLModel.OutputData](outputData) - close(done) + func(outputData []sharedModel.OutputData, delivery amqp.Delivery) error { + output := sharedUtils.NewSuccessResult[[]sharedModel.OutputData](outputData) + outputChannel <- output + + close(outputChannel) return nil }, ) if err != nil { - close(done) - sharedUtils.NewFailureResult[graphQLModel.KPIDefinition](err) + close(outputChannel) + sharedUtils.NewFailureResult[[]graphQLModel.OutputData](err) } }() @@ -62,12 +64,24 @@ func Query(input graphQLModel.StatisticsInput) sharedUtils.Result[[]graphQLModel ) if err != nil { - sharedUtils.NewFailureResult[graphQLModel.KPIDefinition](err) + return sharedUtils.NewFailureResult[[]graphQLModel.OutputData](err) } - <-done + result := <-outputChannel + + if result.IsFailure() { + fmt.Println(result.GetError()) + return sharedUtils.NewFailureResult[[]graphQLModel.OutputData](err) + } + + convertedResult, err := ConvertOutputData(result.GetPayload()) + + if err != nil { + fmt.Println(err) + return sharedUtils.NewFailureResult[[]graphQLModel.OutputData](err) + } - return output + return sharedUtils.NewSuccessResult[[]graphQLModel.OutputData](convertedResult) } func Save(input graphQLModel.InputData) sharedUtils.Result[bool] { @@ -75,3 +89,95 @@ func Save(input graphQLModel.InputData) sharedUtils.Result[bool] { rabbitMQClient.PublishJSONMessage(sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesStoreDataQueueName), sharedUtils.NewOptionalOf(""), sharedUtils.SerializeToJSON(input).GetPayload()) return sharedUtils.NewSuccessResult[bool](true) } + +// MapStatisticsInputToReadRequestBody Refactored function with explicit parameters for SimpleSensors and SensorsWithFields +func MapStatisticsInputToReadRequestBody(statsInput *graphQLModel.StatisticsInput, simpleSensors *graphQLModel.SimpleSensors, sensorsWithFields *graphQLModel.SensorsWithFields) (*sharedModel.ReadRequestBody, error) { + readRequestBody := &sharedModel.ReadRequestBody{} + + // Check if SimpleSensors is provided + if simpleSensors != nil { + readRequestBody.Sensors = simpleSensors.Sensors + } else if sensorsWithFields != nil { + resultMap := make(map[string][]string) + + // Loop through each SensorField and add it to the result map + for _, sensor := range sensorsWithFields.Sensors { + resultMap[sensor.Key] = sensor.Values + } + + readRequestBody.Sensors = resultMap + } else { + return nil, fmt.Errorf("sensors are required, but neither SimpleSensors nor SensorsWithFields were provided") + } + + // If statsInput is not nil, copy the values + if statsInput != nil { + // Copy Operation if present + if statsInput.Operation != nil { + readRequestBody.Operation = sharedModel.Operation(*statsInput.Operation) + } + + // Copy Timezone if present + if statsInput.Timezone != nil { + readRequestBody.Timezone = *statsInput.Timezone + } + + // Copy AggregateMinutes if present + if statsInput.AggregateMinutes != nil { + readRequestBody.AggregateMinutes = *statsInput.AggregateMinutes + } + + // Convert From time (string to *time.Time) + if statsInput.From != nil { + parsedTime, err := time.Parse(time.RFC3339, *statsInput.From) + if err != nil { + return nil, fmt.Errorf("failed to parse From time: %v", err) + } + readRequestBody.From = &parsedTime + } + + // Convert To time (string to *time.Time) + if statsInput.To != nil { + parsedTime, err := time.Parse(time.RFC3339, *statsInput.To) + if err != nil { + return nil, fmt.Errorf("failed to parse To time: %v", err) + } + readRequestBody.To = &parsedTime + } + } + + return readRequestBody, nil +} + +// ConvertOutputData converts a slice of sharedModel.OutputData to a slice of graphQLModel.OutputData +func ConvertOutputData(sharedData []sharedModel.OutputData) ([]graphQLModel.OutputData, error) { + var result []graphQLModel.OutputData + + for _, item := range sharedData { + // Convert Time to string + timeString := item.Time.Format(time.RFC3339) + + // Convert Data map to JSON string + dataBytes, err := json.Marshal(item.Data) + if err != nil { + return nil, fmt.Errorf("error marshaling data: %v", err) + } + dataString := string(dataBytes) + + // Handle DeviceType (may be empty in sharedModel, so we use pointer in graphQLModel) + var deviceType *string + if item.DeviceType != "" { + deviceType = &item.DeviceType + } + + // Map to graphQLModel.OutputData + result = append(result, graphQLModel.OutputData{ + Time: timeString, + DeviceID: item.DeviceID, + DeviceType: deviceType, + Data: dataString, + }) + } + + return result, nil +} diff --git a/backend/backend-core/src/model/graphQLModel/graphQLModel.go b/backend/backend-core/src/model/graphQLModel/graphQLModel.go index c654d2c..333ea6b 100644 --- a/backend/backend-core/src/model/graphQLModel/graphQLModel.go +++ b/backend/backend-core/src/model/graphQLModel/graphQLModel.go @@ -44,10 +44,10 @@ func (this BooleanEQAtomKPINode) GetSdParameterSpecification() string { } type InputData struct { - Time string `json:"time"` - DeviceID string `json:"deviceId"` - DeviceType *string `json:"deviceType,omitempty"` - Data StatisticsFieldInput `json:"data"` + Time string `json:"time"` + DeviceID string `json:"deviceId"` + DeviceType *string `json:"deviceType,omitempty"` + Data string `json:"data"` } type KPIDefinition struct { @@ -212,10 +212,10 @@ func (this NumericLTAtomKPINode) GetSdParameterSpecification() string { } type OutputData struct { - Time string `json:"time"` - DeviceID string `json:"deviceId"` - DeviceType *string `json:"deviceType,omitempty"` - Data StatisticsField `json:"data"` + Time string `json:"time"` + DeviceID string `json:"deviceId"` + DeviceType *string `json:"deviceType,omitempty"` + Data string `json:"data"` } type Query struct { @@ -272,20 +272,6 @@ type SensorField struct { Values []string `json:"values"` } -// Return only the requested sensor fields -type SensorFieldInput struct { - Key string `json:"key"` - Fields []string `json:"fields"` -} - -// Sensors to be queried -type SensorsInput struct { - // Simple definition, returns all available sensor fields - SimpleSensors []*string `json:"simpleSensors,omitempty"` - // Return only the requested sensor fields - SensorsWithFields []*SensorFieldInput `json:"sensorsWithFields,omitempty"` -} - type SensorsWithFields struct { Sensors []SensorField `json:"sensors"` } @@ -294,20 +280,8 @@ type SimpleSensors struct { Sensors []string `json:"sensors"` } -type StatisticsField struct { - Key string `json:"key"` - Value string `json:"value"` -} - -type StatisticsFieldInput struct { - Key string `json:"key"` - Value string `json:"value"` -} - // Data used for querying the selected bucket type StatisticsInput struct { - // Sensors to be queried - Sensors SensorsInput `json:"sensors"` // Start of the querying window From *string `json:"from,omitempty"` // End of the querying window diff --git a/backend/commons/src/rabbitmq/client.go b/backend/commons/src/rabbitmq/client.go index 83f635c..812bab7 100644 --- a/backend/commons/src/rabbitmq/client.go +++ b/backend/commons/src/rabbitmq/client.go @@ -90,14 +90,12 @@ func (c *ClientImpl) PublishJSONMessage(exchangeNameOptional sharedUtils.Optiona } func (c *ClientImpl) PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte, correlationId string, replyToOptional sharedUtils.Optional[string]) error { - fmt.Printf("Publishing JSON RPC message %s\n", messagePayload) ctx, cancelFunction := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFunction() exchangeName := exchangeNameOptional.GetPayloadOrDefault("") routingKey := routingKeyOptional.GetPayloadOrDefault("") replyTo := replyToOptional.GetPayloadOrDefault("") - fmt.Printf("Publishing message with RPC: %s\n", messagePayload) return c.channel.PublishWithContext(ctx, exchangeName, routingKey, false, false, amqp.Publishing{ ContentType: "application/json", Body: messagePayload, @@ -131,7 +129,6 @@ func (c *ClientImpl) SetupMessageConsumption(queueName string, messageConsumerFu } func (c *ClientImpl) SetupMessageConsumptionWithCorrelationId(queueName string, correlationId string, messageConsumerFunction func(message amqp.Delivery) error) error { - fmt.Printf("Waiting for message with correlationId %s\n", correlationId) consumerName := fmt.Sprintf("%s-consumer", correlationId) if err := c.channel.Qos(1, 0, false); err != nil { return err @@ -141,27 +138,25 @@ func (c *ClientImpl) SetupMessageConsumptionWithCorrelationId(queueName string, return err } for message := range messageChannel { - fmt.Printf("Seeing message %s correlationId %s from a consumer %s\n", message.Body, message.CorrelationId, consumerName) + fmt.Printf("Seeing message correlationId %s from a consumer %s\n", message.CorrelationId, consumerName) if message.CorrelationId != correlationId { // Return the message back to the queue if it has a wrong correlation ID if err := message.Nack(false, true); err != nil { return err } - fmt.Println("Skipping") continue } - if err := messageConsumerFunction(message); err != nil { - return err - } if err := message.Ack(false); err != nil { return err } - err := c.channel.Cancel(consumerName, true) if err != nil { return err } + if err := messageConsumerFunction(message); err != nil { + return err + } } return nil } @@ -191,8 +186,10 @@ func ConsumeJSONMessagesWithAccessToDelivery[T any](client Client, queueName str if messageContentType != "application/json" { return fmt.Errorf("incorrect message content type: %s", messageContentType) } + jsonDeserializationResult := sharedUtils.DeserializeFromJSON[T](message.Body) if jsonDeserializationResult.IsFailure() { + fmt.Println(jsonDeserializationResult.GetError()) return jsonDeserializationResult.GetError() } return messagePayloadConsumerFunction(jsonDeserializationResult.GetPayload(), message) diff --git a/backend/commons/src/sharedModel/timeSeries.go b/backend/commons/src/sharedModel/timeSeries.go index 3121bf8..350a738 100644 --- a/backend/commons/src/sharedModel/timeSeries.go +++ b/backend/commons/src/sharedModel/timeSeries.go @@ -17,8 +17,14 @@ type SensorsWithFields map[string][]string // AreSimpleSensors checks if the sensors are of type SimpleSensors. func AreSimpleSensors(sensors interface{}) bool { - _, ok := sensors.(SimpleSensors) - return ok + switch sensors.(type) { + case SimpleSensors: + return true + case []string: + return true + default: + return false + } } // Operation represents aggregation operations. diff --git a/backend/time-series-store/src/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go index 36bbf37..23128ad 100644 --- a/backend/time-series-store/src/internal/Influx2Client.go +++ b/backend/time-series-store/src/internal/Influx2Client.go @@ -34,6 +34,7 @@ func NewInflux2Client(endpoint string, token string, organization string, bucket } func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) ([]sharedModel.OutputData, error) { + fmt.Printf("Handling query with body %s\n", body) aggregation := createAggregation(body) timeRange := convertTimeToQueryTimePart(body) filter := createFilter(body) @@ -65,15 +66,12 @@ func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) ([]sh outputData = append(outputData, mapToOutputData(result.Record().Values())) } - fmt.Printf("%s\n", outputData) - return outputData, nil } func (influx2Client Influx2Client) Write(data sharedModel.InputData) { if parameters, ok := data.Parameters.(map[string]interface{}); ok { point := influxdb2.NewPoint(data.SDInstanceUID, map[string]string{"deviceType": data.SDTypeSpecification}, parameters, time.Unix(int64(data.Timestamp), 0)) - fmt.Println(point) influx2Client.writeApi.WritePoint(point) } else { fmt.Println("parameterAsAny is not a map[string]interface{}") @@ -119,6 +117,7 @@ func convertTimeToQueryTimePart(body sharedModel.ReadRequestBody) string { func createFilter(body sharedModel.ReadRequestBody) string { var filterStrings []string + fmt.Printf("Getting sensors: %s\n", body.Sensors) if sharedModel.AreSimpleSensors(body.Sensors) { simpleSensors := body.Sensors.(sharedModel.SimpleSensors) for _, sensor := range simpleSensors { @@ -171,14 +170,20 @@ func mapToOutputData(influxOutput map[string]interface{}) sharedModel.OutputData Table: influxOutput["table"].(int64), Time: influxOutput["time"].(time.Time), DeviceID: influxOutput["deviceId"].(string), - DeviceType: influxOutput["deviceType"].(string), + DeviceType: "", + } + + if value, exists := influxOutput["deviceType"]; exists { + outputData.DeviceType = value.(string) + delete(influxOutput, "deviceType") + } else { + outputData.DeviceType = "" } delete(influxOutput, "result") delete(influxOutput, "table") delete(influxOutput, "time") delete(influxOutput, "deviceId") - delete(influxOutput, "deviceType") outputData.Data = influxOutput diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index e723721..1bc846f 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -69,7 +69,6 @@ func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2 "", func(readRequestBody sharedModel.ReadRequestBody, delivery amqp.Delivery) error { data, retrieveDataError := influx.Query(readRequestBody) - fmt.Printf("NotMarshalled data %s\n", data) if retrieveDataError != nil { fmt.Println(retrieveDataError.Error()) @@ -77,7 +76,6 @@ func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2 } jsonData, err := json.Marshal(data) - fmt.Printf("Marshalled data %s\n", jsonData) if err != nil { fmt.Printf("Error During Marshall: %s", err) diff --git a/frontend/src/generated/graphql.ts b/frontend/src/generated/graphql.ts index e2014af..27fe53f 100644 --- a/frontend/src/generated/graphql.ts +++ b/frontend/src/generated/graphql.ts @@ -251,7 +251,8 @@ export type Query = { sdInstances: Array; sdType: SdType; sdTypes: Array; - statisticsQuery: Array; + statisticsQuerySensorsWithFields: Array; + statisticsQuerySimpleSensors: Array; }; @@ -270,8 +271,15 @@ export type QuerySdTypeArgs = { }; -export type QueryStatisticsQueryArgs = { - request: StatisticsInput; +export type QueryStatisticsQuerySensorsWithFieldsArgs = { + request?: InputMaybe; + sensors: SensorsWithFields; +}; + + +export type QueryStatisticsQuerySimpleSensorsArgs = { + request?: InputMaybe; + sensors: SimpleSensors; }; export type SdInstance = { @@ -336,33 +344,16 @@ export type SdTypeInput = { }; export type SensorField = { - __typename?: 'SensorField'; - key: Scalars['String']['output']; - values: Array; -}; - -/** Return only the requested sensor fields */ -export type SensorFieldInput = { - fields: Array; key: Scalars['String']['input']; -}; - -/** Sensors to be queried */ -export type SensorsInput = { - /** Return only the requested sensor fields */ - sensorsWithFields?: InputMaybe>>; - /** Simple definition, returns all available sensor fields */ - simpleSensors?: InputMaybe>>; + values: Array; }; export type SensorsWithFields = { - __typename?: 'SensorsWithFields'; sensors: Array; }; export type SimpleSensors = { - __typename?: 'SimpleSensors'; - sensors: Array; + sensors: Array; }; export type StatisticsField = { @@ -387,8 +378,6 @@ export type StatisticsInput = { from?: InputMaybe; /** Aggregation operator to use, if needed */ operation?: InputMaybe; - /** Sensors to be queried */ - sensors: SensorsInput; /** * Timezone override default UTC. * For more details why and how this affects queries see: https://www.influxdata.com/blog/time-zones-in-flux/. From 8f7574252fe85e45b624bc14a16036cd415ccfd2 Mon Sep 17 00:00:00 2001 From: Petr John Date: Sat, 1 Mar 2025 11:05:42 +0100 Subject: [PATCH 15/26] Feature #1: Fix additional querying properties (cherry picked from commit 77a0cde8341b7d115f5c47214f710fe537c6698d) --- .../backend-core/src/api/graphql/gsc/gsc.go | 34 +++++++++---------- .../src/api/graphql/schema.graphqls | 34 +++++++++---------- .../src/model/graphQLModel/graphQLModel.go | 34 +++++++++---------- .../src/internal/Influx2Client.go | 19 +++++++---- 4 files changed, 63 insertions(+), 58 deletions(-) diff --git a/backend/backend-core/src/api/graphql/gsc/gsc.go b/backend/backend-core/src/api/graphql/gsc/gsc.go index 8f58108..6cd45fa 100644 --- a/backend/backend-core/src/api/graphql/gsc/gsc.go +++ b/backend/backend-core/src/api/graphql/gsc/gsc.go @@ -454,23 +454,23 @@ input SensorField { } enum StatisticsOperation { - MEAN - MIN - MAX - FIRST - SUM - LAST - NONE - COUNT - INTEGRAL - MEDIAN - MODE - QUANTILE - REDUCE - SKEW - SPREAD - STDDEV - TIMEWEIGHTEDAVG + mean + min + max + first + sum + last + none + count + integral + median + mode + quantile + reduce + skew + spread + stddev + timeweightedavg } """ diff --git a/backend/backend-core/src/api/graphql/schema.graphqls b/backend/backend-core/src/api/graphql/schema.graphqls index fa0db76..3c69f99 100644 --- a/backend/backend-core/src/api/graphql/schema.graphqls +++ b/backend/backend-core/src/api/graphql/schema.graphqls @@ -223,23 +223,23 @@ input SensorField { } enum StatisticsOperation { - MEAN - MIN - MAX - FIRST - SUM - LAST - NONE - COUNT - INTEGRAL - MEDIAN - MODE - QUANTILE - REDUCE - SKEW - SPREAD - STDDEV - TIMEWEIGHTEDAVG + mean + min + max + first + sum + last + none + count + integral + median + mode + quantile + reduce + skew + spread + stddev + timeweightedavg } """ diff --git a/backend/backend-core/src/model/graphQLModel/graphQLModel.go b/backend/backend-core/src/model/graphQLModel/graphQLModel.go index 333ea6b..517b1cf 100644 --- a/backend/backend-core/src/model/graphQLModel/graphQLModel.go +++ b/backend/backend-core/src/model/graphQLModel/graphQLModel.go @@ -505,23 +505,23 @@ func (e SDParameterType) MarshalGQL(w io.Writer) { type StatisticsOperation string const ( - StatisticsOperationMean StatisticsOperation = "MEAN" - StatisticsOperationMin StatisticsOperation = "MIN" - StatisticsOperationMax StatisticsOperation = "MAX" - StatisticsOperationFirst StatisticsOperation = "FIRST" - StatisticsOperationSum StatisticsOperation = "SUM" - StatisticsOperationLast StatisticsOperation = "LAST" - StatisticsOperationNone StatisticsOperation = "NONE" - StatisticsOperationCount StatisticsOperation = "COUNT" - StatisticsOperationIntegral StatisticsOperation = "INTEGRAL" - StatisticsOperationMedian StatisticsOperation = "MEDIAN" - StatisticsOperationMode StatisticsOperation = "MODE" - StatisticsOperationQuantile StatisticsOperation = "QUANTILE" - StatisticsOperationReduce StatisticsOperation = "REDUCE" - StatisticsOperationSkew StatisticsOperation = "SKEW" - StatisticsOperationSpread StatisticsOperation = "SPREAD" - StatisticsOperationStddev StatisticsOperation = "STDDEV" - StatisticsOperationTimeweightedavg StatisticsOperation = "TIMEWEIGHTEDAVG" + StatisticsOperationMean StatisticsOperation = "mean" + StatisticsOperationMin StatisticsOperation = "min" + StatisticsOperationMax StatisticsOperation = "max" + StatisticsOperationFirst StatisticsOperation = "first" + StatisticsOperationSum StatisticsOperation = "sum" + StatisticsOperationLast StatisticsOperation = "last" + StatisticsOperationNone StatisticsOperation = "none" + StatisticsOperationCount StatisticsOperation = "count" + StatisticsOperationIntegral StatisticsOperation = "integral" + StatisticsOperationMedian StatisticsOperation = "median" + StatisticsOperationMode StatisticsOperation = "mode" + StatisticsOperationQuantile StatisticsOperation = "quantile" + StatisticsOperationReduce StatisticsOperation = "reduce" + StatisticsOperationSkew StatisticsOperation = "skew" + StatisticsOperationSpread StatisticsOperation = "spread" + StatisticsOperationStddev StatisticsOperation = "stddev" + StatisticsOperationTimeweightedavg StatisticsOperation = "timeweightedavg" ) var AllStatisticsOperation = []StatisticsOperation{ diff --git a/backend/time-series-store/src/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go index 23128ad..9f5467e 100644 --- a/backend/time-series-store/src/internal/Influx2Client.go +++ b/backend/time-series-store/src/internal/Influx2Client.go @@ -34,19 +34,24 @@ func NewInflux2Client(endpoint string, token string, organization string, bucket } func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) ([]sharedModel.OutputData, error) { - fmt.Printf("Handling query with body %s\n", body) aggregation := createAggregation(body) timeRange := convertTimeToQueryTimePart(body) filter := createFilter(body) + imports := "" - query := fmt.Sprintf("from(bucket: \"%s\")\n"+ + if body.Timezone != "" { + imports = "import \"timezone\"" + } + + query := fmt.Sprintf("%s\n\n"+ // imports + "from(bucket: \"%s\")\n"+ " %s\n"+ // time range " %s\n"+ // filter " %s\n"+ // aggregation " |> drop(columns: [\"_start\", \"_stop\"])\n"+ " |> pivot(columnKey: [\"_field\"], rowKey: [\"_measurement\", \"_time\"], valueColumn: \"_value\")\n"+ " |> rename(columns: {_time: \"time\", _measurement: \"deviceId\"})\n"+ - " |> group(columns: [\"measurement\"], mode: \"by\")", influx2Client.bucket, timeRange, filter, aggregation) + " |> group(columns: [\"measurement\"], mode: \"by\")", imports, influx2Client.bucket, timeRange, filter, aggregation) fmt.Println(query) @@ -86,11 +91,11 @@ func (influx2Client Influx2Client) Close() { func convertTimeToQueryTimePart(body sharedModel.ReadRequestBody) string { if body.From != nil && body.To == nil { - currentDate := body.From.AddDate(0, 0, 30) + currentDate := time.Now() currentDateString := currentDate.Format(time.RFC3339) - return fmt.Sprintf("|> range(start: %s, stop: %s)", body.From, currentDateString) + return fmt.Sprintf("|> range(start: %s, stop: %s)", body.From.Format(time.RFC3339), currentDateString) } if body.From == nil && body.To != nil { @@ -98,7 +103,7 @@ func convertTimeToQueryTimePart(body sharedModel.ReadRequestBody) string { thirtyDaysAgoString := thirtyDaysAgo.Format(time.RFC3339) - return fmt.Sprintf("|> range(start: %s, stop: %s)", thirtyDaysAgoString, body.To) + return fmt.Sprintf("|> range(start: %s, stop: %s)", thirtyDaysAgoString, body.To.Format(time.RFC3339)) } if body.From == nil && body.To == nil { @@ -157,7 +162,7 @@ func createAggregation(body sharedModel.ReadRequestBody) string { zone = fmt.Sprintf(", location: timezone.location(name: \"%s\")", body.Timezone) } - aggregation = fmt.Sprintf("|> aggregateWindow(every: %xm, fn: %s, createEmpty: false%s)", body.AggregateMinutes, body.Operation, zone) + aggregation = fmt.Sprintf("|> aggregateWindow(every: %dm, fn: %s, createEmpty: false%s)", body.AggregateMinutes, body.Operation, zone) } return aggregation From 2ba4321e8ec3f5063bf933514c2f38d0fc9ca69e Mon Sep 17 00:00:00 2001 From: Petr John Date: Sat, 1 Mar 2025 14:13:36 +0100 Subject: [PATCH 16/26] Feature #1: Use sharedUtils.Result[[]sharedModel.OutputData] (cherry picked from commit 4f5172e66ef7a0d11b389d9aaa864852cc032262) --- .../src/domainLogicLayer/statistics.go | 2 -- .../src/internal/Influx2Client.go | 9 +++++---- backend/time-series-store/src/main.go | 18 ++++++++++++------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/backend/backend-core/src/domainLogicLayer/statistics.go b/backend/backend-core/src/domainLogicLayer/statistics.go index a875b23..fb65ceb 100644 --- a/backend/backend-core/src/domainLogicLayer/statistics.go +++ b/backend/backend-core/src/domainLogicLayer/statistics.go @@ -70,14 +70,12 @@ func Query(input sharedModel.ReadRequestBody) sharedUtils.Result[[]graphQLModel. result := <-outputChannel if result.IsFailure() { - fmt.Println(result.GetError()) return sharedUtils.NewFailureResult[[]graphQLModel.OutputData](err) } convertedResult, err := ConvertOutputData(result.GetPayload()) if err != nil { - fmt.Println(err) return sharedUtils.NewFailureResult[[]graphQLModel.OutputData](err) } diff --git a/backend/time-series-store/src/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go index 9f5467e..754042c 100644 --- a/backend/time-series-store/src/internal/Influx2Client.go +++ b/backend/time-series-store/src/internal/Influx2Client.go @@ -4,6 +4,7 @@ import ( "context" "fmt" sharedModel "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedModel" + "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" influxdb2 "github.com/influxdata/influxdb-client-go/v2" "github.com/influxdata/influxdb-client-go/v2/api" "strings" @@ -33,7 +34,7 @@ func NewInflux2Client(endpoint string, token string, organization string, bucket return influx2Client, influx2Client.writeApi.Errors(), nil } -func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) ([]sharedModel.OutputData, error) { +func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) sharedUtils.Result[[]sharedModel.OutputData] { aggregation := createAggregation(body) timeRange := convertTimeToQueryTimePart(body) filter := createFilter(body) @@ -58,20 +59,20 @@ func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) ([]sh result, err := influx2Client.queryApi.Query(context.Background(), query) if err != nil { - return nil, err + return sharedUtils.NewFailureResult[[]sharedModel.OutputData](err) } outputData := []sharedModel.OutputData{} if result.Err() != nil { - return nil, result.Err() + return sharedUtils.NewFailureResult[[]sharedModel.OutputData](result.Err()) } for result.Next() { outputData = append(outputData, mapToOutputData(result.Record().Values())) } - return outputData, nil + return sharedUtils.NewSuccessResult[[]sharedModel.OutputData](outputData) } func (influx2Client Influx2Client) Write(data sharedModel.InputData) { diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index 1bc846f..4f2621f 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -68,18 +68,24 @@ func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2 sharedConstants.TimeSeriesReadRequestQueueName, "", func(readRequestBody sharedModel.ReadRequestBody, delivery amqp.Delivery) error { - data, retrieveDataError := influx.Query(readRequestBody) + result := influx.Query(readRequestBody) - if retrieveDataError != nil { - fmt.Println(retrieveDataError.Error()) - return retrieveDataError + if result.IsFailure() { + fmt.Println(result.GetError()) } - jsonData, err := json.Marshal(data) + fmt.Printf("%s\n", result) + jsonData := make([]byte, 0) + var err error + + if result.IsSuccess() { + jsonData, err = json.Marshal(result.GetPayload()) + } + + fmt.Printf("%s\n", jsonData) if err != nil { fmt.Printf("Error During Marshall: %s", err) - return nil } err = rabbitMQClient.PublishJSONMessageRPC( From 81ba2d8665c814d67d121219f7d35115c2062c54 Mon Sep 17 00:00:00 2001 From: Petr John Date: Sat, 1 Mar 2025 15:45:38 +0100 Subject: [PATCH 17/26] Feature #1: Fix error not being propagated into api (cherry picked from commit 73f32224e5dc60acee61c39de7316026163ce2bf) --- .../src/domainLogicLayer/statistics.go | 14 +++++++++----- backend/commons/src/sharedModel/iscMessages.go | 5 +++++ backend/time-series-store/src/main.go | 15 ++++++++++----- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/backend/backend-core/src/domainLogicLayer/statistics.go b/backend/backend-core/src/domainLogicLayer/statistics.go index fb65ceb..028036a 100644 --- a/backend/backend-core/src/domainLogicLayer/statistics.go +++ b/backend/backend-core/src/domainLogicLayer/statistics.go @@ -2,6 +2,7 @@ package domainLogicLayer import ( "encoding/json" + "errors" "fmt" "github.com/MichalBures-OG/bp-bures-RIoT-backend-core/src/model/graphQLModel" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/rabbitmq" @@ -36,13 +37,16 @@ func Query(input sharedModel.ReadRequestBody) sharedUtils.Result[[]graphQLModel. outputChannel := make(chan sharedUtils.Result[[]sharedModel.OutputData]) go func() { - err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[[]sharedModel.OutputData]( + err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[sharedModel.ReadRequestResponseOrError]( rabbitMQClient, sharedConstants.TimeSeriesReadRequestBackendCoreResponseQueueName, correlationId, - func(outputData []sharedModel.OutputData, delivery amqp.Delivery) error { - output := sharedUtils.NewSuccessResult[[]sharedModel.OutputData](outputData) - outputChannel <- output + func(readRequestResponseOrError sharedModel.ReadRequestResponseOrError, delivery amqp.Delivery) error { + if readRequestResponseOrError.Error != "" { + outputChannel <- sharedUtils.NewFailureResult[[]sharedModel.OutputData](errors.New(readRequestResponseOrError.Error)) + } else { + outputChannel <- sharedUtils.NewSuccessResult[[]sharedModel.OutputData](readRequestResponseOrError.Data) + } close(outputChannel) return nil @@ -70,7 +74,7 @@ func Query(input sharedModel.ReadRequestBody) sharedUtils.Result[[]graphQLModel. result := <-outputChannel if result.IsFailure() { - return sharedUtils.NewFailureResult[[]graphQLModel.OutputData](err) + return sharedUtils.NewFailureResult[[]graphQLModel.OutputData](result.GetError()) } convertedResult, err := ConvertOutputData(result.GetPayload()) diff --git a/backend/commons/src/sharedModel/iscMessages.go b/backend/commons/src/sharedModel/iscMessages.go index 43a068f..073582e 100644 --- a/backend/commons/src/sharedModel/iscMessages.go +++ b/backend/commons/src/sharedModel/iscMessages.go @@ -33,3 +33,8 @@ type SDInstanceConfigurationUpdateISCMessage []SDInstanceInfo type KPIConfigurationUpdateISCMessage map[string][]KPIDefinition type MessageProcessingUnitConnectionNotification struct{} + +type ReadRequestResponseOrError struct { + Data []OutputData `json:"data,omitempty"` + Error string `json:"error,omitempty"` +} diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index 4f2621f..c0b06fa 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -74,15 +74,20 @@ func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2 fmt.Println(result.GetError()) } - fmt.Printf("%s\n", result) - jsonData := make([]byte, 0) - var err error + responseWithData := sharedModel.ReadRequestResponseOrError{ + Data: nil, + Error: "", + } if result.IsSuccess() { - jsonData, err = json.Marshal(result.GetPayload()) + responseWithData.Data = result.GetPayload() + } + + if result.IsFailure() { + responseWithData.Error = result.GetError().Error() } - fmt.Printf("%s\n", jsonData) + jsonData, err := json.Marshal(responseWithData) if err != nil { fmt.Printf("Error During Marshall: %s", err) From be2541feace5cff743ed4853040950009c752a01 Mon Sep 17 00:00:00 2001 From: Petr John Date: Sat, 1 Mar 2025 16:00:34 +0100 Subject: [PATCH 18/26] Feature #1: Add timeout to API, drop messages, that could not be satisfied instead of ping-pong-ing them (cherry picked from commit 0461b33be97a7d6e7a8c3f1a7aa39b2e28d0859d) --- .../src/domainLogicLayer/statistics.go | 2 +- backend/commons/src/rabbitmq/client.go | 65 ++++++++++++++----- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/backend/backend-core/src/domainLogicLayer/statistics.go b/backend/backend-core/src/domainLogicLayer/statistics.go index 028036a..4cc41a7 100644 --- a/backend/backend-core/src/domainLogicLayer/statistics.go +++ b/backend/backend-core/src/domainLogicLayer/statistics.go @@ -54,8 +54,8 @@ func Query(input sharedModel.ReadRequestBody) sharedUtils.Result[[]graphQLModel. ) if err != nil { + outputChannel <- sharedUtils.NewFailureResult[[]sharedModel.OutputData](err) close(outputChannel) - sharedUtils.NewFailureResult[[]graphQLModel.OutputData](err) } }() diff --git a/backend/commons/src/rabbitmq/client.go b/backend/commons/src/rabbitmq/client.go index 812bab7..79e74f6 100644 --- a/backend/commons/src/rabbitmq/client.go +++ b/backend/commons/src/rabbitmq/client.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" amqp "github.com/rabbitmq/amqp091-go" + "strconv" "sync" "time" ) @@ -90,7 +91,9 @@ func (c *ClientImpl) PublishJSONMessage(exchangeNameOptional sharedUtils.Optiona } func (c *ClientImpl) PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Optional[string], routingKeyOptional sharedUtils.Optional[string], messagePayload []byte, correlationId string, replyToOptional sharedUtils.Optional[string]) error { - ctx, cancelFunction := context.WithTimeout(context.Background(), 5*time.Second) + timeout := 10 * time.Second + ctx, cancelFunction := context.WithTimeout(context.Background(), timeout) + expiration := strconv.Itoa(int(timeout / time.Millisecond)) defer cancelFunction() exchangeName := exchangeNameOptional.GetPayloadOrDefault("") routingKey := routingKeyOptional.GetPayloadOrDefault("") @@ -101,6 +104,7 @@ func (c *ClientImpl) PublishJSONMessageRPC(exchangeNameOptional sharedUtils.Opti Body: messagePayload, CorrelationId: correlationId, ReplyTo: replyTo, + Expiration: expiration, }) } @@ -133,32 +137,57 @@ func (c *ClientImpl) SetupMessageConsumptionWithCorrelationId(queueName string, if err := c.channel.Qos(1, 0, false); err != nil { return err } + messageChannel, err := c.channel.Consume(queueName, consumerName, false, false, false, false, nil) if err != nil { return err } - for message := range messageChannel { - fmt.Printf("Seeing message correlationId %s from a consumer %s\n", message.CorrelationId, consumerName) - if message.CorrelationId != correlationId { - // Return the message back to the queue if it has a wrong correlation ID - if err := message.Nack(false, true); err != nil { + + // Timeout after a time, so you don't ping-pong with a request. Most likely the API won't even care after this time + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second) + defer cancel() + + for { + select { + case message, ok := <-messageChannel: + if !ok { + return fmt.Errorf("message channel closed") + } + + fmt.Printf("Seeing message correlationId %s from a consumer %s\n", message.CorrelationId, consumerName) + + if message.CorrelationId != correlationId { + // Return the message back to the queue if it has a wrong correlation ID + if err := message.Nack(false, true); err != nil { + return err + } + continue + } + + if err := message.Ack(false); err != nil { return err } - continue - } - if err := message.Ack(false); err != nil { - return err - } - err := c.channel.Cancel(consumerName, true) - if err != nil { - return err - } - if err := messageConsumerFunction(message); err != nil { - return err + err := c.channel.Cancel(consumerName, true) + if err != nil { + return err + } + + if err := messageConsumerFunction(message); err != nil { + return err + } + + return nil // Exit after processing + + case <-ctx.Done(): + fmt.Println("Timeout reached. Stopping message consumption.") + err := c.channel.Cancel(consumerName, true) // Stop consuming + if err != nil { + return err + } + return fmt.Errorf("message consumption timed out after 100 seconds") } } - return nil } func (c *ClientImpl) Dispose() { From b38130cc4566014f44fdd72bc161c46f9a9a09a5 Mon Sep 17 00:00:00 2001 From: Petr John Date: Thu, 6 Mar 2025 17:12:51 +0100 Subject: [PATCH 19/26] Feature #1: Remove host from result (cherry picked from commit 2ccd792f84a86c9c7f3e118f4cb8c7e16527c727) --- backend/time-series-store/src/internal/Influx2Client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/time-series-store/src/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go index 754042c..6352888 100644 --- a/backend/time-series-store/src/internal/Influx2Client.go +++ b/backend/time-series-store/src/internal/Influx2Client.go @@ -190,6 +190,7 @@ func mapToOutputData(influxOutput map[string]interface{}) sharedModel.OutputData delete(influxOutput, "table") delete(influxOutput, "time") delete(influxOutput, "deviceId") + delete(influxOutput, "host") outputData.Data = influxOutput From 653b2852658523460ca8179ef51379ab6298ba5f Mon Sep 17 00:00:00 2001 From: Petr John Date: Sat, 8 Mar 2025 20:38:46 +0100 Subject: [PATCH 20/26] Feature #1: Smaller fixes in time series store (cherry picked from commit f302e1fbb49b38a5bca0cefdcf867d8fab965192) --- .../src/domainLogicLayer/statistics.go | 10 ++++++- backend/commons/src/rabbitmq/client.go | 27 +++++++++++++++---- .../src/internal/Influx2Client.go | 15 ++++++++++- backend/time-series-store/src/main.go | 5 ++-- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/backend/backend-core/src/domainLogicLayer/statistics.go b/backend/backend-core/src/domainLogicLayer/statistics.go index 4cc41a7..df9b34d 100644 --- a/backend/backend-core/src/domainLogicLayer/statistics.go +++ b/backend/backend-core/src/domainLogicLayer/statistics.go @@ -10,6 +10,7 @@ import ( "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedModel" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" amqp "github.com/rabbitmq/amqp091-go" + "log" "math/rand" "time" ) @@ -37,8 +38,11 @@ func Query(input sharedModel.ReadRequestBody) sharedUtils.Result[[]graphQLModel. outputChannel := make(chan sharedUtils.Result[[]sharedModel.OutputData]) go func() { + client := rabbitmq.NewClient() + defer client.Dispose() + err := rabbitmq.ConsumeJSONMessagesWithAccessToDelivery[sharedModel.ReadRequestResponseOrError]( - rabbitMQClient, + client, sharedConstants.TimeSeriesReadRequestBackendCoreResponseQueueName, correlationId, func(readRequestResponseOrError sharedModel.ReadRequestResponseOrError, delivery amqp.Delivery) error { @@ -54,6 +58,7 @@ func Query(input sharedModel.ReadRequestBody) sharedUtils.Result[[]graphQLModel. ) if err != nil { + log.Printf("Statistics Query | %s", err) outputChannel <- sharedUtils.NewFailureResult[[]sharedModel.OutputData](err) close(outputChannel) } @@ -68,18 +73,21 @@ func Query(input sharedModel.ReadRequestBody) sharedUtils.Result[[]graphQLModel. ) if err != nil { + log.Printf("Statistics Query | %s", err) return sharedUtils.NewFailureResult[[]graphQLModel.OutputData](err) } result := <-outputChannel if result.IsFailure() { + log.Printf("Statistics Query | %s", result.GetError()) return sharedUtils.NewFailureResult[[]graphQLModel.OutputData](result.GetError()) } convertedResult, err := ConvertOutputData(result.GetPayload()) if err != nil { + log.Printf("Statistics Query | %s", err) return sharedUtils.NewFailureResult[[]graphQLModel.OutputData](err) } diff --git a/backend/commons/src/rabbitmq/client.go b/backend/commons/src/rabbitmq/client.go index 79e74f6..83b971d 100644 --- a/backend/commons/src/rabbitmq/client.go +++ b/backend/commons/src/rabbitmq/client.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" amqp "github.com/rabbitmq/amqp091-go" + "log" "strconv" "sync" "time" @@ -140,52 +141,68 @@ func (c *ClientImpl) SetupMessageConsumptionWithCorrelationId(queueName string, messageChannel, err := c.channel.Consume(queueName, consumerName, false, false, false, false, nil) if err != nil { + log.Printf("SetupMessageConsumptionWithCorrelationId | %s", err) return err } // Timeout after a time, so you don't ping-pong with a request. Most likely the API won't even care after this time - ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() for { select { case message, ok := <-messageChannel: if !ok { - return fmt.Errorf("message channel closed") + return fmt.Errorf("SetupMessageConsumptionWithCorrelationId | Message channel closed") } - fmt.Printf("Seeing message correlationId %s from a consumer %s\n", message.CorrelationId, consumerName) + log.Printf("SetupMessageConsumptionWithCorrelationId | Seeing message correlationId %s from a consumer %s\n", message.CorrelationId, consumerName) if message.CorrelationId != correlationId { // Return the message back to the queue if it has a wrong correlation ID if err := message.Nack(false, true); err != nil { + log.Printf("SetupMessageConsumptionWithCorrelationId | %s", err) return err } continue } if err := message.Ack(false); err != nil { + log.Printf("SetupMessageConsumptionWithCorrelationId | %s", err) return err } err := c.channel.Cancel(consumerName, true) if err != nil { + log.Printf("SetupMessageConsumptionWithCorrelationId | %s", err) return err } if err := messageConsumerFunction(message); err != nil { + log.Printf("SetupMessageConsumptionWithCorrelationId | %s", err) return err } + log.Println("SetupMessageConsumptionWithCorrelationId | Consuming message and closing channel") + + err = c.channel.Cancel(consumerName, true) // Stop consuming + if err != nil { + log.Printf("SetupMessageConsumptionWithCorrelationId | %s", err) + return err + } + + log.Println("SetupMessageConsumptionWithCorrelationId | Closed channel, exiting") + return nil // Exit after processing case <-ctx.Done(): - fmt.Println("Timeout reached. Stopping message consumption.") + log.Println("SetupMessageConsumptionWithCorrelationId | Timeout reached. Stopping message consumption.") err := c.channel.Cancel(consumerName, true) // Stop consuming if err != nil { + log.Printf("SetupMessageConsumptionWithCorrelationId | %s", err) return err } - return fmt.Errorf("message consumption timed out after 100 seconds") + return fmt.Errorf("SetupMessageConsumptionWithCorrelationId | message consumption timed out after 100 seconds") } } } diff --git a/backend/time-series-store/src/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go index 6352888..6928320 100644 --- a/backend/time-series-store/src/internal/Influx2Client.go +++ b/backend/time-series-store/src/internal/Influx2Client.go @@ -172,7 +172,7 @@ func createAggregation(body sharedModel.ReadRequestBody) string { // mapToOutputData converts a map[string]interface{} to an OutputData struct. func mapToOutputData(influxOutput map[string]interface{}) sharedModel.OutputData { outputData := sharedModel.OutputData{ - Result: influxOutput["result"].(string), + Result: "", Table: influxOutput["table"].(int64), Time: influxOutput["time"].(time.Time), DeviceID: influxOutput["deviceId"].(string), @@ -186,6 +186,19 @@ func mapToOutputData(influxOutput map[string]interface{}) sharedModel.OutputData outputData.DeviceType = "" } + if value, exists := influxOutput["result"]; exists { + outputData.DeviceType = value.(string) + delete(influxOutput, "result") + } else { + outputData.DeviceType = "" + } + + for key, value := range influxOutput { + if value == nil { + delete(influxOutput, key) + } + } + delete(influxOutput, "result") delete(influxOutput, "table") delete(influxOutput, "time") diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index c0b06fa..0ca1d46 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -10,6 +10,7 @@ import ( "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" amqp "github.com/rabbitmq/amqp091-go" "github.com/xjohnp00/jiap/backend/shared/time-series-store/src/internal" + "log" "os" ) @@ -90,7 +91,7 @@ func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2 jsonData, err := json.Marshal(responseWithData) if err != nil { - fmt.Printf("Error During Marshall: %s", err) + log.Printf("Error During Marshall: %s", err) } err = rabbitMQClient.PublishJSONMessageRPC( @@ -102,7 +103,7 @@ func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2 ) if err != nil { - fmt.Printf("Error: %s", err) + log.Fatalf("Error: %s", err) return err } return nil From e58a916cf35555d2605e152b2d950aa3cb930333 Mon Sep 17 00:00:00 2001 From: Petr John Date: Mon, 10 Mar 2025 22:14:13 +0100 Subject: [PATCH 21/26] Hotfix #1: Use shared GetEnvironmentVariableValue instead of duplicating code (cherry picked from commit 578021fbf0b13d819fefdc6a02768681cf32376f) --- .../sharedUtils/getEnvironmentParameter.go | 30 ------------ backend/time-series-store/src/main.go | 48 +++++++++---------- 2 files changed, 23 insertions(+), 55 deletions(-) delete mode 100644 backend/commons/src/sharedUtils/getEnvironmentParameter.go diff --git a/backend/commons/src/sharedUtils/getEnvironmentParameter.go b/backend/commons/src/sharedUtils/getEnvironmentParameter.go deleted file mode 100644 index 57d04c9..0000000 --- a/backend/commons/src/sharedUtils/getEnvironmentParameter.go +++ /dev/null @@ -1,30 +0,0 @@ -package sharedUtils - -import ( - "errors" - "flag" - "os" -) - -// GetEnvironmentParameter returns the value of an environment parameter. -// If the parameter is provided as a command-line flag, it takes precedence. -// If neither the flag nor the environment variable is provided, it returns an error. -// Note: This function assumes that flag.Parse() has already been called to parse command-line flags. -func GetEnvironmentParameter(name, description string) (string, error) { - // Check if command-line flag is provided - flagValue := flag.String(name, "", description) - - // If command-line flag is provided, return its value - if *flagValue != "" { - return *flagValue, nil - } - - // If no flag is provided, check environment variable - envValue := os.Getenv(name) - if envValue != "" { - return envValue, nil - } - - // If neither flag nor environment variable is provided, return an error - return "", errors.New("Neither flag nor environment variable provided for " + name) -} diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index 0ca1d46..ae9ca18 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -2,7 +2,6 @@ package main import ( "encoding/json" - "flag" "fmt" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/rabbitmq" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedConstants" @@ -113,42 +112,41 @@ func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2 } func parseParameters() (bool, internal.TimeSeriesStoreEnvironment) { - flag.Parse() - token, tokenError := sharedUtils.GetEnvironmentParameter("INFLUX_TOKEN", "InfluxDB Token") - url, urlError := sharedUtils.GetEnvironmentParameter("INFLUX_URL", "InfluxDB URL") - org, orgError := sharedUtils.GetEnvironmentParameter("INFLUX_ORGANIZATION", "InfluxDB Organization") - bucket, bucketError := sharedUtils.GetEnvironmentParameter("INFLUX_BUCKET", "InfluxDB Bucket") - ampqUrl, ampqUrlError := sharedUtils.GetEnvironmentParameter("RABBITMQ_URL", "RABBITMQ Broker URL") + token := sharedUtils.GetEnvironmentVariableValue("INFLUX_TOKEN") + url := sharedUtils.GetEnvironmentVariableValue("INFLUX_URL") + org := sharedUtils.GetEnvironmentVariableValue("INFLUX_ORGANIZATION") + bucket := sharedUtils.GetEnvironmentVariableValue("INFLUX_BUCKET") + ampqUrl := sharedUtils.GetEnvironmentVariableValue("RABBITMQ_URL") - hasError := tokenError != nil || urlError != nil || orgError != nil || bucketError != nil || ampqUrlError != nil + hasError := token.IsEmpty() || url.IsEmpty() || org.IsEmpty() || bucket.IsEmpty() || ampqUrl.IsEmpty() - if tokenError != nil { - fmt.Println(fmt.Errorf(tokenError.Error())) + if token.IsEmpty() { + log.Fatalln("Empty token") } - if urlError != nil { - fmt.Println(fmt.Errorf(urlError.Error())) + if url.IsEmpty() { + log.Fatalln("Empty url") } - if orgError != nil { - fmt.Println(fmt.Errorf(orgError.Error())) + if org.IsEmpty() { + log.Fatalln("Empty org") } - if bucketError != nil { - fmt.Println(fmt.Errorf(bucketError.Error())) + if bucket.IsEmpty() { + log.Fatalln("Empty bucket") } - if ampqUrlError != nil { - fmt.Println(fmt.Errorf(ampqUrlError.Error())) + if ampqUrl.IsEmpty() { + log.Fatalln("Empty ampqUrl") } - environment := internal.TimeSeriesStoreEnvironment{ - InfluxToken: token, - InfluxUrl: url, - InfluxOrg: org, - InfluxBucket: bucket, - AmqpURLValue: ampqUrl, - } + environment := sharedUtils.Ternary[internal.TimeSeriesStoreEnvironment](!hasError, internal.TimeSeriesStoreEnvironment{ + InfluxToken: token.GetPayload(), + InfluxUrl: url.GetPayload(), + InfluxOrg: org.GetPayload(), + InfluxBucket: bucket.GetPayload(), + AmqpURLValue: ampqUrl.GetPayload(), + }, internal.TimeSeriesStoreEnvironment{}) return hasError, environment } From 2b8657a3932c4c72231420261725075c12e0a0e1 Mon Sep 17 00:00:00 2001 From: Petr John Date: Mon, 10 Mar 2025 22:31:50 +0100 Subject: [PATCH 22/26] Hotfix #1: Use log instead of fmt (cherry picked from commit 263b369cd91c55863130143b621e145955010449) --- .../src/internal/Influx2Client.go | 7 ++++--- backend/time-series-store/src/main.go | 14 +++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/backend/time-series-store/src/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go index 6928320..7b25727 100644 --- a/backend/time-series-store/src/internal/Influx2Client.go +++ b/backend/time-series-store/src/internal/Influx2Client.go @@ -7,6 +7,7 @@ import ( "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedUtils" influxdb2 "github.com/influxdata/influxdb-client-go/v2" "github.com/influxdata/influxdb-client-go/v2/api" + "log" "strings" "time" ) @@ -54,7 +55,7 @@ func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) share " |> rename(columns: {_time: \"time\", _measurement: \"deviceId\"})\n"+ " |> group(columns: [\"measurement\"], mode: \"by\")", imports, influx2Client.bucket, timeRange, filter, aggregation) - fmt.Println(query) + log.Println(query) result, err := influx2Client.queryApi.Query(context.Background(), query) @@ -80,7 +81,7 @@ func (influx2Client Influx2Client) Write(data sharedModel.InputData) { point := influxdb2.NewPoint(data.SDInstanceUID, map[string]string{"deviceType": data.SDTypeSpecification}, parameters, time.Unix(int64(data.Timestamp), 0)) influx2Client.writeApi.WritePoint(point) } else { - fmt.Println("parameterAsAny is not a map[string]interface{}") + log.Println("parameterAsAny is not a map[string]interface{}") } influx2Client.writeApi.Flush() @@ -123,7 +124,7 @@ func convertTimeToQueryTimePart(body sharedModel.ReadRequestBody) string { func createFilter(body sharedModel.ReadRequestBody) string { var filterStrings []string - fmt.Printf("Getting sensors: %s\n", body.Sensors) + log.Printf("Getting sensors: %s\n", body.Sensors) if sharedModel.AreSimpleSensors(body.Sensors) { simpleSensors := body.Sensors.(sharedModel.SimpleSensors) for _, sensor := range simpleSensors { diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index ae9ca18..3b41dcb 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -2,7 +2,6 @@ package main import ( "encoding/json" - "fmt" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/rabbitmq" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedConstants" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedModel" @@ -20,7 +19,7 @@ func main() { os.Exit(1) } - fmt.Println("Time Series Store Starting") + log.Println("Time Series Store Starting") influx, influxErrors, _ := internal.NewInflux2Client(environment.InfluxUrl, environment.InfluxToken, environment.InfluxOrg, environment.InfluxBucket) @@ -28,34 +27,35 @@ func main() { defer rabbitMQClient.Dispose() defer influx.Close() - fmt.Println("Time Series Store Ready") + log.Println("Time Series Store Ready") sharedUtils.WaitForAll( func() { err := consumeInputMessages(rabbitMQClient, influx) if err != nil { - fmt.Println(err.Error()) + log.Println(err.Error()) } }, func() { err := consumeReadRequests(rabbitMQClient, influx) if err != nil { - fmt.Println(err.Error()) + log.Println(err.Error()) } }, ) go func() { for err := range influxErrors { - fmt.Println(err) + log.Println(err) } }() } func consumeInputMessages(rabbitMQClient rabbitmq.Client, influx internal.Influx2Client) error { err := rabbitmq.ConsumeJSONMessages[sharedModel.InputData](rabbitMQClient, sharedConstants.TimeSeriesStoreDataQueueName, func(messagePayload sharedModel.InputData) error { + log.Printf("Writing: %s \n", messagePayload.SDInstanceUID) influx.Write(messagePayload) return nil }) @@ -71,7 +71,7 @@ func consumeReadRequests(rabbitMQClient rabbitmq.Client, influx internal.Influx2 result := influx.Query(readRequestBody) if result.IsFailure() { - fmt.Println(result.GetError()) + log.Println(result.GetError()) } responseWithData := sharedModel.ReadRequestResponseOrError{ From 2a5eed211eb4c7ddcdae8aa6eebe0c71ac6f8d21 Mon Sep 17 00:00:00 2001 From: Petr John Date: Sat, 15 Mar 2025 14:15:08 +0100 Subject: [PATCH 23/26] Feature #1: Use WaitForDSs (cherry picked from commit 32bd564e332e6e61f32a93acb1b3b63885f5e198) --- backend/time-series-store/src/main.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index 3b41dcb..55d13ae 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "fmt" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/rabbitmq" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedConstants" "github.com/MichalBures-OG/bp-bures-RIoT-commons/src/sharedModel" @@ -9,7 +10,9 @@ import ( amqp "github.com/rabbitmq/amqp091-go" "github.com/xjohnp00/jiap/backend/shared/time-series-store/src/internal" "log" + "net/url" "os" + "time" ) func main() { @@ -19,6 +22,24 @@ func main() { os.Exit(1) } + log.Println("Waiting for dependencies...") + rawBackendCoreURL := sharedUtils.GetEnvironmentVariableValue("BACKEND_CORE_URL").GetPayloadOrDefault("http://riot-backend-core:9090") + parsedBackendCoreURL, err := url.Parse(rawBackendCoreURL) + sharedUtils.TerminateOnError(err, fmt.Sprintf("Unable to parse the backend-core URL: %s", rawBackendCoreURL)) + + parsedInfluxURL, err := url.Parse(environment.InfluxUrl) + sharedUtils.TerminateOnError(err, fmt.Sprintf("Unable to parse the InfluxDB URL: %s", environment.InfluxUrl)) + + parsedRabbitMQURL, err := url.Parse(environment.AmqpURLValue) + sharedUtils.TerminateOnError(err, fmt.Sprintf("Unable to parse the RabbitMQ URL: %s", environment.AmqpURLValue)) + + sharedUtils.TerminateOnError(sharedUtils.WaitForDSs(time.Minute, + sharedUtils.NewPairOf(parsedBackendCoreURL.Hostname(), parsedBackendCoreURL.Port()), + sharedUtils.NewPairOf(parsedInfluxURL.Hostname(), parsedInfluxURL.Port()), + sharedUtils.NewPairOf(parsedRabbitMQURL.Hostname(), parsedRabbitMQURL.Port()), + ), "Some dependencies of this application are inaccessible") + log.Println("Dependencies should be up and running...") + log.Println("Time Series Store Starting") influx, influxErrors, _ := internal.NewInflux2Client(environment.InfluxUrl, environment.InfluxToken, environment.InfluxOrg, environment.InfluxBucket) From 5c496a3d205ff1477c372498997187e09802085f Mon Sep 17 00:00:00 2001 From: Petr John Date: Sat, 15 Mar 2025 19:39:44 +0100 Subject: [PATCH 24/26] Hotfix #25: Use float64 for timestamps that get saved into Influx for now (cherry picked from commit 3366e76d8c0d748a56d53c737e23f09de9eb14de) --- backend/commons/src/sharedModel/timeSeries.go | 7 ++++++- backend/mqtt-preprocessor/src/main.go | 7 ++++--- backend/mqtt-preprocessor/src/model/logimic.go | 2 +- backend/time-series-store/src/internal/Influx2Client.go | 3 ++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/commons/src/sharedModel/timeSeries.go b/backend/commons/src/sharedModel/timeSeries.go index 350a738..ddc59b7 100644 --- a/backend/commons/src/sharedModel/timeSeries.go +++ b/backend/commons/src/sharedModel/timeSeries.go @@ -31,7 +31,12 @@ func AreSimpleSensors(sensors interface{}) bool { type Operation string // InputData represents a single input data point. -type InputData KPIFulfillmentCheckRequestISCMessage +type InputData struct { + Timestamp float64 `json:"timestamp"` + SDInstanceUID string `json:"sdInstanceUID"` + SDTypeSpecification string `json:"sdTypeSpecification"` + Parameters any `json:"parameters"` +} // OutputData represents a single data point retrieved from InfluxDB. type OutputData struct { diff --git a/backend/mqtt-preprocessor/src/main.go b/backend/mqtt-preprocessor/src/main.go index b40d055..f7e6c7a 100644 --- a/backend/mqtt-preprocessor/src/main.go +++ b/backend/mqtt-preprocessor/src/main.go @@ -141,7 +141,6 @@ func processMQTTMessagePayload(mqttMessagePayload []byte, rabbitMQClient rabbitm Parameters: sd.Parameters, } jsonSerializationResult := sharedUtils.SerializeToJSON(inputData) - log.Println(inputData) err := rabbitMQClient.PublishJSONMessage(sharedUtils.NewEmptyOptional[string](), sharedUtils.NewOptionalOf(sharedConstants.TimeSeriesStoreDataQueueName), jsonSerializationResult.GetPayload()) if err != nil { @@ -149,14 +148,16 @@ func processMQTTMessagePayload(mqttMessagePayload []byte, rabbitMQClient rabbitm return } + log.Printf("Succesfully to published a time series store request message for %s with timestamp %s", inputData.SDInstanceUID, inputData.Timestamp) + if !mqttMessageSDTypeCorrespondsToSDTypeDefinitions(sd.Type) { return } switch determineSDInstanceScenario(sd.UID) { case unknownSDInstance: - generateSDInstanceRegistrationRequest(sd.UID, sd.Type, messagePayloadObject.Notification.Timestamp, rabbitMQClient) + generateSDInstanceRegistrationRequest(sd.UID, sd.Type, float32(messagePayloadObject.Notification.Timestamp), rabbitMQClient) case confirmedSDInstance: - generateKPIFulfillmentCheckRequest(sd.UID, sd.Type, sd.Parameters, messagePayloadObject.Notification.Timestamp, rabbitMQClient) + generateKPIFulfillmentCheckRequest(sd.UID, sd.Type, sd.Parameters, float32(messagePayloadObject.Notification.Timestamp), rabbitMQClient) } } diff --git a/backend/mqtt-preprocessor/src/model/logimic.go b/backend/mqtt-preprocessor/src/model/logimic.go index f273cb7..d8105f1 100644 --- a/backend/mqtt-preprocessor/src/model/logimic.go +++ b/backend/mqtt-preprocessor/src/model/logimic.go @@ -3,7 +3,7 @@ package model type UpstreamMQTTMessageInJSONBasedProprietaryFormatOfLogimic struct { Notification struct { MessageID string `json:"msgId"` - Timestamp float32 `json:"tst"` + Timestamp float64 `json:"tst"` } `json:"ntf"` Data struct { SDArray []struct { diff --git a/backend/time-series-store/src/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go index 7b25727..7a4a7ac 100644 --- a/backend/time-series-store/src/internal/Influx2Client.go +++ b/backend/time-series-store/src/internal/Influx2Client.go @@ -50,7 +50,7 @@ func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) share " %s\n"+ // time range " %s\n"+ // filter " %s\n"+ // aggregation - " |> drop(columns: [\"_start\", \"_stop\"])\n"+ + " |> drop(columns: [\"_start\", \"_stop\", \"host\", \"deviceType\"])\n"+ " |> pivot(columnKey: [\"_field\"], rowKey: [\"_measurement\", \"_time\"], valueColumn: \"_value\")\n"+ " |> rename(columns: {_time: \"time\", _measurement: \"deviceId\"})\n"+ " |> group(columns: [\"measurement\"], mode: \"by\")", imports, influx2Client.bucket, timeRange, filter, aggregation) @@ -77,6 +77,7 @@ func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) share } func (influx2Client Influx2Client) Write(data sharedModel.InputData) { + log.Printf("Writing %s with ts %s received ts %f", data.SDInstanceUID, time.Unix(int64(data.Timestamp), 0), data.Timestamp) if parameters, ok := data.Parameters.(map[string]interface{}); ok { point := influxdb2.NewPoint(data.SDInstanceUID, map[string]string{"deviceType": data.SDTypeSpecification}, parameters, time.Unix(int64(data.Timestamp), 0)) influx2Client.writeApi.WritePoint(point) From 25666baeee8f87ec9965a7947d4db514cf136a70 Mon Sep 17 00:00:00 2001 From: Petr John Date: Sat, 15 Mar 2025 20:43:10 +0100 Subject: [PATCH 25/26] Chore: Add back grafana prometheus and mqtt-prometheus-exporter under dev profile (cherry picked from commit c359fccff5ed2828d8f809c6805d68366f56542f) --- docker-compose.yml | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3af6391..1f54fac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '2' - services: riot-frontend: @@ -128,3 +126,35 @@ services: - "5672:5672" # Main (amqp) - "15672:15672" # Management - "15692:15692" # Prometheus + + grafana: + image: grafana/grafana-enterprise + profiles: ["dev"] + volumes: + - ./docker/grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards + - ./docker/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources + - ./docker/grafana/dashboards:/var/lib/grafana/dashboards + ports: + - "3000:3000" + environment: + GF_SECURITY_ADMIN_USER: admin + GF_SECURITY_ADMIN_PASSWORD: password + + prometheus: + image: prom/prometheus + profiles: ["dev"] + volumes: + - "./docker/prometheus.yml:/etc/prometheus/prometheus.yml" + ports: + - "9091:9090" + + mqtt-prometheus-exporter: + profiles: ["dev"] + image: kpetrem/mqtt-exporter + ports: + - "9000:9000" + environment: + MQTT_ADDRESS: mosquitto + MQTT_TOPIC: topic + MQTT_USERNAME: admin + MQTT_PASSWORD: password From 75a6b6eec135325c6fca5b9ba24ebdb82828b04c Mon Sep 17 00:00:00 2001 From: Petr John Date: Wed, 26 Mar 2025 21:49:40 +0100 Subject: [PATCH 26/26] Feature #1: Fix nil values breaking message ingestion into time series store (cherry picked from commit 1f0dea9497127111a687936e17b129dbcc6fb614) --- .../src/internal/Influx2Client.go | 35 ++++++++++++++----- backend/time-series-store/src/main.go | 8 +---- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/backend/time-series-store/src/internal/Influx2Client.go b/backend/time-series-store/src/internal/Influx2Client.go index 7a4a7ac..566c4aa 100644 --- a/backend/time-series-store/src/internal/Influx2Client.go +++ b/backend/time-series-store/src/internal/Influx2Client.go @@ -17,11 +17,11 @@ type Influx2Client struct { organization string bucket string client influxdb2.Client - writeApi api.WriteAPI + writeApi api.WriteAPIBlocking queryApi api.QueryAPI } -func NewInflux2Client(endpoint string, token string, organization string, bucket string) (Influx2Client, <-chan error, error) { +func NewInflux2Client(endpoint string, token string, organization string, bucket string) Influx2Client { client := influxdb2.NewClientWithOptions(endpoint, token, influxdb2.DefaultOptions().SetBatchSize(20)) influx2Client := Influx2Client{ @@ -29,10 +29,10 @@ func NewInflux2Client(endpoint string, token string, organization string, bucket organization: organization, bucket: bucket, client: client, - writeApi: client.WriteAPI(organization, bucket), + writeApi: client.WriteAPIBlocking(organization, bucket), queryApi: client.QueryAPI(organization), } - return influx2Client, influx2Client.writeApi.Errors(), nil + return influx2Client } func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) sharedUtils.Result[[]sharedModel.OutputData] { @@ -77,15 +77,34 @@ func (influx2Client Influx2Client) Query(body sharedModel.ReadRequestBody) share } func (influx2Client Influx2Client) Write(data sharedModel.InputData) { - log.Printf("Writing %s with ts %s received ts %f", data.SDInstanceUID, time.Unix(int64(data.Timestamp), 0), data.Timestamp) + log.Printf("Writing %s with ts %s received ts %f\n", data.SDInstanceUID, time.Unix(int64(data.Timestamp), 0), data.Timestamp) if parameters, ok := data.Parameters.(map[string]interface{}); ok { + // Drop nil / null values + for key, value := range parameters { + if value == nil { + log.Printf("Dropping %s because it was nil / null", key) + delete(parameters, key) + } + } + point := influxdb2.NewPoint(data.SDInstanceUID, map[string]string{"deviceType": data.SDTypeSpecification}, parameters, time.Unix(int64(data.Timestamp), 0)) - influx2Client.writeApi.WritePoint(point) + + ctx, cancelFunction := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFunction() + + err := influx2Client.writeApi.WritePoint(ctx, point) + if err != nil { + log.Printf("Writing %s failed with %s\n", data.SDInstanceUID, err) + } + + err = influx2Client.writeApi.Flush(ctx) + if err != nil { + log.Printf("Writing %s failed with %s\n", data.SDInstanceUID, err) + } + } else { log.Println("parameterAsAny is not a map[string]interface{}") } - - influx2Client.writeApi.Flush() } func (influx2Client Influx2Client) Close() { diff --git a/backend/time-series-store/src/main.go b/backend/time-series-store/src/main.go index 55d13ae..8a512f0 100644 --- a/backend/time-series-store/src/main.go +++ b/backend/time-series-store/src/main.go @@ -42,7 +42,7 @@ func main() { log.Println("Time Series Store Starting") - influx, influxErrors, _ := internal.NewInflux2Client(environment.InfluxUrl, environment.InfluxToken, environment.InfluxOrg, environment.InfluxBucket) + influx := internal.NewInflux2Client(environment.InfluxUrl, environment.InfluxToken, environment.InfluxOrg, environment.InfluxBucket) rabbitMQClient := rabbitmq.NewClient() defer rabbitMQClient.Dispose() @@ -66,12 +66,6 @@ func main() { } }, ) - - go func() { - for err := range influxErrors { - log.Println(err) - } - }() } func consumeInputMessages(rabbitMQClient rabbitmq.Client, influx internal.Influx2Client) error {