Skip to content

Commit

Permalink
Support event properties with dimension
Browse files Browse the repository at this point in the history
  • Loading branch information
diericd committed Dec 10, 2024
1 parent 2b3316b commit b84c043
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 60 deletions.
4 changes: 3 additions & 1 deletion pkg/datasource/event_query_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ func (ds *HistorianDataSource) handleEventQuery(ctx context.Context, eventQuery
assetPropertyFieldTypes := getAssetPropertyFieldTypes(eventAssetPropertyFrames)
return EventQueryResultToDataFrame(eventQuery.IncludeParentInfo, slices.Collect(maps.Values(assets)), events, allEventTypes, eventTypeProperties, selectedPropertiesSet, assetPropertyFieldTypes, eventAssetPropertyFrames)
case string(schemas.EventTypePropertyTypePeriodic):
return EventQueryResultToTrendDataFrame(eventQuery.IncludeParentInfo, slices.Collect(maps.Values(assets)), events, util.ByUUID(allEventTypes), eventTypePropertiesByEventType, selectedPropertiesSet, eventAssetPropertyFrames)
return EventQueryResultToTrendDataFrame(eventQuery.IncludeParentInfo, slices.Collect(maps.Values(assets)), events, util.ByUUID(allEventTypes), eventTypePropertiesByEventType, selectedPropertiesSet, eventAssetPropertyFrames, false)
case string(schemas.EventTypePropertyTypePeriodicWithDimension):
return EventQueryResultToTrendDataFrame(eventQuery.IncludeParentInfo, slices.Collect(maps.Values(assets)), events, util.ByUUID(allEventTypes), eventTypePropertiesByEventType, selectedPropertiesSet, eventAssetPropertyFrames, true)
default:
return nil, fmt.Errorf("unsupported event query type %s", eventQuery.Type)
}
Expand Down
34 changes: 25 additions & 9 deletions pkg/datasource/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ type eventFrameColumn struct {
}

