Skip to content

Commit 2696237

Browse files
add option for display of enums (#33)
1 parent b92f75f commit 2696237

5 files changed

Lines changed: 498 additions & 7 deletions

File tree

pkg/plugin/datasource.go

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ const QueryVersion = "2.1"
3939

4040
const maxParallelDataQueries = 10
4141

42+
const (
43+
EnumDisplayNone = ""
44+
EnumDisplayBoth = "both"
45+
EnumDisplayValue = "value"
46+
EnumDisplayString = "string"
47+
)
48+
4249
var ValidSiftGrafanaDataTypes = []string{
4350
"CHANNEL_DATA_TYPE_STRING",
4451
"CHANNEL_DATA_TYPE_BOOL",
@@ -237,6 +244,7 @@ type queryModel struct {
237244
commonQueryProperties
238245
ChannelDataQueries []channelDataQuery `json:"channelDataQueries"`
239246
CombineRuns bool `json:"combineRuns"`
247+
EnumDisplay string `json:"enumDisplay"`
240248
QueryVersion string `json:"queryVersion"`
241249
}
242250

@@ -393,7 +401,7 @@ func (d *SiftDatasource) query(pCtx backend.PluginContext, query backend.DataQue
393401
}
394402
afterExecutingQueries := time.Now()
395403

396-
frame, err := generateDataFrame(responseData, calculatedChannelKeys, fqm.CombineRuns)
404+
frame, err := generateDataFrame(responseData, calculatedChannelKeys, fqm.CombineRuns, fqm.EnumDisplay)
397405
if err != nil {
398406
return backend.ErrDataResponse(backend.StatusBadRequest, fmt.Sprintf("error generating data frame: %v", err.Error()))
399407
}
@@ -534,7 +542,7 @@ func runDataQueries(pCtx backend.PluginContext, queries []siftApiGetDataSubQuery
534542
return allData, nil
535543
}
536544

537-
func generateDataFrame(responseData []queryResponseData, calculatedChannelKeys map[string]calculatedChannelKey, combineRuns bool) (*data.Frame, error) {
545+
func generateDataFrame(responseData []queryResponseData, calculatedChannelKeys map[string]calculatedChannelKey, combineRuns bool, enumDisplay string) (*data.Frame, error) {
538546
// create data frame response.
539547
// For an overview on data frames and how grafana handles them:
540548
// https://grafana.com/developers/plugin-tools/introduction/data-frames
@@ -765,6 +773,9 @@ func generateDataFrame(responseData []queryResponseData, calculatedChannelKeys m
765773
return allDataKeys[i].runId < allDataKeys[j].runId && allDataKeys[i].channelId < allDataKeys[j].channelId
766774
})
767775

776+
// Track enum field base names for filtering later
777+
enumFieldBaseNames := map[string]bool{}
778+
768779
for _, key := range allDataKeys {
769780
m := md[key]
770781
name := m.Channel.Name
@@ -832,6 +843,8 @@ func generateDataFrame(responseData []queryResponseData, calculatedChannelKeys m
832843
field = data.NewField(name, labels, []*uint64{})
833844

834845
case "CHANNEL_DATA_TYPE_ENUM":
846+
// Track the base name for this enum field
847+
enumFieldBaseNames[name] = true
835848
if key.isEnumString {
836849
name = name + "_string"
837850
field = data.NewField(name, labels, []*string{})
@@ -856,6 +869,46 @@ func generateDataFrame(responseData []queryResponseData, calculatedChannelKeys m
856869
frame.Fields = append(frame.Fields, field)
857870
}
858871

872+
// Filter and rename enum fields based on enumDisplay setting
873+
if enumDisplay == EnumDisplayString || enumDisplay == EnumDisplayValue {
874+
875+
filteredFields := []*data.Field{}
876+
for _, field := range frame.Fields {
877+
fieldName := field.Name
878+
// Check if this is an enum field by looking for the suffix
879+
isEnumField := false
880+
baseName := ""
881+
keepField := false
882+
883+
if strings.HasSuffix(fieldName, "_string") {
884+
baseName = strings.TrimSuffix(fieldName, "_string")
885+
if enumFieldBaseNames[baseName] {
886+
isEnumField = true
887+
keepField = enumDisplay == EnumDisplayString
888+
}
889+
} else if strings.HasSuffix(fieldName, "_value") {
890+
baseName = strings.TrimSuffix(fieldName, "_value")
891+
if enumFieldBaseNames[baseName] {
892+
isEnumField = true
893+
keepField = enumDisplay == EnumDisplayValue
894+
}
895+
}
896+
897+
if isEnumField {
898+
if keepField {
899+
// Rename the field to remove the suffix
900+
field.Name = baseName
901+
filteredFields = append(filteredFields, field)
902+
}
903+
// Skip fields we don't want to keep
904+
} else {
905+
// Keep non-enum fields as-is
906+
filteredFields = append(filteredFields, field)
907+
}
908+
}
909+
frame.Fields = filteredFields
910+
}
911+
859912
// Sort fields by channel name first.
860913
// Calculated channels will all have the
861914
// same channel name, so we also sort by labels.

pkg/plugin/datasource_test.go

Lines changed: 222 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,7 @@ func (s *DatasourceTestSuite) TestGenerateDataFrameWithMultipleDataTypes() {
11111111
},
11121112
}
11131113

1114-
frame, err := generateDataFrame(responseData, nil, false)
1114+
frame, err := generateDataFrame(responseData, nil, false, EnumDisplayBoth)
11151115
s.NoError(err)
11161116

11171117
// Verify frame structure
@@ -1179,7 +1179,7 @@ func (s *DatasourceTestSuite) TestGenerateDataFrameHandlesCalculatedChannels() {
11791179
},
11801180
}
11811181

1182-
frame, err := generateDataFrame(responseData, calculatedChannelKeys, false)
1182+
frame, err := generateDataFrame(responseData, calculatedChannelKeys, false, EnumDisplayBoth)
11831183
s.NoError(err)
11841184

11851185
// Verify frame structure
@@ -1232,7 +1232,7 @@ func (s *DatasourceTestSuite) TestGenerateDataFrameHandlesGroupByRun() {
12321232
},
12331233
}
12341234

1235-
frame, err := generateDataFrame(responseData, nil, false)
1235+
frame, err := generateDataFrame(responseData, nil, false, EnumDisplayBoth)
12361236
s.NoError(err)
12371237

12381238
// Verify frame structure
@@ -1944,3 +1944,222 @@ func (s *DatasourceTestSuite) channelsMatch(expected, actual *siftApiChannel) bo
19441944
}
19451945
return true
19461946
}
1947+
1948+
func (s *DatasourceTestSuite) TestGenerateDataFrameWithEnumDisplayBoth() {
1949+
now := time.Now()
1950+
responseData := []queryResponseData{
1951+
{
1952+
Metadata: queryResponseMetadata{
1953+
DataType: "CHANNEL_DATA_TYPE_ENUM",
1954+
Asset: struct {
1955+
AssetId string "json:\"assetId\""
1956+
Name string "json:\"name\""
1957+
}{
1958+
AssetId: "asset1",
1959+
Name: "Asset 1",
1960+
},
1961+
Channel: struct {
1962+
ChannelId string "json:\"channelId\""
1963+
Name string "json:\"name\""
1964+
EnumTypes []queryResponseChannelEnumType "json:\"enumTypes\""
1965+
BitFieldElements []queryResponseChannelBitFieldElement "json:\"bitFieldElements\""
1966+
}{
1967+
ChannelId: "channel1",
1968+
Name: "Status",
1969+
EnumTypes: []queryResponseChannelEnumType{
1970+
{Name: "ACTIVE", Key: 1},
1971+
{Name: "INACTIVE", Key: 2},
1972+
},
1973+
},
1974+
},
1975+
Values: json.RawMessage(`[
1976+
{"timestamp": "` + now.Format(time.RFC3339Nano) + `", "value": 1},
1977+
{"timestamp": "` + now.Add(time.Second).Format(time.RFC3339Nano) + `", "value": 2}
1978+
]`),
1979+
},
1980+
}
1981+
1982+
frame, err := generateDataFrame(responseData, nil, true, EnumDisplayBoth)
1983+
s.NoError(err)
1984+
1985+
// Verify frame structure - should have both _string and _value fields
1986+
s.Equal(3, len(frame.Fields))
1987+
s.Equal("time", frame.Fields[0].Name)
1988+
s.Equal("Status_string", frame.Fields[1].Name)
1989+
s.Equal("Status_value", frame.Fields[2].Name)
1990+
1991+
// Verify enum string values
1992+
s.Equal("ACTIVE", *frame.Fields[1].At(0).(*string))
1993+
s.Equal("INACTIVE", *frame.Fields[1].At(1).(*string))
1994+
1995+
// Verify enum numeric values
1996+
s.Equal(uint32(1), *frame.Fields[2].At(0).(*uint32))
1997+
s.Equal(uint32(2), *frame.Fields[2].At(1).(*uint32))
1998+
}
1999+
2000+
func (s *DatasourceTestSuite) TestGenerateDataFrameWithEnumDisplayString() {
2001+
now := time.Now()
2002+
responseData := []queryResponseData{
2003+
{
2004+
Metadata: queryResponseMetadata{
2005+
DataType: "CHANNEL_DATA_TYPE_ENUM",
2006+
Asset: struct {
2007+
AssetId string "json:\"assetId\""
2008+
Name string "json:\"name\""
2009+
}{
2010+
AssetId: "asset1",
2011+
Name: "Asset 1",
2012+
},
2013+
Channel: struct {
2014+
ChannelId string "json:\"channelId\""
2015+
Name string "json:\"name\""
2016+
EnumTypes []queryResponseChannelEnumType "json:\"enumTypes\""
2017+
BitFieldElements []queryResponseChannelBitFieldElement "json:\"bitFieldElements\""
2018+
}{
2019+
ChannelId: "channel1",
2020+
Name: "Status",
2021+
EnumTypes: []queryResponseChannelEnumType{
2022+
{Name: "ACTIVE", Key: 1},
2023+
{Name: "INACTIVE", Key: 2},
2024+
},
2025+
},
2026+
},
2027+
Values: json.RawMessage(`[
2028+
{"timestamp": "` + now.Format(time.RFC3339Nano) + `", "value": 1},
2029+
{"timestamp": "` + now.Add(time.Second).Format(time.RFC3339Nano) + `", "value": 2}
2030+
]`),
2031+
},
2032+
}
2033+
2034+
frame, err := generateDataFrame(responseData, nil, true, EnumDisplayString)
2035+
s.NoError(err)
2036+
2037+
// Verify frame structure - should only have string field without suffix
2038+
s.Equal(2, len(frame.Fields))
2039+
s.Equal("time", frame.Fields[0].Name)
2040+
s.Equal("Status", frame.Fields[1].Name) // No _string suffix
2041+
2042+
// Verify enum string values
2043+
s.Equal("ACTIVE", *frame.Fields[1].At(0).(*string))
2044+
s.Equal("INACTIVE", *frame.Fields[1].At(1).(*string))
2045+
}
2046+
2047+
func (s *DatasourceTestSuite) TestGenerateDataFrameWithEnumDisplayValue() {
2048+
now := time.Now()
2049+
responseData := []queryResponseData{
2050+
{
2051+
Metadata: queryResponseMetadata{
2052+
DataType: "CHANNEL_DATA_TYPE_ENUM",
2053+
Asset: struct {
2054+
AssetId string "json:\"assetId\""
2055+
Name string "json:\"name\""
2056+
}{
2057+
AssetId: "asset1",
2058+
Name: "Asset 1",
2059+
},
2060+
Channel: struct {
2061+
ChannelId string "json:\"channelId\""
2062+
Name string "json:\"name\""
2063+
EnumTypes []queryResponseChannelEnumType "json:\"enumTypes\""
2064+
BitFieldElements []queryResponseChannelBitFieldElement "json:\"bitFieldElements\""
2065+
}{
2066+
ChannelId: "channel1",
2067+
Name: "Status",
2068+
EnumTypes: []queryResponseChannelEnumType{
2069+
{Name: "ACTIVE", Key: 1},
2070+
{Name: "INACTIVE", Key: 2},
2071+
},
2072+
},
2073+
},
2074+
Values: json.RawMessage(`[
2075+
{"timestamp": "` + now.Format(time.RFC3339Nano) + `", "value": 1},
2076+
{"timestamp": "` + now.Add(time.Second).Format(time.RFC3339Nano) + `", "value": 2}
2077+
]`),
2078+
},
2079+
}
2080+
2081+
frame, err := generateDataFrame(responseData, nil, true, EnumDisplayValue)
2082+
s.NoError(err)
2083+
2084+
// Verify frame structure - should only have value field without suffix
2085+
s.Equal(2, len(frame.Fields))
2086+
s.Equal("time", frame.Fields[0].Name)
2087+
s.Equal("Status", frame.Fields[1].Name) // No _value suffix
2088+
2089+
// Verify enum numeric values
2090+
s.Equal(uint32(1), *frame.Fields[1].At(0).(*uint32))
2091+
s.Equal(uint32(2), *frame.Fields[1].At(1).(*uint32))
2092+
}
2093+
2094+
func (s *DatasourceTestSuite) TestGenerateDataFrameWithEnumDisplayMixedChannels() {
2095+
now := time.Now()
2096+
responseData := []queryResponseData{
2097+
{
2098+
Metadata: queryResponseMetadata{
2099+
DataType: "CHANNEL_DATA_TYPE_ENUM",
2100+
Asset: struct {
2101+
AssetId string "json:\"assetId\""
2102+
Name string "json:\"name\""
2103+
}{
2104+
AssetId: "asset1",
2105+
Name: "Asset 1",
2106+
},
2107+
Channel: struct {
2108+
ChannelId string "json:\"channelId\""
2109+
Name string "json:\"name\""
2110+
EnumTypes []queryResponseChannelEnumType "json:\"enumTypes\""
2111+
BitFieldElements []queryResponseChannelBitFieldElement "json:\"bitFieldElements\""
2112+
}{
2113+
ChannelId: "channel1",
2114+
Name: "Status",
2115+
EnumTypes: []queryResponseChannelEnumType{
2116+
{Name: "ON", Key: 1},
2117+
{Name: "OFF", Key: 2},
2118+
},
2119+
},
2120+
},
2121+
Values: json.RawMessage(`[
2122+
{"timestamp": "` + now.Format(time.RFC3339Nano) + `", "value": 1}
2123+
]`),
2124+
},
2125+
{
2126+
Metadata: queryResponseMetadata{
2127+
DataType: "CHANNEL_DATA_TYPE_DOUBLE",
2128+
Asset: struct {
2129+
AssetId string "json:\"assetId\""
2130+
Name string "json:\"name\""
2131+
}{
2132+
AssetId: "asset1",
2133+
Name: "Asset 1",
2134+
},
2135+
Channel: struct {
2136+
ChannelId string "json:\"channelId\""
2137+
Name string "json:\"name\""
2138+
EnumTypes []queryResponseChannelEnumType "json:\"enumTypes\""
2139+
BitFieldElements []queryResponseChannelBitFieldElement "json:\"bitFieldElements\""
2140+
}{
2141+
ChannelId: "channel2",
2142+
Name: "Temperature",
2143+
},
2144+
},
2145+
Values: json.RawMessage(`[
2146+
{"timestamp": "` + now.Format(time.RFC3339Nano) + `", "value": 23.5}
2147+
]`),
2148+
},
2149+
}
2150+
2151+
frame, err := generateDataFrame(responseData, nil, true, EnumDisplayString)
2152+
s.NoError(err)
2153+
2154+
// Verify frame structure - enum should be filtered, non-enum should remain
2155+
s.Equal(3, len(frame.Fields))
2156+
s.Equal("time", frame.Fields[0].Name)
2157+
s.Equal("Status", frame.Fields[1].Name) // Enum without suffix
2158+
s.Equal("Temperature", frame.Fields[2].Name) // Non-enum unchanged
2159+
2160+
// Verify enum string value
2161+
s.Equal("ON", *frame.Fields[1].At(0).(*string))
2162+
2163+
// Verify temperature value
2164+
s.Equal(23.5, *frame.Fields[2].At(0).(*float64))
2165+
}

0 commit comments

Comments
 (0)