Skip to content

Commit

Permalink
Add variable query for distinct event property values
Browse files Browse the repository at this point in the history
  • Loading branch information
vereecw committed Nov 15, 2024
1 parent 7b944e5 commit b995b41
Show file tree
Hide file tree
Showing 17 changed files with 924 additions and 239 deletions.
49 changes: 49 additions & 0 deletions pkg/api/historian.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"

"github.com/factrylabs/factry-historian-datasource.git/pkg/schemas"
"github.com/go-playground/form"
"golang.org/x/exp/maps"
)

// GetMeasurements calls get measurements in the historian API
Expand Down Expand Up @@ -198,3 +200,50 @@ func (api *API) GetInfo(ctx context.Context) (schemas.HistorianInfo, error) {

return info, nil
}

// GetDistinctEventPropertyValues calls get distinct event property values in the historian API
func (api *API) GetDistinctEventPropertyValues(ctx context.Context, eventTypePropertyUUID string, request schemas.EventPropertyValuesRequest) ([]interface{}, error) {
assets, err := api.GetFilteredAssets(ctx, request.Assets, &request.HistorianInfo)
if err != nil {
return nil, err
}

eventTypes, err := api.GetFilteredEventTypes(ctx, request.EventTypes, &request.HistorianInfo)
if err != nil {
return nil, err
}

if len(assets) == 0 || len(eventTypes) == 0 {
return []interface{}{}, nil
}

filter := schemas.EventFilter{
AssetUUIDs: maps.Keys(assets),
EventTypeUUIDs: maps.Keys(eventTypes),
Limit: 0,
Status: request.Statuses,
StartTime: request.From,
StopTime: request.To,
}
encoder := form.NewEncoder()
urlValues, err := encoder.Encode(filter)
if err != nil {
return nil, err
}

eventTypePropertyValues := schemas.EventPropertyValues{}
response, err := api.client.R().SetContext(ctx).SetQueryParamsFromValues(urlValues).Get("/api/event-type-properties/" + eventTypePropertyUUID + "/values")
if err != nil {
return nil, err
}

if response.StatusCode() >= 300 {
return nil, handleHistorianError(response)
}

if err := json.Unmarshal(response.Body(), &eventTypePropertyValues); err != nil {
return nil, err
}

return eventTypePropertyValues, nil
}
114 changes: 114 additions & 0 deletions pkg/api/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package api

import (
"context"
"net/url"
"regexp"
"strings"

"github.com/factrylabs/factry-historian-datasource.git/pkg/schemas"
"github.com/factrylabs/factry-historian-datasource.git/pkg/util"
"github.com/google/uuid"
)

// GetFilteredAssets returns a map of assets that match the given asset strings
func (api *API) GetFilteredAssets(ctx context.Context, assetStrings []string, historianInfo *schemas.HistorianInfo) (map[uuid.UUID]schemas.Asset, error) {
assetUUIDSet := map[uuid.UUID]schemas.Asset{}

if util.CheckMinimumVersion(historianInfo, "6.4.0") {
for _, assetString := range assetStrings {
assetQuery := url.Values{}
assetQuery.Add("Keyword", assetString)
assets, err := api.GetAssets(ctx, assetQuery.Encode())
if err != nil {
return nil, err
}

assetUUIDSet = util.ByUUID(assets)
}
} else {
// Deprecated
assets, err := api.GetAssets(ctx, "")
if err != nil {
return nil, err
}

for _, assetString := range assetStrings {
if filteredAssets := filterAssetUUIDs(assets, assetString); len(filteredAssets) > 0 {
assetUUIDSet = util.ByUUID(filteredAssets)
}
}
}

return assetUUIDSet, nil
}

// GetFilteredEventTypes returns a map of event types that match the given event type strings
func (api *API) GetFilteredEventTypes(ctx context.Context, eventTypeStrings []string, historianInfo *schemas.HistorianInfo) (map[uuid.UUID]schemas.EventType, error) {
eventTypeUUIDSet := map[uuid.UUID]schemas.EventType{}

if util.CheckMinimumVersion(historianInfo, "6.4.0") {
for _, eventTypeString := range eventTypeStrings {
eventTypeQuery := url.Values{}
eventTypeQuery.Add("Keyword", eventTypeString)
eventTypes, err := api.GetEventTypes(ctx, eventTypeQuery.Encode())
if err != nil {
return nil, err
}

eventTypeUUIDSet = util.ByUUID(eventTypes)
}
} else {
// Deprecated
eventTypes, err := api.GetEventTypes(ctx, "")
if err != nil {
return nil, err
}

for _, eventTypeString := range eventTypeStrings {
if filteredEventTypes := filterEventTypeUUIDs(eventTypes, eventTypeString); len(filteredEventTypes) > 0 {
eventTypeUUIDSet = util.ByUUID(filteredEventTypes)
}
}
}

return eventTypeUUIDSet, nil
}