// EventQueryResultToTrendDataFrame converts a event query result to data frames
func EventQueryResultToTrendDataFrame(includeParentInfo bool, assets []schemas.Asset, events []schemas.Event, eventTypes map[uuid.UUID]schemas.EventType, eventTypePropertiesForEventType map[uuid.UUID][]schemas.EventTypeProperty, selectedProperties map[string]struct{}, eventAssetPropertyFrames map[uuid.UUID]data.Frames) (data.Frames, error) {
func EventQueryResultToTrendDataFrame(includeParentInfo bool, assets []schemas.Asset, events []schemas.Event, eventTypes map[uuid.UUID]schemas.EventType, eventTypePropertiesForEventType map[uuid.UUID][]schemas.EventTypeProperty, selectedProperties map[string]struct{}, eventAssetPropertyFrames map[uuid.UUID]data.Frames, byDimension bool) (data.Frames, error) {
uuidToAssetMap := make(map[uuid.UUID]schemas.Asset)
for _, asset := range assets {
uuidToAssetMap[asset.UUID] = asset
Expand All @@ -78,26 +78,30 @@ func EventQueryResultToTrendDataFrame(includeParentInfo bool, assets []schemas.A
for _, eventType := range eventTypes {
eventTypeProperties := eventTypePropertiesForEventType[eventType.UUID]
for _, eventTypeProperty := range eventTypeProperties {
if eventTypeProperty.Type == schemas.EventTypePropertyTypePeriodic {
if (!byDimension && eventTypeProperty.Type == schemas.EventTypePropertyTypePeriodic) || eventTypeProperty.Type == schemas.EventTypePropertyTypePeriodicWithDimension {
if _, ok := selectedProperties[eventTypeProperty.Name]; len(selectedProperties) == 0 || ok {
periodicEventTypeProperties = append(periodicEventTypeProperties, eventTypeProperty)
} else if _, ok := selectedProperties[eventTypeProperty.UUID.String()]; len(selectedProperties) == 0 || ok {
periodicEventTypeProperties = append(periodicEventTypeProperties, eventTypeProperty)
}
} else {
}
if eventTypeProperty.Type == schemas.EventTypePropertyTypeSimple {
simpleEventTypeProperties = append(simpleEventTypeProperties, eventTypeProperty)
}
}
}

periodicPropertyData := map[float64]map[eventFrameColumn]interface{}{}

columns := []eventFrameColumn{}

for i := range events {
eventLabels := data.Labels{}
for j := range simpleEventTypeProperties {
name := simpleEventTypeProperties[j].Name
if events[i].Properties == nil {
break
}

if value, ok := events[i].Properties.Properties[name]; ok {
eventLabels[name] = fmt.Sprintf("%v", value)
}
Expand All @@ -122,6 +126,10 @@ func EventQueryResultToTrendDataFrame(includeParentInfo bool, assets []schemas.A
labels := data.Labels{}
maps.Copy(labels, eventLabels)
periodicPropertyValues := util.PeriodicPropertyValues{}
if events[i].Properties == nil {
continue
}

if err := events[i].Properties.Properties.Get(periodicEventTypeProperties[j].Name, &periodicPropertyValues); err != nil {
continue
}
Expand Down Expand Up @@ -188,13 +196,19 @@ func EventQueryResultToTrendDataFrame(includeParentInfo bool, assets []schemas.A
setUOMFieldConfig(identifier.Field, periodicEventTypeProperties[j])
columns = append(columns, identifier)

for k := range periodicPropertyValues.Offsets {
offset := periodicPropertyValues.Offsets[k]
for offset, value := range periodicPropertyValues.ValuesByOffset {
if byDimension {
if dimensionValue, ok := periodicPropertyValues.DimensionValuesByOffset[offset].(float64); ok {
offset = dimensionValue
}
}

if _, ok := periodicPropertyData[offset]; !ok {
periodicPropertyData[offset] = map[eventFrameColumn]interface{}{}
}
periodicPropertyData[offset][identifier] = periodicPropertyValues.Values[k]
periodicPropertyData[offset][identifier] = value
}

}

assetPropertyFrames := eventAssetPropertyFrames[events[i].UUID]
Expand Down Expand Up @@ -249,8 +263,10 @@ func EventQueryResultToTrendDataFrame(includeParentInfo bool, assets []schemas.A
data.NewField("Offset", nil, []float64{}),
}

fields[0].Config = &data.FieldConfig{
Unit: "dtdhms",
if !byDimension {
fields[0].Config = &data.FieldConfig{
Unit: "dtdhms",
}
}

offsets := slices.Sorted(maps.Keys(periodicPropertyData))
Expand Down
5 changes: 3 additions & 2 deletions pkg/schemas/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ const (

// The order determines how EventTypePropertyType gets sorted
const (
EventTypePropertyTypeSimple PropertyType = "simple"
EventTypePropertyTypePeriodic PropertyType = "periodic"
EventTypePropertyTypeSimple PropertyType = "simple"
EventTypePropertyTypePeriodic PropertyType = "periodic"
EventTypePropertyTypePeriodicWithDimension PropertyType = "periodic_with_dimension"
)

// EventTypeProperty has the fields of an event type property that are used by the data source
Expand Down
170 changes: 141 additions & 29 deletions pkg/util/periodic_property_values.go
Original file line number Diff line number Diff line change
@@ -1,66 +1,178 @@
package util

import "slices"
import (
"encoding/json"

"maps"
"slices"
)

// PeriodicPropertyValues is a map of offsets and values
type PeriodicPropertyValues struct {
// Offsets is a list of offsets
Offsets []float64 `json:"t"`
// Values is a list of values
Values []interface{} `json:"v"`
ValuesByOffset map[float64]interface{} `json:"-"`
DimensionValuesByOffset map[float64]interface{} `json:"-"`
}

// NewPeriodicPropertyValues creates a new PeriodicPropertyValues
func NewPeriodicPropertyValues() *PeriodicPropertyValues {
return &PeriodicPropertyValues{
ValuesByOffset: map[float64]interface{}{},
}
}

// NewPeriodicPropertyValuesWithDimension creates a new PeriodicPropertyValues with dimension values
func NewPeriodicPropertyValuesWithDimension() *PeriodicPropertyValues {
return &PeriodicPropertyValues{
ValuesByOffset: map[float64]interface{}{},
DimensionValuesByOffset: map[float64]interface{}{},
}
}

// HasDimensionValues returns true if the values have dimension values
func (p *PeriodicPropertyValues) HasDimensionValues() bool {
return p.DimensionValuesByOffset != nil
}

// GetValueAt returns the value at the given offset
func (p *PeriodicPropertyValues) GetValueAt(offset float64) interface{} {
index := slices.Index(p.Offsets, offset)
if index == -1 {
return p.ValuesByOffset[offset]
}

// GetDimensionValueAt returns the dimension value at the given offset
func (p *PeriodicPropertyValues) GetDimensionValueAt(offset float64) interface{} {
if p.DimensionValuesByOffset == nil {
return nil
}

return p.Values[index]
return p.DimensionValuesByOffset[offset]
}

// SetValueAt sets the value at the given offset
func (p *PeriodicPropertyValues) SetValueAt(offset float64, value interface{}) {
index := slices.Index(p.Offsets, offset)
if index == -1 {
p.Offsets = append(p.Offsets, offset)
p.Values = append(p.Values, value)
p.ValuesByOffset[offset] = value
if p.DimensionValuesByOffset != nil {
p.DimensionValuesByOffset[offset] = nil
}
}

// SetDimensionValueAt sets the dimension value at the given offset
func (p *PeriodicPropertyValues) SetDimensionValueAt(offset float64, values interface{}) {
if p.DimensionValuesByOffset == nil {
return
}

p.Values[index] = value
p.DimensionValuesByOffset[offset] = values
p.ValuesByOffset[offset] = nil
}

// AppendValue appends a value to the end of the values
func (p *PeriodicPropertyValues) AppendValue(offset float64, value interface{}) {
p.Offsets = append(p.Offsets, offset)
p.Values = append(p.Values, value)
if p.DimensionValuesByOffset != nil {
p.DimensionValuesByOffset[offset] = nil
}

p.ValuesByOffset[offset] = value
}

// RemoveValueAt removes a value at the given offset
func (p *PeriodicPropertyValues) RemoveValueAt(offset float64) {
index := slices.Index(p.Offsets, offset)
if index == -1 {
delete(p.ValuesByOffset, offset)
if p.DimensionValuesByOffset != nil {
delete(p.DimensionValuesByOffset, offset)
}
}

// SetValueAtWithDimension sets the value at the given offset with a unit value
func (p *PeriodicPropertyValues) SetValueAtWithDimension(offset float64, value interface{}, unitValue interface{}) {
if p.DimensionValuesByOffset == nil {
return
}

p.Offsets = append(p.Offsets[:index], p.Offsets[index+1:]...)
p.Values = append(p.Values[:index], p.Values[index+1:]...)
p.ValuesByOffset[offset] = value
p.DimensionValuesByOffset[offset] = unitValue
}

// AppendValueWithDimension appends a value to the end of the values with a unit value
func (p *PeriodicPropertyValues) AppendValueWithDimension(offset float64, value interface{}, unitValue interface{}) {
p.SetValueAtWithDimension(offset, value, unitValue)
}

type periodicPropertyValuesJSON struct {
Offsets []float64 `json:"t"`
Values []interface{} `json:"v"`
DimensionValues []interface{} `json:"d,omitempty"`
}

// Sort sorts the values by offset
func (p *PeriodicPropertyValues) Sort() {
mapped := make(map[float64]interface{})
for i, offset := range p.Offsets {
mapped[offset] = p.Values[i]
// MarshalJSON marshals the PeriodicPropertyValues to JSON
func (p *PeriodicPropertyValues) MarshalJSON() ([]byte, error) {
t := make([]float64, 0, len(p.ValuesByOffset))
v := make([]interface{}, 0, len(p.ValuesByOffset))
var d []interface{}
if p.DimensionValuesByOffset != nil {
d = make([]interface{}, 0, len(p.ValuesByOffset))
}

// Sort the offsets
offsets := slices.Sorted(maps.Keys(p.ValuesByOffset))

for _, offset := range offsets {
t = append(t, offset)
v = append(v, p.ValuesByOffset[offset])
if p.DimensionValuesByOffset != nil {
d = append(d, p.DimensionValuesByOffset[offset])
}
}

result := periodicPropertyValuesJSON{
Offsets: t,
Values: v,
}

if p.DimensionValuesByOffset != nil {
result.DimensionValues = d
}

return json.Marshal(result)
}

// ParseValues parses the values in the PeriodicPropertyValues
func (p *PeriodicPropertyValues) ParseValues(parser func(interface{}) (interface{}, error)) error {
values := make(map[float64]interface{})

for offset, value := range p.ValuesByOffset {
parsedValue, err := parser(value)
if err != nil {
return err
}

values[offset] = parsedValue
}

p.ValuesByOffset = values

return nil
}

// UnmarshalJSON unmarshals the PeriodicPropertyValues from JSON
func (p *PeriodicPropertyValues) UnmarshalJSON(data []byte) error {
v := periodicPropertyValuesJSON{}

if err := json.Unmarshal(data, &v); err != nil {
return err
}

p.ValuesByOffset = map[float64]interface{}{}

if v.DimensionValues != nil {
p.DimensionValuesByOffset = map[float64]interface{}{}
}

sortedValues := make([]interface{}, len(p.Offsets))
slices.Sort(p.Offsets)
for i, offset := range p.Offsets {
sortedValues[i] = mapped[offset]
for i := range v.Offsets {
p.ValuesByOffset[v.Offsets[i]] = v.Values[i]
if len(v.DimensionValues) > i {
p.DimensionValuesByOffset[v.Offsets[i]] = v.DimensionValues[i]
}
}

p.Values = sortedValues
return nil
}
13 changes: 13 additions & 0 deletions pkg/util/semver.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ func min(a, b int) int {

// SemverCompare compares two semver strings, returns true if a is less than b
func SemverCompare(a string, b string) bool {
if a == b {
return false
}

// debug is always more than any other version
if b == "debug" {
return true
}

if a == "debug" {
return false
}

aParts := strings.Split(a, ".")
bParts := strings.Split(b, ".")
length := min(len(aParts), len(bParts))
Expand Down
Loading

0 comments on commit b84c043

Please sign in to comment.