-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Grouping by with added labels working, but not sorted
- Loading branch information
1 parent
647ee7c
commit 46ce01d
Showing
6 changed files
with
261 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package framemap | ||
|
||
import ( | ||
"fmt" | ||
"github.com/grafana/grafana-plugin-sdk-go/data" | ||
"hash/fnv" | ||
) | ||
|
||
type FrameAndLabels struct { | ||
labels data.Labels | ||
fieldMap map[string]interface{} | ||
} | ||
|
||
func labelsEqual(labelsA data.Labels, labelsB data.Labels) bool { | ||
if len(labelsA) != len(labelsB) { | ||
return false | ||
} | ||
for key, value := range labelsA { | ||
otherValue, exists := labelsB[key] | ||
if !exists || value != otherValue { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
func labelsHash(labels data.Labels) uint64 { | ||
h := fnv.New64a() | ||
for key, value := range labels { | ||
_, err := h.Write([]byte(key)) | ||
if err != nil { | ||
panic(fmt.Sprintf("Error writing to hash: %v", err)) | ||
} | ||
_, err = h.Write([]byte(value)) | ||
if err != nil { | ||
panic(fmt.Sprintf("Error writing to hash: %v", err)) | ||
} | ||
} | ||
return h.Sum64() | ||
} | ||
|
||
type FrameMap struct { | ||
data map[uint64][]FrameAndLabels | ||
} | ||
|
||
func CreateFrameMap() FrameMap { | ||
return FrameMap{ | ||
data: map[uint64][]FrameAndLabels{}, | ||
} | ||
} | ||
|
||
func (f *FrameMap) Get(labels data.Labels) (map[string]interface{}, bool) { | ||
hash := labelsHash(labels) | ||
values, exists := f.data[hash] | ||
if !exists { | ||
return nil, false | ||
} | ||
|
||
for _, value := range values { | ||
if labelsEqual(value.labels, labels) { | ||
return value.fieldMap, true | ||
} | ||
} | ||
return nil, false | ||
} | ||
func (f *FrameMap) Put(labels data.Labels, fieldMap map[string]interface{}) { | ||
hash := labelsHash(labels) | ||
values, exists := f.data[hash] | ||
if !exists { | ||
f.data[hash] = []FrameAndLabels{{labels: labels, fieldMap: fieldMap}} | ||
return | ||
} | ||
for index, value := range values { | ||
if labelsEqual(value.labels, labels) { | ||
values[index] = FrameAndLabels{labels: labels, fieldMap: fieldMap} | ||
return | ||
} | ||
} | ||
|
||
newValues := append(values, FrameAndLabels{labels: labels, fieldMap: fieldMap}) | ||
f.data[hash] = newValues | ||
} | ||
|
||
func (f *FrameMap) ToFrames() []*data.Frame { | ||
// create data frame response. | ||
// For an overview on data frames and how grafana handles them: | ||
// https://grafana.com/developers/plugin-tools/introduction/data-frames | ||
// The goal here is to output a long format. If needed, prepare time series can transform it | ||
// https://grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/transform-data/#prepare-time-series | ||
|
||
// NOTE: The order of the frames here determines the order they appear in the legend in Grafana | ||
// A workaround on the frontend is to make the legend in "Table" mode and then sort the "Name" column: https://github.com/grafana/grafana/pull/69490 | ||
var r []*data.Frame | ||
for _, values := range f.data { | ||
for _, value := range values { | ||
frameName := fmt.Sprintf("response %v", value.labels) | ||
frame := data.NewFrame(frameName) | ||
for key, values := range value.fieldMap { | ||
frame.Fields = append(frame.Fields, | ||
data.NewField(key, value.labels, values), | ||
) | ||
} | ||
r = append(r, frame) | ||
} | ||
} | ||
return r | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package framemap | ||
|
||
import ( | ||
"github.com/grafana/grafana-plugin-sdk-go/data" | ||
"reflect" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestEqualityAndHash(t *testing.T) { | ||
labelsA := data.Labels{ | ||
"asdf": "a", | ||
} | ||
labelsB := data.Labels{ | ||
"asdf": "b", | ||
} | ||
if labelsEqual(labelsA, labelsB) { | ||
t.Error("labelsA should not equal labelsB") | ||
} | ||
if labelsHash(labelsA) == labelsHash(labelsB) { | ||
t.Error("Should not have the same hashes!") | ||
} | ||
} | ||
|
||
func TestFrameMap(t *testing.T) { | ||
fm := CreateFrameMap() | ||
labelsA := data.Labels{ | ||
"asdf": "a", | ||
} | ||
labelsB := data.Labels{ | ||
"asdf": "b", | ||
} | ||
dataA := map[string]interface{}{ | ||
"batteryVoltage": []float64{22.4, 22.5}, | ||
"dateMillis": []time.Time{time.UnixMilli(1705974650887), time.UnixMilli(1705974659884)}, | ||
} | ||
dataB := map[string]interface{}{ | ||
"batteryVoltage": []float64{22.43, 22.51}, | ||
"dateMillis": []time.Time{time.UnixMilli(1705974650888), time.UnixMilli(1705974659885)}, | ||
} | ||
|
||
_, exists := fm.Get(labelsA) | ||
if exists { | ||
t.Error("We haven't put anything in the map! Nothing should exist!") | ||
} | ||
|
||
fm.Put(labelsA, dataA) | ||
expectingDataA, exists := fm.Get(labelsA) | ||
if !exists { | ||
t.Error("We expect that fm.get(labelsA) exists now!") | ||
} | ||
if !reflect.DeepEqual(dataA, expectingDataA) { | ||
t.Error("We expect this to be dataA!") | ||
} | ||
|
||
fm.Put(labelsB, dataB) | ||
expectingDataB, exists := fm.Get(labelsB) | ||
if !exists { | ||
t.Error("We expect that fm.get(labelsB) exists now!") | ||
} | ||
if !reflect.DeepEqual(dataB, expectingDataB) { | ||
t.Error("We expect this to be dataB!") | ||
} | ||
|
||
if len(fm.ToFrames()) != 2 { | ||
t.Error("ToFrames() should result in an array of size 2!") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.