func filterItems[T any](items []T, searchValue string, matchFuncs ...func(T) string) []T {
filteredItems := make([]T, 0, len(items))
if len(searchValue) == 0 {
return filteredItems // Early exit for empty search
}
var re *regexp.Regexp
if strings.HasPrefix(searchValue, "/") && strings.HasSuffix(searchValue, "/") {
if len(searchValue) > 2 {
pattern := searchValue[1 : len(searchValue)-1]
var err error
re, err = regexp.Compile(pattern)
if err != nil {
return filteredItems
}
}
}

for _, item := range items {
for _, matchFunc := range matchFuncs {
if (re != nil && re.MatchString(matchFunc(item))) || (re == nil && matchFunc(item) == searchValue) {
filteredItems = append(filteredItems, item)
break
}
}
}
return filteredItems
}
func filterAssetUUIDs(assets []schemas.Asset, searchValue string) []schemas.Asset {
return filterItems(assets, searchValue,
func(asset schemas.Asset) string { return asset.AssetPath },
func(asset schemas.Asset) string { return asset.UUID.String() })
}
func filterEventTypeUUIDs(eventTypes []schemas.EventType, searchValue string) []schemas.EventType {
return filterItems(eventTypes, searchValue,
func(eventType schemas.EventType) string { return eventType.Name },
func(eventType schemas.EventType) string { return eventType.UUID.String() })
}
42 changes: 4 additions & 38 deletions pkg/datasource/event_query_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@ import (

"github.com/factrylabs/factry-historian-datasource.git/pkg/api"
"github.com/factrylabs/factry-historian-datasource.git/pkg/schemas"
"github.com/factrylabs/factry-historian-datasource.git/pkg/util"
"github.com/google/uuid"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"golang.org/x/exp/maps"
)

func handleEventQuery(ctx context.Context, eventQuery schemas.EventQuery, backendQuery backend.DataQuery, seriesLimit int, historianInfo *schemas.HistorianInfo, api *api.API) (data.Frames, error) {
assets, err := getFilteredAssets(ctx, api, eventQuery.Assets, historianInfo)
assets, err := api.GetFilteredAssets(ctx, eventQuery.Assets, historianInfo)
if err != nil {
return nil, err
}

eventTypes, err := getFilteredEventTypes(ctx, api, eventQuery.EventTypes, historianInfo)
eventTypes, err := api.GetFilteredEventTypes(ctx, eventQuery.EventTypes, historianInfo)
if err != nil {
return nil, err
}
Expand All @@ -47,7 +48,7 @@ func handleEventQuery(ctx context.Context, eventQuery schemas.EventQuery, backen
}

eventTypeProperties := []schemas.EventTypeProperty{}
if checkMinimumVersion(historianInfo, "6.4.0") {
if util.CheckMinimumVersion(historianInfo, "6.4.0") {
eventTypeQuery := url.Values{}
for i, eventTypeUUID := range maps.Keys(eventTypes) {
eventTypeQuery.Add(fmt.Sprintf("EventTypeUUIDs[%d]", i), eventTypeUUID.String())
Expand Down Expand Up @@ -182,38 +183,3 @@ func getAssetPropertyFieldTypes(eventAssetPropertyFrames map[uuid.UUID]data.Fram
}
return assetPropertyFieldTypes
}

func getFilteredEventTypes(ctx context.Context, api *api.API, eventTypeStrings []string, historianInfo *schemas.HistorianInfo) (map[uuid.UUID]schemas.EventType, error) {
eventTypeUUIDSet := map[uuid.UUID]schemas.EventType{}

if checkMinimumVersion(historianInfo, "6.4.0") {
for _, eventTypeString := range eventTypeStrings {
eventTypeQuery := url.Values{}
eventTypeQuery.Add("Keyword", eventTypeString)
eventTypes, err := api.GetEventTypes(ctx, eventTypeQuery.Encode())
if err != nil {
return nil, err
}

for _, eventType := range eventTypes {
eventTypeUUIDSet[eventType.UUID] = eventType
}
}
} else {
// Deprecated
eventTypes, err := api.GetEventTypes(ctx, "")
if err != nil {
return nil, err
}

for _, eventTypeString := range eventTypeStrings {
if filteredEventTypeUUIDs := filterEventTypeUUIDs(eventTypes, eventTypeString); len(filteredEventTypeUUIDs) > 0 {
for eventTypeUUID, eventType := range filteredEventTypeUUIDs {
eventTypeUUIDSet[eventTypeUUID] = eventType
}
}
}
}

return eventTypeUUIDSet, nil
}
21 changes: 6 additions & 15 deletions pkg/datasource/historian.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package datasource

import (
"context"
"strings"

"github.com/factrylabs/factry-historian-datasource.git/pkg/api"
"github.com/factrylabs/factry-historian-datasource.git/pkg/schemas"
"github.com/factrylabs/factry-historian-datasource.git/pkg/util"
"github.com/go-playground/form"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
)
Expand All @@ -27,7 +25,8 @@ const (

// HistorianDataSource ...
type HistorianDataSource struct {
API *api.API
API *api.API
Decoder *form.Decoder
}

// NewDataSource creates a new data source instance
Expand All @@ -37,7 +36,9 @@ func NewDataSource(_ context.Context, s backend.DataSourceInstanceSettings) (ins
return nil, err
}

historianDataSource := &HistorianDataSource{}
historianDataSource := &HistorianDataSource{
Decoder: form.NewDecoder(),
}
historianDataSource.API, err = api.NewAPIWithToken(settings.URL, settings.Token, settings.Organization)
if err != nil {
return nil, err
Expand All @@ -48,13 +49,3 @@ func NewDataSource(_ context.Context, s backend.DataSourceInstanceSettings) (ins

// Dispose here tells plugin SDK that plugin wants to clean up resources when a new instance is created.
func (*HistorianDataSource) Dispose() {}

func checkMinimumVersion(info *schemas.HistorianInfo, minVersion string) bool {
if info == nil {
return false
}

historianVersion, _ := strings.CutPrefix(info.Version, "v")
// check if historian version is not less than minVersion
return !util.SemverCompare(historianVersion, minVersion)
}
Loading

0 comments on commit b995b41

Please sign in to comment.