From 92dd5dcf190adca86d6f5eca7f2a5a22121f011e Mon Sep 17 00:00:00 2001 From: Wannes Vereecken Date: Mon, 2 Dec 2024 10:09:40 +0100 Subject: [PATCH] Added include parent info option to Events query editor --- pkg/datasource/event_query_handler.go | 38 +++- pkg/datasource/events.go | 287 +++++++++++++++++++------- pkg/schemas/events.go | 2 + pkg/schemas/queries.go | 1 + pkg/util/util.go | 14 ++ src/QueryEditor/Events.tsx | 15 ++ src/types.ts | 1 + 7 files changed, 272 insertions(+), 86 deletions(-) diff --git a/pkg/datasource/event_query_handler.go b/pkg/datasource/event_query_handler.go index 2e138c0..d253980 100644 --- a/pkg/datasource/event_query_handler.go +++ b/pkg/datasource/event_query_handler.go @@ -28,6 +28,11 @@ func (ds *HistorianDataSource) handleEventQuery(ctx context.Context, eventQuery return nil, err } + allEventTypes, err := ds.API.GetEventTypes(ctx, "") + if err != nil { + return nil, err + } + if len(assets) == 0 || len(eventTypes) == 0 { return data.Frames{}, nil } @@ -48,10 +53,19 @@ func (ds *HistorianDataSource) handleEventQuery(ctx context.Context, eventQuery return nil, err } - eventTypeProperties := []schemas.EventTypeProperty{} + // get all unique event types from the events + eventTypeUUIDs := map[uuid.UUID]struct{}{} + for i := range events { + eventTypeUUIDs[events[i].EventTypeUUID] = struct{}{} + if events[i].Parent != nil { + eventTypeUUIDs[events[i].Parent.EventTypeUUID] = struct{}{} + } + } + + var eventTypeProperties []schemas.EventTypeProperty if util.CheckMinimumVersion(historianInfo, "6.4.0") { eventTypeQuery := url.Values{} - for i, eventTypeUUID := range slices.Collect(maps.Keys(eventTypes)) { + for i, eventTypeUUID := range slices.Collect(maps.Keys(eventTypeUUIDs)) { eventTypeQuery.Add(fmt.Sprintf("EventTypeUUIDs[%d]", i), eventTypeUUID.String()) } eventTypeQuery.Add("Types[0]", eventQuery.Type) @@ -60,16 +74,10 @@ func (ds *HistorianDataSource) handleEventQuery(ctx context.Context, eventQuery return nil, err } } else { - allEventTypeProperties, err := ds.API.GetEventTypeProperties(ctx, "") + eventTypeProperties, err = ds.API.GetEventTypeProperties(ctx, "") if err != nil { return nil, err } - - for _, eventTypeProperty := range allEventTypeProperties { - if _, ok := eventTypes[eventTypeProperty.EventTypeUUID]; ok { - eventTypeProperties = append(eventTypeProperties, eventTypeProperty) - } - } } selectedPropertiesSet := map[string]struct{}{} @@ -99,12 +107,20 @@ func (ds *HistorianDataSource) handleEventQuery(ctx context.Context, eventQuery } } + eventTypePropertiesByEventType := map[uuid.UUID][]schemas.EventTypeProperty{} + for _, eventTypeProperty := range eventTypeProperties { + if _, ok := eventTypePropertiesByEventType[eventTypeProperty.EventTypeUUID]; !ok { + eventTypePropertiesByEventType[eventTypeProperty.EventTypeUUID] = []schemas.EventTypeProperty{} + } + eventTypePropertiesByEventType[eventTypeProperty.EventTypeUUID] = append(eventTypePropertiesByEventType[eventTypeProperty.EventTypeUUID], eventTypeProperty) + } + switch eventQuery.Type { case string(schemas.EventTypePropertyTypeSimple): assetPropertyFieldTypes := getAssetPropertyFieldTypes(eventAssetPropertyFrames) - return EventQueryResultToDataFrame(slices.Collect(maps.Values(assets)), events, slices.Collect(maps.Values(eventTypes)), eventTypeProperties, selectedPropertiesSet, assetPropertyFieldTypes, eventAssetPropertyFrames) + return EventQueryResultToDataFrame(eventQuery.IncludeParentInfo, slices.Collect(maps.Values(assets)), events, allEventTypes, eventTypeProperties, selectedPropertiesSet, assetPropertyFieldTypes, eventAssetPropertyFrames) case string(schemas.EventTypePropertyTypePeriodic): - return EventQueryResultToTrendDataFrame(slices.Collect(maps.Values(assets)), events, slices.Collect(maps.Values(eventTypes)), eventTypeProperties, selectedPropertiesSet, eventAssetPropertyFrames) + return EventQueryResultToTrendDataFrame(eventQuery.IncludeParentInfo, slices.Collect(maps.Values(assets)), events, util.ByUUID(allEventTypes), eventTypePropertiesByEventType, selectedPropertiesSet, eventAssetPropertyFrames) default: return nil, fmt.Errorf("unsupported event query type %s", eventQuery.Type) } diff --git a/pkg/datasource/events.go b/pkg/datasource/events.go index 8214b84..471662e 100644 --- a/pkg/datasource/events.go +++ b/pkg/datasource/events.go @@ -5,6 +5,7 @@ import ( "maps" "slices" "strconv" + "strings" "time" "github.com/factrylabs/factry-historian-datasource.git/pkg/schemas" @@ -29,7 +30,7 @@ const ( ) // EventQueryResultToDataFrame converts a event query result to data frames -func EventQueryResultToDataFrame(assets []schemas.Asset, events []schemas.Event, eventTypes []schemas.EventType, eventTypeProperties []schemas.EventTypeProperty, selectedProperties map[string]struct{}, assetPropertyFieldTypes map[string]data.FieldType, eventAssetPropertyFrames map[uuid.UUID]data.Frames) (data.Frames, error) { +func EventQueryResultToDataFrame(includeParentInfo bool, assets []schemas.Asset, events []schemas.Event, eventTypes []schemas.EventType, eventTypeProperties []schemas.EventTypeProperty, selectedProperties map[string]struct{}, assetPropertyFieldTypes map[string]data.FieldType, eventAssetPropertyFrames map[uuid.UUID]data.Frames) (data.Frames, error) { dataFrames := data.Frames{} groupedEvents := map[uuid.UUID][]schemas.Event{} eventTypePropertiesForEventType := map[uuid.UUID][]schemas.EventTypeProperty{} @@ -38,11 +39,11 @@ func EventQueryResultToDataFrame(assets []schemas.Asset, events []schemas.Event, eventTypesByUUID[eventType.UUID] = eventType eventTypePropertiesForEventType[eventType.UUID] = []schemas.EventTypeProperty{} for _, eventTypeProperty := range eventTypeProperties { - if _, ok := selectedProperties[eventTypeProperty.Name]; (len(selectedProperties) == 0 || ok) && eventTypeProperty.EventTypeUUID == eventType.UUID { - eventTypePropertiesForEventType[eventType.UUID] = append(eventTypePropertiesForEventType[eventType.UUID], eventTypeProperty) - } else if _, ok := selectedProperties[eventTypeProperty.UUID.String()]; (len(selectedProperties) == 0 || ok) && eventTypeProperty.EventTypeUUID == eventType.UUID { - eventTypePropertiesForEventType[eventType.UUID] = append(eventTypePropertiesForEventType[eventType.UUID], eventTypeProperty) + if eventTypeProperty.EventTypeUUID != eventType.UUID { + continue } + + eventTypePropertiesForEventType[eventType.UUID] = append(eventTypePropertiesForEventType[eventType.UUID], eventTypeProperty) } } @@ -51,7 +52,7 @@ func EventQueryResultToDataFrame(assets []schemas.Asset, events []schemas.Event, } for eventTypeUUID, groupedEvents := range groupedEvents { - dataFrames = append(dataFrames, dataFrameForEventType(assets, eventTypesByUUID[eventTypeUUID], groupedEvents, eventTypePropertiesForEventType[eventTypeUUID], assetPropertyFieldTypes, eventAssetPropertyFrames)) + dataFrames = append(dataFrames, dataFrameForEventType(includeParentInfo, assets, eventTypesByUUID[eventTypeUUID], selectedProperties, eventTypesByUUID, groupedEvents, eventTypePropertiesForEventType, assetPropertyFieldTypes, eventAssetPropertyFrames)) } return dataFrames, nil @@ -65,29 +66,27 @@ type eventFrameColumn struct { } // EventQueryResultToTrendDataFrame converts a event query result to data frames -func EventQueryResultToTrendDataFrame(assets []schemas.Asset, events []schemas.Event, eventTypes []schemas.EventType, eventTypeProperties []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) (data.Frames, error) { uuidToAssetMap := make(map[uuid.UUID]schemas.Asset) for _, asset := range assets { uuidToAssetMap[asset.UUID] = asset } - uuidToEventTypeMap := make(map[uuid.UUID]schemas.EventType) - for _, eventType := range eventTypes { - uuidToEventTypeMap[eventType.UUID] = eventType - } - simpleEventTypeProperties := []schemas.EventTypeProperty{} periodicEventTypeProperties := []schemas.EventTypeProperty{} - for _, eventTypeProperty := range eventTypeProperties { - if eventTypeProperty.Type == schemas.EventTypePropertyTypePeriodic { - 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) + for _, eventType := range eventTypes { + eventTypeProperties := eventTypePropertiesForEventType[eventType.UUID] + for _, eventTypeProperty := range eventTypeProperties { + if eventTypeProperty.Type == schemas.EventTypePropertyTypePeriodic { + 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 { + simpleEventTypeProperties = append(simpleEventTypeProperties, eventTypeProperty) } - } else { - simpleEventTypeProperties = append(simpleEventTypeProperties, eventTypeProperty) } } @@ -106,7 +105,7 @@ func EventQueryResultToTrendDataFrame(assets []schemas.Asset, events []schemas.E eventLabels[AssetPathColumnName] = getAssetPath(uuidToAssetMap, events[i].AssetUUID) eventLabels[AssetColumnName] = uuidToAssetMap[events[i].AssetUUID].Name - eventLabels[EventTypeColumnName] = uuidToEventTypeMap[events[i].EventTypeUUID].Name + eventLabels[EventTypeColumnName] = eventTypes[events[i].EventTypeUUID].Name eventLabels[StartTimeColumnName] = events[i].StartTime.Format(time.RFC3339) eventLabels[StopTimeColumnName] = "" eventLabels[EventUUIDColumnName] = events[i].UUID.String() @@ -136,6 +135,45 @@ func EventQueryResultToTrendDataFrame(assets []schemas.Asset, events []schemas.E name := fmt.Sprintf("%s (%s)", periodicEventTypeProperties[j].Name, events[i].UUID) + // Add parent event details as labels if `includeParentInfo` is true + if includeParentInfo && events[i].Parent != nil { + parentEvent := *events[i].Parent + parentType := eventTypes[parentEvent.EventTypeUUID] + parentPrefix := parentType.Name + "_" + + // Add parent event details + labels[parentPrefix+EventUUIDColumnName] = parentEvent.UUID.String() + labels[parentPrefix+StartTimeColumnName] = parentEvent.StartTime.Format(time.RFC3339) + labels[parentPrefix+StopTimeColumnName] = "" + if parentEvent.StopTime != nil { + eventLabels[parentPrefix+StopTimeColumnName] = parentEvent.StopTime.Format(time.RFC3339) + } + labels[parentPrefix+AssetUUIDColumnName] = parentEvent.AssetUUID.String() + labels[parentPrefix+AssetColumnName] = uuidToAssetMap[parentEvent.AssetUUID].Name + labels[parentPrefix+AssetPathColumnName] = getAssetPath(uuidToAssetMap, parentEvent.AssetUUID) + labels[parentPrefix+EventTypeUUIDColumnName] = parentEvent.EventTypeUUID.String() + labels[parentPrefix+EventTypeColumnName] = parentType.Name + + // Add parent event properties + for _, parentProperty := range eventTypePropertiesForEventType[parentEvent.EventTypeUUID] { + if parentProperty.Type == schemas.EventTypePropertyTypePeriodic { + continue + } + + name := parentPrefix + parentProperty.Name + if parentEvent.Properties == nil || parentEvent.Properties.Properties == nil { + labels[name] = "" + continue + } + + if value, ok := parentEvent.Properties.Properties[parentProperty.Name]; ok { + labels[name] = fmt.Sprintf("%v", value) + } else { + labels[name] = "" + } + } + } + switch periodicEventTypeProperties[j].Datatype { case schemas.EventTypePropertyDatatypeBool: identifier.Field = data.NewField(name, labels, []*bool{}) @@ -234,35 +272,97 @@ func EventQueryResultToTrendDataFrame(assets []schemas.Asset, events []schemas.E }, nil } -func dataFrameForEventType(assets []schemas.Asset, eventType schemas.EventType, events []schemas.Event, eventTypeProperties []schemas.EventTypeProperty, assetPropertyFieldTypes map[string]data.FieldType, eventAssetPropertyFrames map[uuid.UUID]data.Frames) *data.Frame { +func buildSimpleFieldsForEvent(prefix string, eventTypeProperties []schemas.EventTypeProperty) []*data.Field { + fields := []*data.Field{ + data.NewField(prefix+EventUUIDColumnName, nil, []*string{}), + data.NewField(prefix+ParentEventUUIDColumnName, nil, []string{}), + data.NewField(prefix+AssetUUIDColumnName, nil, []*string{}), + data.NewField(prefix+EventTypeUUIDColumnName, nil, []*string{}), + data.NewField(prefix+AssetColumnName, nil, []*string{}), + data.NewField(prefix+AssetPathColumnName, nil, []*string{}), + data.NewField(prefix+EventTypeColumnName, nil, []*string{}), + data.NewField(prefix+StartTimeColumnName, nil, []*time.Time{}), + data.NewField(prefix+StopTimeColumnName, nil, []*time.Time{}), + data.NewField(prefix+DurationColumnName, nil, []*float64{}), + } + + for _, parentEventTypeProperty := range eventTypeProperties { + if parentEventTypeProperty.Type == schemas.EventTypePropertyTypePeriodic { + continue + } + + parentPropertyFieldName := prefix + parentEventTypeProperty.Name + var parentField *data.Field + switch parentEventTypeProperty.Datatype { + case schemas.EventTypePropertyDatatypeBool: + parentField = data.NewField(parentPropertyFieldName, nil, []*bool{}) + case schemas.EventTypePropertyDatatypeNumber: + parentField = data.NewField(parentPropertyFieldName, nil, []*float64{}) + case schemas.EventTypePropertyDatatypeString: + parentField = data.NewField(parentPropertyFieldName, nil, []*string{}) + default: + parentField = data.NewField(parentPropertyFieldName, nil, []interface{}{}) + } + fields = append(fields, parentField) + } + return fields +} + +func fillFields(prefix string, fieldByColumn map[string]*data.Field, event *schemas.Event, uuidToAssetMap map[uuid.UUID]schemas.Asset, eventType schemas.EventType, eventTypeProperties []schemas.EventTypeProperty) { + parentUUID := "" + if event.ParentUUID != nil { + parentUUID = event.ParentUUID.String() + } + addValueToField(fieldByColumn[prefix+EventUUIDColumnName], event.UUID.String()) + addValueToField(fieldByColumn[prefix+ParentEventUUIDColumnName], parentUUID) + addValueToField(fieldByColumn[prefix+AssetUUIDColumnName], event.AssetUUID.String()) + addValueToField(fieldByColumn[prefix+EventTypeUUIDColumnName], event.EventTypeUUID.String()) + addValueToField(fieldByColumn[prefix+AssetColumnName], uuidToAssetMap[event.AssetUUID].Name) + addValueToField(fieldByColumn[prefix+AssetPathColumnName], getAssetPath(uuidToAssetMap, event.AssetUUID)) + addValueToField(fieldByColumn[prefix+EventTypeColumnName], eventType.Name) + addValueToField(fieldByColumn[prefix+StartTimeColumnName], &event.StartTime) + addValueToField(fieldByColumn[prefix+StopTimeColumnName], event.StopTime) + + if event.StopTime != nil { + duration := event.StopTime.Sub(event.StartTime).Seconds() + fieldByColumn[prefix+DurationColumnName].Append(&duration) + } else { + fieldByColumn[prefix+DurationColumnName].Append(nil) + } + + if event.Properties == nil { + for _, eventPropertyType := range eventTypeProperties { + if eventPropertyType.Type == schemas.EventTypePropertyTypePeriodic { + continue + } + + fieldByColumn[prefix+eventPropertyType.Name].Append(nil) + } + return + } + + for _, eventTypeProperty := range eventTypeProperties { + if eventTypeProperty.Type == schemas.EventTypePropertyTypePeriodic { + continue + } + + setUOMFieldConfig(fieldByColumn[prefix+eventTypeProperty.Name], eventTypeProperty) + addValueToField(fieldByColumn[prefix+eventTypeProperty.Name], event.Properties.Properties[eventTypeProperty.Name]) + } +} + +func dataFrameForEventType(includeParentInfo bool, assets []schemas.Asset, eventType schemas.EventType, selectedProperties map[string]struct{}, eventTypes map[uuid.UUID]schemas.EventType, events []schemas.Event, eventTypePropertiesForEventType map[uuid.UUID][]schemas.EventTypeProperty, assetPropertyFieldTypes map[string]data.FieldType, eventAssetPropertyFrames map[uuid.UUID]data.Frames) *data.Frame { uuidToAssetMap := make(map[uuid.UUID]schemas.Asset) for _, asset := range assets { uuidToAssetMap[asset.UUID] = asset } - fieldByColumn := map[string]*data.Field{} - fieldByColumn[StartTimeColumnName] = data.NewField(StartTimeColumnName, nil, []*time.Time{}) - fieldByColumn[StopTimeColumnName] = data.NewField(StopTimeColumnName, nil, []*time.Time{}) - fieldByColumn[EventUUIDColumnName] = data.NewField(EventUUIDColumnName, nil, []string{}) - fieldByColumn[ParentEventUUIDColumnName] = data.NewField(ParentEventUUIDColumnName, nil, []string{}) - fieldByColumn[AssetUUIDColumnName] = data.NewField(AssetUUIDColumnName, nil, []string{}) - fieldByColumn[AssetColumnName] = data.NewField(AssetColumnName, nil, []string{}) - fieldByColumn[AssetPathColumnName] = data.NewField(AssetPathColumnName, nil, []string{}) - fieldByColumn[DurationColumnName] = data.NewField(DurationColumnName, nil, []*float64{}) - fieldByColumn[EventTypeUUIDColumnName] = data.NewField(EventTypeUUIDColumnName, nil, []string{}) - fieldByColumn[EventTypeColumnName] = data.NewField(EventTypeColumnName, nil, []string{}) + eventTypeProperties := eventTypePropertiesForEventType[eventType.UUID] + fields := buildSimpleFieldsForEvent("", eventTypeProperties) - fields := []*data.Field{ - fieldByColumn[EventUUIDColumnName], - fieldByColumn[ParentEventUUIDColumnName], - fieldByColumn[AssetUUIDColumnName], - fieldByColumn[EventTypeUUIDColumnName], - fieldByColumn[AssetColumnName], - fieldByColumn[AssetPathColumnName], - fieldByColumn[EventTypeColumnName], - fieldByColumn[StartTimeColumnName], - fieldByColumn[StopTimeColumnName], - fieldByColumn[DurationColumnName], + fieldByColumn := map[string]*data.Field{} + for _, field := range fields { + fieldByColumn[field.Name] = field } for assetProperty := range assetPropertyFieldTypes { @@ -286,6 +386,14 @@ func dataFrameForEventType(assets []schemas.Asset, eventType schemas.EventType, continue } + if len(selectedProperties) > 0 { + if _, ok := selectedProperties[eventTypeProperty.Name]; !ok { + if _, ok := selectedProperties[eventTypeProperty.UUID.String()]; !ok { + continue + } + } + } + var field *data.Field switch eventTypeProperty.Datatype { case schemas.EventTypePropertyDatatypeBool: @@ -302,28 +410,32 @@ func dataFrameForEventType(assets []schemas.Asset, eventType schemas.EventType, fieldByColumn[eventTypeProperty.Name] = field } + parentFields := []*data.Field{} + parentFieldsAdded := make(map[string]bool) // Track added fields for parent event types + for i := range events { - fieldByColumn[EventUUIDColumnName].Append(events[i].UUID.String()) - parentUUID := "" if events[i].ParentUUID != nil { - parentUUID = events[i].ParentUUID.String() - } - fieldByColumn[ParentEventUUIDColumnName].Append(parentUUID) - fieldByColumn[AssetUUIDColumnName].Append(events[i].AssetUUID.String()) - fieldByColumn[EventTypeUUIDColumnName].Append(events[i].EventTypeUUID.String()) - fieldByColumn[AssetColumnName].Append(uuidToAssetMap[events[i].AssetUUID].Name) - fieldByColumn[AssetPathColumnName].Append(getAssetPath(uuidToAssetMap, events[i].AssetUUID)) - fieldByColumn[EventTypeColumnName].Append(eventType.Name) - fieldByColumn[StartTimeColumnName].Append(&events[i].StartTime) - fieldByColumn[StopTimeColumnName].Append(events[i].StopTime) + if includeParentInfo { + parentEvent := *events[i].Parent + parentEventType, parentEventTypeExists := eventTypes[parentEvent.EventTypeUUID] + if !parentEventTypeExists { + continue + } - if events[i].StopTime != nil { - duration := events[i].StopTime.Sub(events[i].StartTime).Seconds() - fieldByColumn[DurationColumnName].Append(&duration) - } else { - fieldByColumn[DurationColumnName].Append(nil) + parentPrefix := parentEventType.Name + "_" + if !parentFieldsAdded[parentPrefix] { + parentFieldsAdded[parentPrefix] = true + + parentFieldsForEventType := buildSimpleFieldsForEvent(parentPrefix, eventTypePropertiesForEventType[parentEvent.EventTypeUUID]) + for _, parentField := range parentFieldsForEventType { + fieldByColumn[parentField.Name] = parentField + parentFields = append(parentFields, parentField) + } + } + } } + fillFields("", fieldByColumn, &events[i], uuidToAssetMap, eventType, eventTypeProperties) assetPropertyFrames := eventAssetPropertyFrames[events[i].UUID] for assetProperty := range assetPropertyFieldTypes { found := false @@ -365,26 +477,51 @@ func dataFrameForEventType(assets []schemas.Asset, eventType schemas.EventType, field.Append(nil) } } + } - if events[i].Properties == nil { - for _, eventPropertyType := range eventTypeProperties { - if eventPropertyType.Type == schemas.EventTypePropertyTypePeriodic { - continue + if includeParentInfo { + // Populate parent data or append nil for rows without a parent + for i := range events { + if events[i].Parent != nil { + if _, ok := eventTypes[events[i].Parent.EventTypeUUID]; ok { + parentEvent := *events[i].Parent + parentEventType := eventTypes[parentEvent.EventTypeUUID] + parentPrefix := parentEventType.Name + "_" + + fillFields(parentPrefix, fieldByColumn, &parentEvent, uuidToAssetMap, parentEventType, eventTypePropertiesForEventType[parentEvent.EventTypeUUID]) + + // Append nil for other parent-related fields if there are multiple parent event types + for otherParentPrefix := range parentFieldsAdded { + if otherParentPrefix == parentPrefix { + continue + } + + for _, field := range fieldByColumn { + // skip if field is not a parent-related field + if !strings.HasPrefix(field.Name, otherParentPrefix) { + continue + } + + addValueToField(field, nil) + } + } } - - fieldByColumn[eventPropertyType.Name].Append(nil) - } - continue - } - - for _, eventTypeProperty := range eventTypeProperties { - if eventTypeProperty.Type == schemas.EventTypePropertyTypePeriodic { continue } - setUOMFieldConfig(fieldByColumn[eventTypeProperty.Name], eventTypeProperty) - addValueToField(fieldByColumn[eventTypeProperty.Name], events[i].Properties.Properties[eventTypeProperty.Name]) + // Append nil for all parent-related fields if no parent exists + for parentPrefix := range parentFieldsAdded { + for _, parentField := range fieldByColumn { + if !strings.HasPrefix(parentField.Name, parentPrefix) { + continue + } + + addValueToField(parentField, nil) + } + } } + + fields = append(parentFields, fields...) } return data.NewFrame(eventType.Name, fields...) diff --git a/pkg/schemas/events.go b/pkg/schemas/events.go index b1455f5..c47c2ec 100644 --- a/pkg/schemas/events.go +++ b/pkg/schemas/events.go @@ -32,6 +32,7 @@ type Event struct { StopTime *time.Time ParentUUID *uuid.UUID Properties *EventProperties + Parent *Event Source EventSource Status EventStatus UUID uuid.UUID @@ -49,6 +50,7 @@ type EventFilter struct { Status []string EventConfigurations []uuid.UUID PropertyFilter []EventPropertyValueFilter + IncludeParentInfo bool Limit int ExcludeManualEvents bool Ascending bool diff --git a/pkg/schemas/queries.go b/pkg/schemas/queries.go index 66e1dfb..a585922 100644 --- a/pkg/schemas/queries.go +++ b/pkg/schemas/queries.go @@ -58,6 +58,7 @@ type EventQuery struct { Statuses []string Properties []string PropertyFilter []EventPropertyValueFilter + IncludeParentInfo bool QueryAssetProperties bool AssetProperties []string Options *MeasurementQueryOptions diff --git a/pkg/util/util.go b/pkg/util/util.go index dd4e54d..2999145 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -90,3 +90,17 @@ func MarshalStructToMap(input interface{}) map[string]interface{} { return result } + +// Ptr returns a pointer to the provided value +func Ptr[T any](v T) *T { + return &v +} + +// PtrSlice returns a slice of pointers to the provided values +func PtrSlice[T any](v []T) []*T { + ptrs := make([]*T, len(v)) + for i := range v { + ptrs[i] = &v[i] + } + return ptrs +} diff --git a/src/QueryEditor/Events.tsx b/src/QueryEditor/Events.tsx index 4c2c3b2..c5bc30a 100644 --- a/src/QueryEditor/Events.tsx +++ b/src/QueryEditor/Events.tsx @@ -254,6 +254,12 @@ export const Events = (props: Props): JSX.Element => { props.onChangeEventQuery(updatedQuery) } + const onChangeIncludeParentInfo = (event: FormEvent): void => { + const enabled = (event as ChangeEvent).target.checked + const updatedQuery = { ...props.query, IncludeParentInfo: enabled } as EventQuery + props.onChangeEventQuery(updatedQuery) + } + const onChangeAssetProperties = (values: string[]): void => { const updatedQuery = { ...props.query, AssetProperties: values } props.onChangeEventQuery(updatedQuery) @@ -352,6 +358,15 @@ export const Events = (props: Props): JSX.Element => { /> + + + + +
diff --git a/src/types.ts b/src/types.ts index 870e440..062d2a0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -128,6 +128,7 @@ export interface EventQuery { QueryAssetProperties: boolean AssetProperties?: string[] Options?: MeasurementQueryOptions + IncludeParentInfo?: boolean } export enum EventTypePropertyType {