diff --git a/.prettierignore b/.prettierignore index 31a4ae9..13144ae 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,9 +1,11 @@ .DS_Store +.dockerignore .gitkeep .gitattributes .gitignore .editorconfig .prettierignore +Dockerfile LICENSE yarn.lock go.mod @@ -12,6 +14,7 @@ go.sum .github # Folders +docker-compose node_modules coverage data diff --git a/docker-compose/cluster.yml b/docker-compose/cluster.yml index 9531abd..1fe0db9 100644 --- a/docker-compose/cluster.yml +++ b/docker-compose/cluster.yml @@ -1,4 +1,4 @@ -version: "3.7" +version: '3.7' networks: cluster-network: @@ -46,7 +46,7 @@ services: container_name: redis3 build: context: cluster - entrypoint: ["/usr/local/bin/startup_cluster.sh"] + entrypoint: ['/usr/local/bin/startup_cluster.sh'] ports: - '6381:6379' - '16379' diff --git a/pkg/redis-time-series.go b/pkg/redis-time-series.go index 5d95b27..7339d9d 100644 --- a/pkg/redis-time-series.go +++ b/pkg/redis-time-series.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "strconv" "time" @@ -97,13 +98,25 @@ func queryTsMRange(from int64, to int64, qm queryModel, client redisClient) back return response } + var args []interface{} + //args := []interface{}{strconv.FormatInt(from, 10), to} + // Execute command if qm.Aggregation != "" { - err = client.RunFlatCmd(&result, qm.Command, strconv.FormatInt(from, 10), to, "AGGREGATION", qm.Aggregation, qm.Bucket, "WITHLABELS", "FILTER", filter) + args = []interface{}{to, "AGGREGATION", qm.Aggregation, qm.Bucket, "WITHLABELS", "FILTER", filter} } else { - err = client.RunFlatCmd(&result, qm.Command, strconv.FormatInt(from, 10), to, "WITHLABELS", "FILTER", filter) + args = []interface{}{to, qm.Bucket, "WITHLABELS", "FILTER", filter} } + if qm.TsGroupByLabel != "" { + if qm.TsReducer == "" { + return errorHandler(response, errors.New("reducer not provided for groups, please provide a reducer (e.g. avg, sum) and try again")) + } + args = append(args, "GROUPBY", qm.TsGroupByLabel, "REDUCE", qm.TsReducer) + } + + err = client.RunFlatCmd(&result, qm.Command, strconv.FormatInt(from, 10), args...) + // Check error if err != nil { return errorHandler(response, err) diff --git a/pkg/redis-time-series_test.go b/pkg/redis-time-series_test.go index 50b65db..15c00a9 100644 --- a/pkg/redis-time-series_test.go +++ b/pkg/redis-time-series_test.go @@ -346,6 +346,60 @@ func TestQueryTsMRange(t *testing.T) { "", nil, }, + { + "test groupby/reduction", + queryModel{Command: models.TimeSeriesMRange, TsReducer: "SUM", TsGroupByLabel: "reduceLabel"}, + []interface{}{ + []interface{}{ + []byte("foo=bar"), + []interface{}{ + []interface{}{ + []byte("foo"), + []byte("bar"), + }, + []interface{}{ + []byte("__reducer__"), + []byte("sum"), + }, + []interface{}{ + []byte("__source__"), + []byte("ts:1,ts:2,ts:3"), + }, + }, + []interface{}{ + []interface{}{int64(1686835010300), []byte("2102")}, + []interface{}{int64(1686835011312), []byte("1882")}, + []interface{}{int64(1686835013348), []byte("2378")}, + []interface{}{int64(1686835014362), []byte("3007")}, + }, + }, + }, + 1686835010300, + 1686835014362, + 2, + 4, + []valueToCheckInResponse{ + {frameIndex: 0, fieldIndex: 0, rowIndex: 0, value: time.Unix(0, 1686835010300*int64(time.Millisecond))}, + {frameIndex: 0, fieldIndex: 1, rowIndex: 0, value: float64(2102)}, + }, + "foo=bar", + "", + "", + nil, + }, + {"should return error because we missed an actual reducer", + queryModel{Command: models.TimeSeriesMRange, Key: "test1", Filter: "filter", TsGroupByLabel: "foo"}, + interface{}("someString"), + 0, + 0, + 0, + 0, + nil, + "", + "", + "reducer not provided for groups, please provide a reducer (e.g. avg, sum) and try again", + nil, + }, { "should return error if result is string", queryModel{Command: models.TimeSeriesMRange, Key: "test1", Filter: "filter"}, diff --git a/pkg/types.go b/pkg/types.go index cd08973..3897352 100644 --- a/pkg/types.go +++ b/pkg/types.go @@ -69,6 +69,8 @@ type queryModel struct { Max string `json:"max"` ZRangeQuery string `json:"zrangeQuery"` Path string `json:"path"` + TsReducer string `json:"tsReducer"` + TsGroupByLabel string `json:"tsGroupByLabel"` SearchQuery string `json:"searchQuery"` SortBy string `json:"sortBy"` SortDirection string `json:"sortDirection"` diff --git a/src/components/QueryEditor/QueryEditor.test.tsx b/src/components/QueryEditor/QueryEditor.test.tsx index 79f400b..77618a2 100644 --- a/src/components/QueryEditor/QueryEditor.test.tsx +++ b/src/components/QueryEditor/QueryEditor.test.tsx @@ -501,6 +501,16 @@ describe('QueryEditor', () => { queryWhenShown: { refId: '', type: QueryTypeValue.TIMESERIES, command: RedisTimeSeries.RANGE }, queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: Redis.INFO }, }, + { + name: 'tsGroupByLabel', + getComponent: (wrapper: ShallowComponent) => + wrapper.findWhere((node) => { + return node.prop('onChange') === wrapper.instance().onTsGroupByLabelChange; + }), + type: 'string', + queryWhenShown: { refId: '', type: QueryTypeValue.TIMESERIES, command: RedisTimeSeries.MRANGE }, + queryWhenHidden: { refId: '', type: QueryTypeValue.REDIS, command: Redis.INFO }, + }, { name: 'zrangeQuery', getComponent: (wrapper: ShallowComponent) => diff --git a/src/components/QueryEditor/QueryEditor.tsx b/src/components/QueryEditor/QueryEditor.tsx index 989d06d..408aabe 100644 --- a/src/components/QueryEditor/QueryEditor.tsx +++ b/src/components/QueryEditor/QueryEditor.tsx @@ -31,6 +31,8 @@ import { RedisJson, RedisQuery, RedisTimeSeries, + Reducers, + ReducerValue, ZRangeQuery, ZRangeQueryValue, } from '../../redis'; @@ -189,6 +191,16 @@ export class QueryEditor extends PureComponent { }); }; + /** + * Ts Reducer Change + */ + onTsReducerChange = this.createSelectFieldHandler('tsReducer'); + + /** + * Group By Change + */ + onTsGroupByLabelChange = this.createTextFieldHandler('tsGroupByLabel'); + /** * Aggregation change */ @@ -332,6 +344,8 @@ export class QueryEditor extends PureComponent { streamingInterval, streamingCapacity, streamingDataType, + tsGroupByLabel, + tsReducer, } = this.props.query; const { onRunQuery, datasource } = this.props; @@ -717,6 +731,30 @@ export class QueryEditor extends PureComponent { )} + {type === QueryTypeValue.TIMESERIES && + command && + CommandParameters.tsGroupBy.includes(command as RedisTimeSeries) && ( +
+ + {tsGroupByLabel && ( +