Skip to content

Commit

Permalink
Refactor RedisTimeSeries and RedisGears commands (#202)
Browse files Browse the repository at this point in the history
* Refactor RedisTimeSeries and RedisGears commands

* Add tests
mikhail-vl authored May 18, 2021
1 parent 8946bed commit 1a900bb
Showing 17 changed files with 328 additions and 234 deletions.
57 changes: 57 additions & 0 deletions pkg/models/redis-gears.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package models

/**
* RedisGears Commands
*/
const (
GearsPyStats = "rg.pystats"
GearsDumpRegistrations = "rg.dumpregistrations"
GearsPyExecute = "rg.pyexecute"
GearsPyDumpReqs = "rg.pydumpreqs"
)

/**
* RG.PYSTATS Radix marshaling
*/
type PyStats struct {
TotalAllocated int64 `redis:"TotalAllocated"`
PeakAllocated int64 `redis:"PeakAllocated"`
CurrAllocated int64 `redis:"CurrAllocated"`
}

/**
* RG.DUMPREGISTRATIONS Radix marshaling
*/
type DumpRegistrations struct {
ID string `redis:"id"`
Reader string `redis:"reader"`
Desc string `redis:"desc"`
RegistrationData RegistrationData `redis:"RegistrationData"`
PD string `redis:"PD"`
}

/**
* Registration data for RG.DUMPREGISTRATIONS Radix marshaling
*/
type RegistrationData struct {
Mode string `redis:"mode"`
NumTriggered int64 `redis:"numTriggered"`
NumSuccess int64 `redis:"numSuccess"`
NumFailures int64 `redis:"numFailures"`
NumAborted int64 `redis:"numAborted"`
LastError string `redis:"lastError"`
Args map[string]interface{} `redis:"args"`
Status string `redis:"status"`
}

/**
* RG.PYDUMPREQS Radix marshaling
*/
type PyDumpReq struct {
GearReqVersion int64 `redis:"GearReqVersion"`
Name string `redis:"Name"`
IsDownloaded string `redis:"IsDownloaded"`
IsInstalled string `redis:"IsInstalled"`
CompiledOs string `redis:"CompiledOs"`
Wheels []string `redis:"Wheels"`
}
14 changes: 8 additions & 6 deletions pkg/models/redis-graph.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package models

/**
* Commands
* RedisGraph Commands
*/
const GraphConfig = "graph.config"
const GraphExplain = "graph.explain"
const GraphProfile = "graph.profile"
const GraphQuery = "graph.query"
const GraphSlowlog = "graph.slowlog"
const (
GraphConfig = "graph.config"
GraphExplain = "graph.explain"
GraphProfile = "graph.profile"
GraphQuery = "graph.query"
GraphSlowlog = "graph.slowlog"
)

/**
* Represents node
12 changes: 12 additions & 0 deletions pkg/models/redis-time-series.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package models

/**
* RedisTimeSeries Commands
*/
const (
TimeSeriesGet = "ts.get"
TimeSeriesInfo = "ts.info"
TimeSeriesQueryIndex = "ts.queryindex"
TimeSeriesRange = "ts.range"
TimeSeriesMRange = "ts.mrange"
)
18 changes: 9 additions & 9 deletions pkg/query.go
Original file line number Diff line number Diff line change
@@ -39,15 +39,15 @@ func query(ctx context.Context, query backend.DataQuery, client redisClient, qm
/**
* Redis Timeseries
*/
case "ts.get":
case models.TimeSeriesGet:
return queryTsGet(qm, client)
case "ts.info":
case models.TimeSeriesInfo:
return queryTsInfo(qm, client)
case "ts.queryindex":
case models.TimeSeriesQueryIndex:
return queryTsQueryIndex(qm, client)
case "ts.range":
case models.TimeSeriesRange:
return queryTsRange(from, to, qm, client)
case "ts.mrange":
case models.TimeSeriesMRange:
return queryTsMRange(from, to, qm, client)

/**
@@ -107,13 +107,13 @@ func query(ctx context.Context, query backend.DataQuery, client redisClient, qm
/**
* Redis Gears
*/
case "rg.pystats":
case models.GearsPyStats:
return queryRgPystats(qm, client)
case "rg.dumpregistrations":
case models.GearsDumpRegistrations:
return queryRgDumpregistrations(qm, client)
case "rg.pyexecute":
case models.GearsPyExecute:
return queryRgPyexecute(qm, client)
case "rg.pydumpreqs":
case models.GearsPyDumpReqs:
return queryRgPydumpReqs(qm, client)

/**
18 changes: 9 additions & 9 deletions pkg/query_test.go
Original file line number Diff line number Diff line change
@@ -21,11 +21,11 @@ func TestQuery(t *testing.T) {
tests := []struct {
qm queryModel
}{
{queryModel{Command: "ts.get"}},
{queryModel{Command: "ts.info"}},
{queryModel{Command: "ts.queryindex"}},
{queryModel{Command: "ts.range"}},
{queryModel{Command: "ts.mrange"}},
{queryModel{Command: models.TimeSeriesGet}},
{queryModel{Command: models.TimeSeriesInfo}},
{queryModel{Command: models.TimeSeriesQueryIndex}},
{queryModel{Command: models.TimeSeriesRange}},
{queryModel{Command: models.TimeSeriesMRange}},
{queryModel{Command: "hgetall"}},
{queryModel{Command: "smembers"}},
{queryModel{Command: "hkeys"}},
@@ -41,10 +41,10 @@ func TestQuery(t *testing.T) {
{queryModel{Command: "ft.info"}},
{queryModel{Command: "xinfoStream"}},
{queryModel{Command: "tmscan"}},
{queryModel{Command: "rg.pystats"}},
{queryModel{Command: "rg.dumpregistrations"}},
{queryModel{Command: "rg.pyexecute"}},
{queryModel{Command: "rg.pydumpreqs"}},
{queryModel{Command: models.GearsPyStats}},
{queryModel{Command: models.GearsDumpRegistrations}},
{queryModel{Command: models.GearsPyExecute}},
{queryModel{Command: models.GearsPyDumpReqs}},
{queryModel{Command: "xrange"}},
{queryModel{Command: "xrevrange"}},
{queryModel{Command: models.GraphConfig}},
71 changes: 13 additions & 58 deletions pkg/redis-gears.go
Original file line number Diff line number Diff line change
@@ -9,54 +9,9 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/redisgrafana/grafana-redis-datasource/pkg/models"
)

/**
* RG.PYSTATS Radix marshaling
*/
type pystats struct {
TotalAllocated int64 `redis:"TotalAllocated"`
PeakAllocated int64 `redis:"PeakAllocated"`
CurrAllocated int64 `redis:"CurrAllocated"`
}

/**
* RG.DUMPREGISTRATIONS Radix marshaling
*/
type dumpregistrations struct {
ID string `redis:"id"`
Reader string `redis:"reader"`
Desc string `redis:"desc"`
RegistrationData registrationData `redis:"RegistrationData"`
PD string `redis:"PD"`
}

/**
* Registration data for RG.DUMPREGISTRATIONS Radix marshaling
*/
type registrationData struct {
Mode string `redis:"mode"`
NumTriggered int64 `redis:"numTriggered"`
NumSuccess int64 `redis:"numSuccess"`
NumFailures int64 `redis:"numFailures"`
NumAborted int64 `redis:"numAborted"`
LastError string `redis:"lastError"`
Args map[string]interface{} `redis:"args"`
Status string `redis:"status"`
}

/**
* RG.PYDUMPREQS Radix marshaling
*/
type pydumpreq struct {
GearReqVersion int64 `redis:"GearReqVersion"`
Name string `redis:"Name"`
IsDownloaded string `redis:"IsDownloaded"`
IsInstalled string `redis:"IsInstalled"`
CompiledOs string `redis:"CompiledOs"`
Wheels []string `redis:"Wheels"`
}

/**
* RG.PYSTATS
*
@@ -67,10 +22,10 @@ func queryRgPystats(qm queryModel, client redisClient) backend.DataResponse {
response := backend.DataResponse{}

// Using radix marshaling of key-value arrays to structs
var stats pystats
var stats models.PyStats

// Run command
err := client.RunCmd(&stats, "RG.PYSTATS")
err := client.RunCmd(&stats, models.GearsPyStats)

// Check error
if err != nil {
@@ -99,10 +54,10 @@ func queryRgDumpregistrations(qm queryModel, client redisClient) backend.DataRes
response := backend.DataResponse{}

// Using radix marshaling of key-value arrays to structs
var models []dumpregistrations
var registrations []models.DumpRegistrations

// Run command
err := client.RunCmd(&models, "RG.DUMPREGISTRATIONS")
err := client.RunCmd(&registrations, models.GearsDumpRegistrations)

// Check error
if err != nil {
@@ -129,16 +84,16 @@ func queryRgDumpregistrations(qm queryModel, client redisClient) backend.DataRes
frame.Fields = append(frame.Fields, data.NewField("status", nil, []string{}))

// Registrations
for _, model := range models {
for _, registration := range registrations {
// Merging args to string like "key"="value"\n
args := new(bytes.Buffer)
for key, value := range model.RegistrationData.Args {
for key, value := range registration.RegistrationData.Args {
fmt.Fprintf(args, "\"%s\"=\"%s\"\n", key, value)
}

frame.AppendRow(model.ID, model.Reader, model.Desc, model.PD, model.RegistrationData.Mode,
model.RegistrationData.NumTriggered, model.RegistrationData.NumSuccess, model.RegistrationData.NumFailures,
model.RegistrationData.NumAborted, model.RegistrationData.LastError, args.String(), model.RegistrationData.Status)
frame.AppendRow(registration.ID, registration.Reader, registration.Desc, registration.PD, registration.RegistrationData.Mode,
registration.RegistrationData.NumTriggered, registration.RegistrationData.NumSuccess, registration.RegistrationData.NumFailures,
registration.RegistrationData.NumAborted, registration.RegistrationData.LastError, args.String(), registration.RegistrationData.Status)
}

return response
@@ -166,7 +121,7 @@ func queryRgPyexecute(qm queryModel, client redisClient) backend.DataResponse {
}

// Run command
err := client.RunFlatCmd(&result, "RG.PYEXECUTE", qm.Key, args...)
err := client.RunFlatCmd(&result, models.GearsPyExecute, qm.Key, args...)

// Check error
if err != nil {
@@ -227,10 +182,10 @@ func queryRgPydumpReqs(qm queryModel, client redisClient) backend.DataResponse {
response := backend.DataResponse{}

// Using radix marshaling of key-value arrays to structs
var reqs []pydumpreq
var reqs []models.PyDumpReq

// Run command
err := client.RunCmd(&reqs, "RG.PYDUMPREQS")
err := client.RunCmd(&reqs, models.GearsPyDumpReqs)

// Check error
if err != nil {
15 changes: 8 additions & 7 deletions pkg/redis-gears_integration_test.go
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import (
"time"

"github.com/mediocregopher/radix/v3"
"github.com/redisgrafana/grafana-redis-datasource/pkg/models"
"github.com/stretchr/testify/require"
)

@@ -20,7 +21,7 @@ func TestRgPystatsIntegration(t *testing.T) {
client := radixV3Impl{radixClient: radixClient}

// Response
resp := queryRgPystats(queryModel{Command: "rg.pystats"}, &client)
resp := queryRgPystats(queryModel{Command: models.GearsPyStats}, &client)
require.Len(t, resp.Frames, 1)
require.Len(t, resp.Frames[0].Fields, 3)
require.IsType(t, int64(0), resp.Frames[0].Fields[0].At(0))
@@ -40,7 +41,7 @@ func TestRgDumpregistrationsIntegration(t *testing.T) {
client := radixV3Impl{radixClient: radixClient}

// Response
resp := queryRgDumpregistrations(queryModel{Command: "rg.dumpregistrations"}, &client)
resp := queryRgDumpregistrations(queryModel{Command: models.GearsDumpRegistrations}, &client)
require.Len(t, resp.Frames[0].Fields, 12)
require.Equal(t, "id", resp.Frames[0].Fields[0].Name)
require.Equal(t, "reader", resp.Frames[0].Fields[1].Name)
@@ -74,7 +75,7 @@ func TestRgPyexecuteIntegration(t *testing.T) {

// Results
t.Run("Test command with full response", func(t *testing.T) {
resp := queryRgPyexecute(queryModel{Command: "rg.pyexecute", Key: "GB().run()"}, &client)
resp := queryRgPyexecute(queryModel{Command: models.GearsPyExecute, Key: "GB().run()"}, &client)
require.Len(t, resp.Frames, 2)
require.Len(t, resp.Frames[0].Fields, 1)
require.Equal(t, "results", resp.Frames[0].Name)
@@ -89,7 +90,7 @@ func TestRgPyexecuteIntegration(t *testing.T) {

// UNBLOCKING and REQUIREMENTS
t.Run("Test command with UNBLOCKING and REQUIREMENTS", func(t *testing.T) {
resp := queryRgPyexecute(queryModel{Command: "rg.pyexecute", Key: "GearsBuilder(reader=\"KeysReader\").run()", Unblocking: true, Requirements: "numpy"}, &client)
resp := queryRgPyexecute(queryModel{Command: models.GearsPyExecute, Key: "GearsBuilder(reader=\"KeysReader\").run()", Unblocking: true, Requirements: "numpy"}, &client)
require.Len(t, resp.Frames, 1)
require.Len(t, resp.Frames[0].Fields, 1)
require.Equal(t, "operationId", resp.Frames[0].Name)
@@ -100,7 +101,7 @@ func TestRgPyexecuteIntegration(t *testing.T) {

// OK
t.Run("Test command with full OK string", func(t *testing.T) {
resp := queryRgPyexecute(queryModel{Command: "rg.pyexecute", Key: "GB('CommandReader')"}, &client)
resp := queryRgPyexecute(queryModel{Command: models.GearsPyExecute, Key: "GB('CommandReader')"}, &client)
require.Len(t, resp.Frames, 2)
require.Len(t, resp.Frames[0].Fields, 1)
require.Equal(t, "results", resp.Frames[0].Name)
@@ -115,7 +116,7 @@ func TestRgPyexecuteIntegration(t *testing.T) {

// Error
t.Run("Test command with error", func(t *testing.T) {
resp := queryRgPyexecute(queryModel{Command: "rg.pyexecute", Key: "some key"}, &client)
resp := queryRgPyexecute(queryModel{Command: models.GearsPyExecute, Key: "some key"}, &client)
require.Len(t, resp.Frames, 0)
require.Error(t, resp.Error)
})
@@ -130,7 +131,7 @@ func TestRgDumpReqsIntegration(t *testing.T) {
client := radixV3Impl{radixClient: radixClient}

// Response
resp := queryRgPydumpReqs(queryModel{Command: "rg.pydumpreqs"}, &client)
resp := queryRgPydumpReqs(queryModel{Command: models.GearsPyDumpReqs}, &client)

require.Len(t, resp.Frames[0].Fields, 6)
require.Equal(t, "GearReqVersion", resp.Frames[0].Fields[0].Name)
50 changes: 36 additions & 14 deletions pkg/redis-gears_test.go
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import (
"errors"
"testing"

"github.com/redisgrafana/grafana-redis-datasource/pkg/models"
"github.com/stretchr/testify/require"
)

@@ -21,7 +22,7 @@ func TestRgPystats(t *testing.T) {

// Client
client := testClient{
rcv: pystats{
rcv: models.PyStats{
TotalAllocated: int64(11),
PeakAllocated: int64(12),
CurrAllocated: int64(13),
@@ -30,7 +31,7 @@ func TestRgPystats(t *testing.T) {
}

// Response
resp := queryRgPystats(queryModel{Command: "rg.pystats"}, &client)
resp := queryRgPystats(queryModel{Command: models.GearsPyStats}, &client)
require.Len(t, resp.Frames, 1)
require.Len(t, resp.Frames[0].Fields, 3)
require.Equal(t, int64(11), resp.Frames[0].Fields[0].At(0))
@@ -55,7 +56,7 @@ func TestRgPystats(t *testing.T) {
err: errors.New("error occurred")}

// Response
resp := queryRgPystats(queryModel{Command: "rg.pystats"}, &client)
resp := queryRgPystats(queryModel{Command: models.GearsPyStats}, &client)
require.EqualError(t, resp.Error, "error occurred")
})
}
@@ -74,11 +75,11 @@ func TestRgDumpregistrations(t *testing.T) {

// Client
client := testClient{
rcv: []dumpregistrations{{
rcv: []models.DumpRegistrations{{
ID: "123",
Reader: "reader",
Desc: "desc",
RegistrationData: registrationData{
RegistrationData: models.RegistrationData{
Mode: "async",
NumTriggered: 1,
NumSuccess: 2,
@@ -93,7 +94,7 @@ func TestRgDumpregistrations(t *testing.T) {
}

// Response
resp := queryRgDumpregistrations(queryModel{Command: "rg.dumpregistrations"}, &client)
resp := queryRgDumpregistrations(queryModel{Command: models.GearsDumpRegistrations}, &client)
require.Len(t, resp.Frames, 1)
require.Len(t, resp.Frames[0].Fields, 12)
require.Equal(t, "id", resp.Frames[0].Fields[0].Name)
@@ -133,7 +134,7 @@ func TestRgDumpregistrations(t *testing.T) {
err: errors.New("error occurred")}

// Response
resp := queryRgDumpregistrations(queryModel{Command: "rg.dumpregistrations"}, &client)
resp := queryRgDumpregistrations(queryModel{Command: models.GearsDumpRegistrations}, &client)
require.EqualError(t, resp.Error, "error occurred")
})
}
@@ -157,7 +158,7 @@ func TestRgPyexecute(t *testing.T) {
}

// Response
resp := queryRgPyexecute(queryModel{Command: "rg.pyexecute", Key: "GB().run()"}, &client)
resp := queryRgPyexecute(queryModel{Command: models.GearsPyExecute, Key: "GB().run()"}, &client)
require.Len(t, resp.Frames, 2)
require.Len(t, resp.Frames[0].Fields, 1)
require.Equal(t, "results", resp.Frames[0].Name)
@@ -183,7 +184,7 @@ func TestRgPyexecute(t *testing.T) {
}

// Response
resp := queryRgPyexecute(queryModel{Command: "rg.pyexecute", Key: "GB().run()", Unblocking: true, Requirements: "numpy"}, &client)
resp := queryRgPyexecute(queryModel{Command: models.GearsPyExecute, Key: "GB().run()", Unblocking: true, Requirements: "numpy"}, &client)
require.Len(t, resp.Frames, 1)
require.Len(t, resp.Frames[0].Fields, 1)
require.Equal(t, "operationId", resp.Frames[0].Name)
@@ -213,7 +214,7 @@ func TestRgPyexecute(t *testing.T) {
}

// Response
resp := queryRgPyexecute(queryModel{Command: "rg.pyexecute", Key: "GB().run()"}, &client)
resp := queryRgPyexecute(queryModel{Command: models.GearsPyExecute, Key: "GB().run()"}, &client)
require.Len(t, resp.Frames, 2)
require.Len(t, resp.Frames[0].Fields, 1)
require.Equal(t, "results", resp.Frames[0].Name)
@@ -229,6 +230,27 @@ func TestRgPyexecute(t *testing.T) {
require.NoError(t, resp.Error)
})

/**
* Unexpected Type Error
*/
t.Run("should handle unexpected type error", func(t *testing.T) {
t.Parallel()

// Client
client := testClient{
rcv: 0,
batchRcv: nil,
err: nil,
}

// Response
resp := queryRgPyexecute(queryModel{Command: models.GearsPyExecute, Key: "GB().run()"}, &client)
require.Len(t, resp.Frames, 2)
require.Len(t, resp.Frames[0].Fields, 1)
require.Equal(t, 0, resp.Frames[0].Fields[0].Len())
require.Equal(t, 0, resp.Frames[1].Fields[0].Len())
})

/**
* Error
*/
@@ -242,7 +264,7 @@ func TestRgPyexecute(t *testing.T) {
err: errors.New("error occurred")}

// Response
resp := queryRgPyexecute(queryModel{Command: "rg.pyexecute", Key: "GB().run()"}, &client)
resp := queryRgPyexecute(queryModel{Command: models.GearsPyExecute, Key: "GB().run()"}, &client)
require.EqualError(t, resp.Error, "error occurred")
})
}
@@ -261,7 +283,7 @@ func TestRgPyDumpReqs(t *testing.T) {

// Client
client := testClient{
rcv: []pydumpreq{{
rcv: []models.PyDumpReq{{
GearReqVersion: 1,
Name: "pandas",
IsDownloaded: "yes",
@@ -273,7 +295,7 @@ func TestRgPyDumpReqs(t *testing.T) {
}

// Response
resp := queryRgPydumpReqs(queryModel{Command: "rg.pydumpreqs"}, &client)
resp := queryRgPydumpReqs(queryModel{Command: models.GearsPyDumpReqs}, &client)
require.Len(t, resp.Frames, 1)
require.Len(t, resp.Frames[0].Fields, 6)
require.Equal(t, int64(1), resp.Frames[0].Fields[0].At(0))
@@ -301,7 +323,7 @@ func TestRgPyDumpReqs(t *testing.T) {
err: errors.New("error occurred")}

// Response
resp := queryRgPydumpReqs(queryModel{Command: "rg.pydumpreqs"}, &client)
resp := queryRgPydumpReqs(queryModel{Command: models.GearsPyDumpReqs}, &client)
require.EqualError(t, resp.Error, "error occurred")
})
}
3 changes: 2 additions & 1 deletion pkg/redis-time-series_integration_test.go
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import (
"testing"

"github.com/mediocregopher/radix/v3"
"github.com/redisgrafana/grafana-redis-datasource/pkg/models"
"github.com/stretchr/testify/require"
)

@@ -19,7 +20,7 @@ func TestTSInfoIntegration(t *testing.T) {
client := radixV3Impl{radixClient: radixClient}

// Response
resp := queryTsInfo(queryModel{Command: "ts.info", Key: "test:timeseries2"}, &client)
resp := queryTsInfo(queryModel{Command: models.TimeSeriesInfo, Key: "test:timeseries2"}, &client)
require.Len(t, resp.Frames, 1)
require.Len(t, resp.Frames[0].Fields, 12)
}
43 changes: 22 additions & 21 deletions pkg/redis-time-series_test.go
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import (
"testing"
"time"

"github.com/redisgrafana/grafana-redis-datasource/pkg/models"
"github.com/stretchr/testify/require"
)

@@ -23,7 +24,7 @@ func TestQueryTsRange(t *testing.T) {
}{
{
"should process receiver without aggregation and legend provided",
queryModel{Command: "ts.range", Key: "test1"},
queryModel{Command: models.TimeSeriesRange, Key: "test1"},
[][]string{
{"1548149180000", "26.199999999999999"},
{"1548149195000", "27.399999999999999"},
@@ -45,7 +46,7 @@ func TestQueryTsRange(t *testing.T) {
},
{
"should process receiver with aggregation and legend",
queryModel{Command: "ts.range", Aggregation: "avg", Bucket: 5000, Key: "test1", Legend: "Legend"},
queryModel{Command: models.TimeSeriesRange, Aggregation: "avg", Bucket: 5000, Key: "test1", Legend: "Legend"},
[][]string{
{"1548149180000", "26.199999999999999"},
{"1548149185000", "27.399999999999999"},
@@ -67,7 +68,7 @@ func TestQueryTsRange(t *testing.T) {
},
{
"should process receiver with fill",
queryModel{Command: "ts.range", Bucket: 5000, Key: "test1", Fill: true},
queryModel{Command: models.TimeSeriesRange, Bucket: 5000, Key: "test1", Fill: true},
[][]string{
{"1548149180000", "26.199999999999999"},
{"1548149195000", "27.399999999999999"},
@@ -89,7 +90,7 @@ func TestQueryTsRange(t *testing.T) {
},
{
"should process receiver error",
queryModel{Command: "ts.range", Key: "test1"},
queryModel{Command: models.TimeSeriesRange, Key: "test1"},
nil,
0,
0,
@@ -146,7 +147,7 @@ func TestQueryTsMRange(t *testing.T) {
}{
{
"should process receiver without aggregation and legend provided but with labels",
queryModel{Command: "ts.mrange", Key: "test1", Filter: "area_id=32 sensor_id!=1"},
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Filter: "area_id=32 sensor_id!=1"},
[]interface{}{
[]interface{}{
[]byte("temperature:2:32"),
@@ -190,7 +191,7 @@ func TestQueryTsMRange(t *testing.T) {
},
{
"should process receiver with aggregation and legend",
queryModel{Command: "ts.mrange", Key: "test1", Aggregation: "avg", Legend: "Legend", Bucket: 5000, Filter: "area_id=32 sensor_id!=1"},
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Aggregation: "avg", Legend: "Legend", Bucket: 5000, Filter: "area_id=32 sensor_id!=1"},
[]interface{}{
[]interface{}{
[]byte("temperature:2:32"),
@@ -221,7 +222,7 @@ func TestQueryTsMRange(t *testing.T) {
},
{
"should process receiver with labels specified in legend",
queryModel{Command: "ts.mrange", Key: "test1", Legend: "area_id", Filter: "area_id=32 sensor_id!=1"},
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Legend: "area_id", Filter: "area_id=32 sensor_id!=1"},
[]interface{}{
[]interface{}{
[]byte("temperature:2:32"),
@@ -243,7 +244,7 @@ func TestQueryTsMRange(t *testing.T) {
},
{
"should process receiver with value field existed in labels",
queryModel{Command: "ts.mrange", Key: "test1", Value: "sensor_id", Filter: "area_id=32 sensor_id!=1"},
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Value: "sensor_id", Filter: "area_id=32 sensor_id!=1"},
[]interface{}{
[]interface{}{
[]byte("temperature:2:32"),
@@ -266,7 +267,7 @@ func TestQueryTsMRange(t *testing.T) {

{
"should process receiver with []byte field instead of int",
queryModel{Command: "ts.mrange", Key: "test1", Legend: "area_id", Filter: "area_id=32 sensor_id!=1"},
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Legend: "area_id", Filter: "area_id=32 sensor_id!=1"},
[]interface{}{
[]interface{}{
[]byte("temperature:2:32"),
@@ -291,7 +292,7 @@ func TestQueryTsMRange(t *testing.T) {
},
{
"should process receiver with string field instead of int",
queryModel{Command: "ts.mrange", Key: "test1", Legend: "area_id", Filter: "area_id=32 sensor_id!=1"},
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Legend: "area_id", Filter: "area_id=32 sensor_id!=1"},
[]interface{}{
[]interface{}{
[]byte("temperature:2:32"),
@@ -316,7 +317,7 @@ func TestQueryTsMRange(t *testing.T) {
},
{
"should process receiver with Fill",
queryModel{Command: "ts.mrange", Key: "test1", Fill: true, Bucket: 5000, Filter: "area_id=32 sensor_id!=1"},
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Fill: true, Bucket: 5000, Filter: "area_id=32 sensor_id!=1"},
[]interface{}{
[]interface{}{
[]byte("temperature:2:32"),
@@ -347,7 +348,7 @@ func TestQueryTsMRange(t *testing.T) {
},
{
"should return error if result is string",
queryModel{Command: "ts.mrange", Key: "test1", Filter: "filter"},
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Filter: "filter"},
interface{}("someString"),
0,
0,
@@ -361,7 +362,7 @@ func TestQueryTsMRange(t *testing.T) {
},
{
"should return error on bad filter",
queryModel{Command: "ts.mrange", Key: "test1", Filter: "\""},
queryModel{Command: models.TimeSeriesMRange, Key: "test1", Filter: "\""},
nil,
0,
0,
@@ -375,7 +376,7 @@ func TestQueryTsMRange(t *testing.T) {
},
{
"should process receiver error",
queryModel{Command: "ts.mrange", Key: "test1"},
queryModel{Command: models.TimeSeriesMRange, Key: "test1"},
nil,
0,
0,
@@ -427,7 +428,7 @@ func TestQueryTsGet(t *testing.T) {
}{
{
"should process receiver without aggregation and legend provided",
queryModel{Command: "ts.get", Key: "test1"},
queryModel{Command: models.TimeSeriesGet, Key: "test1"},
[]string{"1548149180000", "26.199999999999999"},
2,
1,
@@ -439,7 +440,7 @@ func TestQueryTsGet(t *testing.T) {
},
{
"should process receiver error",
queryModel{Command: "ts.range", Key: "test1"},
queryModel{Command: models.TimeSeriesRange, Key: "test1"},
nil,

0,
@@ -486,7 +487,7 @@ func TestQueryTsInfo(t *testing.T) {
}{
{
"should process receiver",
queryModel{Command: "ts.queryindex", Filter: "test1"},
queryModel{Command: models.TimeSeriesQueryIndex, Filter: "test1"},
map[string]interface{}{
"totalSamples": int64(100),
"memoryUsage": int64(4184),
@@ -513,7 +514,7 @@ func TestQueryTsInfo(t *testing.T) {
},
{
"should process receiver error",
queryModel{Command: "ts.info", Filter: "test1"},
queryModel{Command: models.TimeSeriesInfo, Filter: "test1"},
nil,

0,
@@ -564,7 +565,7 @@ func TestQueryTsQueryIndex(t *testing.T) {
}{
{
"should process receiver without aggregation and legend provided",
queryModel{Command: "ts.queryindex", Filter: "sensor_id=2"},
queryModel{Command: models.TimeSeriesQueryIndex, Filter: "sensor_id=2"},
[]string{"temperature:2:32", "temperature:2:33"},
1,
2,
@@ -576,7 +577,7 @@ func TestQueryTsQueryIndex(t *testing.T) {
},
{
"should process error on bad filter",
queryModel{Command: "ts.queryindex", Filter: "\""},
queryModel{Command: models.TimeSeriesQueryIndex, Filter: "\""},
nil,

0,
@@ -586,7 +587,7 @@ func TestQueryTsQueryIndex(t *testing.T) {
},
{
"should process receiver error",
queryModel{Command: "ts.range", Filter: "test1"},
queryModel{Command: models.TimeSeriesRange, Filter: "test1"},
nil,

0,
13 changes: 7 additions & 6 deletions pkg/testing-utilities_test.go
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import (

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/redisgrafana/grafana-redis-datasource/pkg/models"
"github.com/stretchr/testify/mock"
)

@@ -117,14 +118,14 @@ func assignReceiver(to interface{}, from interface{}) {
*(to.(*map[string]interface{})) = from.(map[string]interface{})
case *string:
*(to.(*string)) = from.(string)
case *pystats:
*(to.(*pystats)) = from.(pystats)
case *models.PyStats:
*(to.(*models.PyStats)) = from.(models.PyStats)
case *xinfo:
*(to.(*xinfo)) = from.(xinfo)
case *[]dumpregistrations:
*(to.(*[]dumpregistrations)) = from.([]dumpregistrations)
case *[]pydumpreq:
*(to.(*[]pydumpreq)) = from.([]pydumpreq)
case *[]models.DumpRegistrations:
*(to.(*[]models.DumpRegistrations)) = from.([]models.DumpRegistrations)
case *[]models.PyDumpReq:
*(to.(*[]models.PyDumpReq)) = from.([]models.PyDumpReq)
case interface{}:
switch from.(type) {
case int:
35 changes: 20 additions & 15 deletions src/components/query-editor/query-editor.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { shallow, ShallowWrapper } from 'enzyme';
import React from 'react';
import { RedisGraph } from 'redis/graph';
import { AggregationValue, QueryTypeValue, RedisQuery } from '../../redis';
import { AggregationValue, QueryTypeValue, RedisQuery, RedisTimeSeries } from '../../redis';
import { getQuery } from '../../tests/utils';
import { QueryEditor } from './query-editor';

@@ -175,8 +175,8 @@ describe('QueryEditor', () => {
return node.name() === 'FormField' && node.prop('label') === 'Label Filter';
}),
type: 'string',
queryWhenShown: { refId: '', type: QueryTypeValue.REDIS, command: 'ts.mrange' },
queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: 'ts.mrange123' },
queryWhenShown: { refId: '', type: QueryTypeValue.REDIS, command: RedisTimeSeries.MRANGE },
queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: 'info' },
},
{
name: 'field',
@@ -196,8 +196,8 @@ describe('QueryEditor', () => {
return node.name() === 'FormField' && node.prop('label') === 'Legend';
}),
type: 'string',
queryWhenShown: { refId: '', type: QueryTypeValue.REDIS, command: 'ts.range' },
queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: 'ts.range123' },
queryWhenShown: { refId: '', type: QueryTypeValue.REDIS, command: RedisTimeSeries.RANGE },
queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: 'info' },
},
{
name: 'legend',
@@ -207,8 +207,8 @@ describe('QueryEditor', () => {
return node.name() === 'FormField' && node.prop('label') === 'Legend Label';
}),
type: 'string',
queryWhenShown: { refId: '', type: QueryTypeValue.REDIS, command: 'ts.mrange' },
queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: 'ts.mrange123' },
queryWhenShown: { refId: '', type: QueryTypeValue.REDIS, command: RedisTimeSeries.MRANGE },
queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: 'info' },
},
{
name: 'value',
@@ -217,8 +217,8 @@ describe('QueryEditor', () => {
return node.name() === 'FormField' && node.prop('label') === 'Value Label';
}),
type: 'string',
queryWhenShown: { refId: '', type: QueryTypeValue.REDIS, command: 'ts.mrange' },
queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: 'ts.mrange123' },
queryWhenShown: { refId: '', type: QueryTypeValue.REDIS, command: RedisTimeSeries.MRANGE },
queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: 'info' },
},
{
name: 'cypher',
@@ -317,8 +317,8 @@ describe('QueryEditor', () => {
return node.prop('onChange') === wrapper.instance().onAggregationChange;
}),
type: 'select',
queryWhenShown: { refId: '', type: QueryTypeValue.TIMESERIES, command: 'ts.range' },
queryWhenHidden: { refId: '', type: QueryTypeValue.TIMESERIES, command: 'ts.range123' },
queryWhenShown: { refId: '', type: QueryTypeValue.TIMESERIES, command: RedisTimeSeries.RANGE },
queryWhenHidden: { refId: '', type: QueryTypeValue.TIMESERIES, command: 'info' },
},
{
name: 'bucket',
@@ -330,10 +330,15 @@ describe('QueryEditor', () => {
queryWhenShown: {
refId: '',
type: QueryTypeValue.TIMESERIES,
command: 'ts.range',
command: RedisTimeSeries.RANGE,
aggregation: AggregationValue.AVG,
},
queryWhenHidden: { refId: '', type: QueryTypeValue.TIMESERIES, command: 'ts.range', aggregation: undefined },
queryWhenHidden: {
refId: '',
type: QueryTypeValue.TIMESERIES,
command: RedisTimeSeries.RANGE,
aggregation: undefined,
},
},
{
name: 'fill',
@@ -345,15 +350,15 @@ describe('QueryEditor', () => {
queryWhenShown: {
refId: '',
type: QueryTypeValue.TIMESERIES,
command: 'ts.range',
command: RedisTimeSeries.RANGE,
aggregation: AggregationValue.AVG,
bucket: 123,
fill: false,
},
queryWhenHidden: {
refId: '',
type: QueryTypeValue.TIMESERIES,
command: 'ts.range',
command: RedisTimeSeries.RANGE,
aggregation: AggregationValue.AVG,
bucket: 0,
},
79 changes: 41 additions & 38 deletions src/components/query-editor/query-editor.tsx
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ import {
QueryType,
QueryTypeValue,
RedisQuery,
RedisTimeSeries,
} from '../../redis';
import { RedisDataSourceOptions } from '../../types';

@@ -298,7 +299,7 @@ export class QueryEditor extends PureComponent<Props> {
/>
)}

{CommandParameters.filter.includes(command) && (
{CommandParameters.filter.includes(command as RedisTimeSeries) && (
<FormField
labelWidth={8}
inputWidth={30}
@@ -315,7 +316,7 @@ export class QueryEditor extends PureComponent<Props> {
<FormField labelWidth={8} inputWidth={30} value={field} onChange={this.onFieldChange} label="Field" />
)}

{CommandParameters.legend.includes(command) && (
{CommandParameters.legend.includes(command as RedisTimeSeries) && (
<FormField
labelWidth={8}
inputWidth={20}
@@ -326,7 +327,7 @@ export class QueryEditor extends PureComponent<Props> {
/>
)}

{CommandParameters.value.includes(command) && (
{CommandParameters.value.includes(command as RedisTimeSeries) && (
<FormField
labelWidth={8}
inputWidth={10}
@@ -337,7 +338,7 @@ export class QueryEditor extends PureComponent<Props> {
/>
)}

{CommandParameters.legendLabel.includes(command) && (
{CommandParameters.legendLabel.includes(command as RedisTimeSeries) && (
<FormField
labelWidth={8}
inputWidth={10}
@@ -347,7 +348,7 @@ export class QueryEditor extends PureComponent<Props> {
/>
)}

{CommandParameters.valueLabel.includes(command) && (
{CommandParameters.valueLabel.includes(command as RedisTimeSeries) && (
<FormField
labelWidth={8}
inputWidth={10}
@@ -456,40 +457,42 @@ export class QueryEditor extends PureComponent<Props> {
</div>
)}

{type === QueryTypeValue.TIMESERIES && command && CommandParameters.aggregation.includes(command) && (
<div className="gf-form">
<InlineFormLabel width={8}>Aggregation</InlineFormLabel>
<Select
className={css`
margin-right: 5px;
`}
options={Aggregations}
width={30}
onChange={this.onAggregationChange}
value={aggregation}
menuPlacement="bottom"
/>
{aggregation && (
<FormField
labelWidth={8}
value={bucket}
type="number"
onChange={this.onBucketChange}
label="Time Bucket"
tooltip="Time bucket for aggregation in milliseconds"
/>
)}
{aggregation && bucket && CommandParameters.fill.includes(command) && (
<Switch
label="Fill Missing"
labelClass="width-10"
tooltip="If checked, the datasource will fill missing intervals."
checked={fill || false}
onChange={this.onFillChange}
{type === QueryTypeValue.TIMESERIES &&
command &&
CommandParameters.aggregation.includes(command as RedisTimeSeries) && (
<div className="gf-form">
<InlineFormLabel width={8}>Aggregation</InlineFormLabel>
<Select
className={css`
margin-right: 5px;
`}
options={Aggregations}
width={30}
onChange={this.onAggregationChange}
value={aggregation}
menuPlacement="bottom"
/>
)}
</div>
)}
{aggregation && (
<FormField
labelWidth={8}
value={bucket}
type="number"
onChange={this.onBucketChange}
label="Time Bucket"
tooltip="Time bucket for aggregation in milliseconds"
/>
)}
{aggregation && bucket && CommandParameters.fill.includes(command as RedisTimeSeries) && (
<Switch
label="Fill Missing"
labelClass="width-10"
tooltip="If checked, the datasource will fill missing intervals."
checked={fill || false}
onChange={this.onFillChange}
/>
)}
</div>
)}

{refId === 'A' && (
<>
64 changes: 14 additions & 50 deletions src/redis/command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { RedisGearsCommands } from './gears';
import { RedisGraph, RedisGraphCommands } from './graph';
import { QueryTypeValue } from './query';
import { RedisTimeSeries, RedisTimeSeriesCommands } from './time-series';

/**
* Commands
@@ -84,63 +86,25 @@ export const Commands = {
value: 'xrevrange',
},
],
[QueryTypeValue.TIMESERIES]: [
{
label: 'TS.GET',
description: 'Returns the last sample',
value: 'ts.get',
},
{
label: 'TS.INFO',
description: 'Returns information and statistics on the time-series',
value: 'ts.info',
},
{
label: 'TS.MRANGE',
description: 'Query a timestamp range across multiple time-series by filters',
value: 'ts.mrange',
},
{
label: 'TS.QUERYINDEX',
description: 'Query all the keys matching the filter list',
value: 'ts.queryindex',
},
{ label: 'TS.RANGE', description: 'Query a range', value: 'ts.range' },
],
[QueryTypeValue.TIMESERIES]: RedisTimeSeriesCommands,
[QueryTypeValue.SEARCH]: [
{
label: 'FT.INFO',
description: 'Returns information and statistics on the index',
value: 'ft.info',
},
],
[QueryTypeValue.GEARS]: [
{
label: 'RG.DUMPREGISTRATIONS',
description: 'Outputs the list of function registrations',
value: 'rg.dumpregistrations',
},
{
label: 'RG.PYSTATS',
description: 'Returns memory usage statistics from the Python interpreter',
value: 'rg.pystats',
},
{
label: 'RG.PYDUMPREQS',
description: 'Returns a list of all the python requirements available',
value: 'rg.pydumpreqs',
},
],
[QueryTypeValue.GEARS]: RedisGearsCommands,
[QueryTypeValue.GRAPH]: RedisGraphCommands,
};

/**
* Input for Commands
*/
export const CommandParameters = {
aggregation: ['ts.range', 'ts.mrange'],
aggregation: [RedisTimeSeries.RANGE, RedisTimeSeries.MRANGE],
field: ['hget', 'hmget'],
filter: ['ts.mrange', 'ts.queryindex'],
filter: [RedisTimeSeries.MRANGE, RedisTimeSeries.QUERYINDEX],
keyName: [
'get',
'hget',
@@ -151,9 +115,9 @@ export const CommandParameters = {
'llen',
'scard',
'smembers',
'ts.range',
'ts.get',
'ts.info',
RedisTimeSeries.RANGE,
RedisTimeSeries.GET,
RedisTimeSeries.INFO,
'ttl',
'type',
'xinfoStream',
@@ -166,12 +130,12 @@ export const CommandParameters = {
RedisGraph.EXPLAIN,
RedisGraph.PROFILE,
],
legend: ['ts.range'],
legendLabel: ['ts.mrange'],
legend: [RedisTimeSeries.RANGE],
legendLabel: [RedisTimeSeries.MRANGE],
section: ['info'],
value: ['ts.range'],
valueLabel: ['ts.mrange'],
fill: ['ts.range', 'ts.mrange'],
value: [RedisTimeSeries.RANGE],
valueLabel: [RedisTimeSeries.MRANGE],
fill: [RedisTimeSeries.RANGE, RedisTimeSeries.MRANGE],
size: ['slowlogGet', 'tmscan'],
cursor: ['tmscan'],
match: ['tmscan'],
30 changes: 30 additions & 0 deletions src/redis/gears.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Supported Commands
*/
export enum RedisGears {
DUMPREGISTRATIONS = 'rg.dumpregistrations',
PYSTATS = 'rg.pystats',
PYDUMPREQS = 'rg.pydumpreqs',
PYEXECUTE = 'rg.pyexecute',
}

/**
* Commands List
*/
export const RedisGearsCommands = [
{
label: RedisGears.DUMPREGISTRATIONS.toUpperCase(),
description: 'Outputs the list of function registrations',
value: RedisGears.DUMPREGISTRATIONS,
},
{
label: RedisGears.PYSTATS.toUpperCase(),
description: 'Returns memory usage statistics from the Python interpreter',
value: RedisGears.PYSTATS,
},
{
label: RedisGears.PYDUMPREQS.toUpperCase(),
description: 'Returns a list of all the python requirements available',
value: RedisGears.PYDUMPREQS,
},
];
2 changes: 2 additions & 0 deletions src/redis/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from './command';
export * from './gears';
export * from './graph';
export * from './info';
export * from './query';
export * from './time-series';
38 changes: 38 additions & 0 deletions src/redis/time-series.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
import { SelectableValue } from '@grafana/data';

/**
* Supported Commands
*/
export enum RedisTimeSeries {
GET = 'ts.get',
INFO = 'ts.info',
MRANGE = 'ts.mrange',
QUERYINDEX = 'ts.queryindex',
RANGE = 'ts.range',
}

/**
* Commands List
*/
export const RedisTimeSeriesCommands = [
{
label: RedisTimeSeries.GET.toUpperCase(),
description: 'Returns the last sample',
value: RedisTimeSeries.GET,
},
{
label: RedisTimeSeries.INFO.toUpperCase(),
description: 'Returns information and statistics on the time-series',
value: RedisTimeSeries.INFO,
},
{
label: RedisTimeSeries.MRANGE.toUpperCase(),
description: 'Query a timestamp range across multiple time-series by filters',
value: RedisTimeSeries.MRANGE,
},
{
label: RedisTimeSeries.QUERYINDEX.toUpperCase(),
description: 'Query all the keys matching the filter list',
value: RedisTimeSeries.QUERYINDEX,
},
{ label: RedisTimeSeries.RANGE.toUpperCase(), description: 'Query a range', value: RedisTimeSeries.RANGE },
];

/**
* Aggregation Values
*/

0 comments on commit 1a900bb

Please sign in to comment.