Skip to content

Commit

Permalink
Merge pull request #116 from sasaki77/string-value
Browse files Browse the repository at this point in the history
BUG: handling string value data
  • Loading branch information
sasaki77 authored Nov 9, 2022
2 parents 427b286 + 0e77707 commit e2f41cd
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 22 deletions.
28 changes: 15 additions & 13 deletions pkg/archiverappliance/aaclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,21 +150,23 @@ func archiverSingleQueryParser(jsonAsBytes []byte) (models.SingleData, error) {

var d models.DataResponse
if response[0].Meta.Waveform {
var data models.ArrayResponseModel
jsonErr = json.Unmarshal(response[0].Data, &data)
if jsonErr != nil {
log.DefaultLogger.Warn("Conversion of incoming data to JSON has failed", "Error", jsonErr)
return sD, jsonErr
}
d = data
d = &models.ArrayResponseModel{}
} else {
var data models.ScalarResponseModel
jsonErr = json.Unmarshal(response[0].Data, &data)
if jsonErr != nil {
log.DefaultLogger.Warn("Conversion of incoming data to JSON has failed", "Error", jsonErr)
return sD, jsonErr
d = &models.ScalarResponseModel{}
}

jsonErr = json.Unmarshal(response[0].Data, d)

// If unmarshal is failed, response data might be string data
if jsonErr != nil {
d = &models.StringResponseModel{}
err := json.Unmarshal(response[0].Data, d)

// If unmarshal is failed again, response data is not supported data type
if err != nil {
log.DefaultLogger.Warn("Conversion of incoming data to JSON has failed", "Error", err)
return sD, err
}
d = data
}

// Obtain PV name
Expand Down
62 changes: 62 additions & 0 deletions pkg/archiverappliance/aaclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,68 @@ func TestArchiverSingleQueryParserEmpty(t *testing.T) {
}
}

func TestArchiverSingleQueryParserString(t *testing.T) {
type responseParams struct {
length int
name string
firstVal string
lastVal string
}

var dataNames = []struct {
fileName string
output responseParams
}{
{
fileName: "../test_data/string_value_response.JSON",
output: responseParams{length: 4, name: "PFROP:RING:STATUS_STR", firstVal: "Injection", lastVal: "Top-up"},
},
}

type testData struct {
input []byte
output responseParams
}

var tests []testData
for _, entry := range dataNames {
fileData, err := ioutil.ReadFile(entry.fileName)
if err != nil {
t.Fatalf("Failed to load test data: %v", err)
}
tests = append(tests, testData{input: fileData, output: entry.output})
}

for idx, testCase := range tests {
testName := fmt.Sprintf("Case: %d", idx)
t.Run(testName, func(t *testing.T) {
// result := testCase.output
result, err := archiverSingleQueryParser(testCase.input)
if err != nil {
t.Fatalf("An unexpected error has occurred")
}
if result.Name != testCase.output.name {
t.Fatalf("Names differ - Wanted: %v Got: %v", testCase.output.name, result.Name)
}

v := result.Values.(*models.Strings)
if len(v.Times) != len(v.Values) {
t.Fatalf("Lengths of Times and Values differ - Times: %v Values: %v", len(v.Times), len(v.Values))
}
resultLength := len(v.Times)
if resultLength != testCase.output.length {
t.Fatalf("Lengths differ - Wanted: %v Got: %v", testCase.output.length, resultLength)
}
if v.Values[0] != testCase.output.firstVal {
t.Fatalf("First values differ - Wanted: %v Got: %v", testCase.output.firstVal, v.Values[0])
}
if v.Values[resultLength-1] != testCase.output.lastVal {
t.Fatalf("Last values differ - Wanted: %v Got: %v", testCase.output.lastVal, v.Values[resultLength-1])
}
})
}
}

func TestArchiverSingleQueryParserInvalidData(t *testing.T) {
var dataNames = []struct {
name string
Expand Down
74 changes: 67 additions & 7 deletions pkg/archiverappliance/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@ func (f fakeClient) ExecuteSingleQuery(target string, qm models.ArchiverQueryMod
return models.SingleData{}, errors.New("test error")
}

var values []float64
if target == "PV:NAME1" {
values = []float64{0, 1, 2}
} else {
values = []float64{3, 4, 5}
}
var v models.Values

v := &models.Scalars{Times: testhelper.TimeArrayHelper(0, 3), Values: values}
switch target {
case "string":
s := []string{"test1", "test2", "test3"}
v = &models.Strings{Times: testhelper.TimeArrayHelper(0, 3), Values: s}
case "PV:NAME1":
values := []float64{0, 1, 2}
v = &models.Scalars{Times: testhelper.TimeArrayHelper(0, 3), Values: values}
default:
values := []float64{3, 4, 5}
v = &models.Scalars{Times: testhelper.TimeArrayHelper(0, 3), Values: values}
}

sd := models.SingleData{
Name: target,
Expand Down Expand Up @@ -228,6 +233,61 @@ func TestQuery(t *testing.T) {
},
},
},
{
name: "test string response",
req: &backend.QueryDataRequest{
Queries: []backend.DataQuery{
{
Interval: testhelper.MultiReturnHelperParseDuration(time.ParseDuration("0s")),
JSON: json.RawMessage(`{
"alias": "",
"aliasPattern": "",
"constant":6.5,
"functions":[],
"hide":false ,
"operator": "",
"refId":"A" ,
"regex":false ,
"target":"string"
}`),
MaxDataPoints: 1000,
QueryType: "",
RefID: "A",
TimeRange: backend.TimeRange{
From: testhelper.MultiReturnHelperParse(time.Parse(TIME_FORMAT, "2021-01-27T14:30:41.678-08:00")),
To: testhelper.MultiReturnHelperParse(time.Parse(TIME_FORMAT, "2021-01-28T14:30:41.678-08:00")),
},
},
},
},
out: &backend.QueryDataResponse{
Responses: map[string]backend.DataResponse{
"A": {
Frames: data.Frames{
&data.Frame{
Name: "string",
RefID: "",
Fields: []*data.Field{
{
Name: "Time",
},
{
Name: "string",
Labels: data.Labels{
"pvname": "string",
},
Config: &data.FieldConfig{
DisplayName: "string",
},
},
},
Meta: &data.FrameMeta{},
},
},
},
},
},
},
}
f := fakeClient{}
for _, testCase := range tests {
Expand Down
24 changes: 24 additions & 0 deletions pkg/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,15 @@ type SingleScalarResponseModel struct {
Val json.Number `json:"val"`
}

type SingleStringResponseModel struct {
Millis *json.Number `json:"millis,omitempty"`
Nanos *json.Number `json:"nanos,omitempty"`
Secs *json.Number `json:"secs,omitempty"`
Val string `json:"val"`
}

type ScalarResponseModel []SingleScalarResponseModel
type StringResponseModel []SingleStringResponseModel
type ArrayResponseModel []SingleArrayResponseModel

type DataResponse interface {
Expand Down Expand Up @@ -131,6 +139,22 @@ func (response ScalarResponseModel) ToSingleDataValues() (Values, error) {
return &Scalars{Times: times, Values: values}, nil
}

func (response StringResponseModel) ToSingleDataValues() (Values, error) {
// Build output data block
dataSize := len(response)

// initialize the slices with their final size so append operations are not necessary
times := make([]time.Time, dataSize)
values := make([]string, dataSize)

for idx, dataPt := range response {
times[idx] = convertNanosec(dataPt.Millis)
values[idx] = dataPt.Val
}

return &Strings{Times: times, Values: values}, nil
}

func (response ArrayResponseModel) ToSingleDataValues() (Values, error) {
// Build output data block
dataSize := len(response)
Expand Down
74 changes: 72 additions & 2 deletions pkg/models/singledata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,60 @@ func TestToFrameScalar(t *testing.T) {
}
}

func TestToFrameString(t *testing.T) {
var tests = []struct {
sD SingleData
name string
pvname string
values []string
dataSize int
}{
{
sD: SingleData{
Name: "testing_name",
PVname: "pvname",
Values: &Strings{
Times: []time.Time{testhelper.TimeHelper(0), testhelper.TimeHelper(1), testhelper.TimeHelper(2)},
Values: []string{"1", "2", "3"},
},
},
name: "testing_name",
pvname: "pvname",
values: []string{"1", "2", "3"},
dataSize: 3,
},
}
for idx, testCase := range tests {
testName := fmt.Sprintf("%d: %s", idx, testCase.name)
t.Run(testName, func(t *testing.T) {
result := testCase.sD.ToFrame(FormatOption(FORMAT_TIMESERIES))
if testCase.name != result.Name {
t.Errorf("got %v, want %v", result.Name, testCase.name)
}
if result.Fields[0].Name != "time" {
t.Errorf("got %v, want time", result.Fields[0].Name)
}
if result.Fields[0].Len() != testCase.dataSize {
t.Errorf("got %d, want %d", result.Fields[0].Len(), testCase.dataSize)
}
if testCase.name != result.Fields[1].Config.DisplayName {
t.Errorf("got %v, want %v", result.Fields[1].Config.DisplayName, testCase.name)
}
if testCase.pvname != result.Fields[1].Labels["pvname"] {
t.Errorf("got %v, want %v", result.Fields[1].Labels["pvname"], testCase.pvname)
}
if testCase.name != result.Fields[1].Name {
t.Errorf("got %v, want %v", result.Fields[1].Name, testCase.name)
}
for i := 0; i < result.Fields[1].Len(); i++ {
if testCase.values[i] != result.Fields[1].CopyAt(i) {
t.Errorf("got %v, want %v", result.Fields[1].CopyAt(i), testCase.values[i])
}
}
})
}
}

func TestToFrameArray(t *testing.T) {
var tests = []struct {
sD SingleData
Expand Down Expand Up @@ -333,7 +387,7 @@ func TestExtrapolation(t *testing.T) {
Values: []float64{1},
},
},
name: "extrapolation",
name: "scalars extrapolation",
t: testhelper.TimeHelper(5),
sDOut: SingleData{
Values: &Scalars{
Expand All @@ -342,14 +396,30 @@ func TestExtrapolation(t *testing.T) {
},
},
},
{
sDIn: SingleData{
Values: &Strings{
Times: []time.Time{testhelper.TimeHelper(0)},
Values: []string{"1"},
},
},
name: "strings extrapolation",
t: testhelper.TimeHelper(5),
sDOut: SingleData{
Values: &Strings{
Times: []time.Time{testhelper.TimeHelper(0)},
Values: []string{"1"},
},
},
},
{
sDIn: SingleData{
Values: &Arrays{
Times: []time.Time{testhelper.TimeHelper(0)},
Values: [][]float64{{1, 1}},
},
},
name: "extrapolation",
name: "arrays extrapolation",
t: testhelper.TimeHelper(5),
sDOut: SingleData{
Values: &Arrays{
Expand Down
34 changes: 34 additions & 0 deletions pkg/models/strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package models

import (
"time"

"github.com/grafana/grafana-plugin-sdk-go/data"
)

type Strings struct {
Times []time.Time
Values []string
}

func (v *Strings) ToFields(pvname string, name string, format FormatOption) []*data.Field {
// ToFields doesn't use FormatOption in Strings for now

var fields []*data.Field

//add the time dimension
fields = append(fields, data.NewField("time", nil, v.Times))

// add values
labels := make(data.Labels, 1)
labels["pvname"] = pvname

valueField := data.NewField(name, labels, v.Values)
valueField.Config = &data.FieldConfig{DisplayName: name}
fields = append(fields, valueField)

return fields
}

func (v *Strings) Extrapolation(t time.Time) {
}
18 changes: 18 additions & 0 deletions pkg/models/testhelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@ func SingleDataCompareHelper(result []*SingleData, wanted []*SingleData, t *test
}
}
}
case *Strings:
wantedv := wanted[udx].Values.(*Strings)
if len(wantedv.Times) != len(resultv.Times) {
t.Errorf("Input and output arrays' times differ in length. Wanted %v, got %v", len(wantedv.Times), len(resultv.Times))
return
}
if len(wantedv.Values) != len(resultv.Values) {
t.Errorf("Input and output arrays' values differ in length. Wanted %v, got %v", len(wantedv.Values), len(resultv.Values))
return
}
for idx := range wantedv.Values {
if resultv.Times[idx] != wantedv.Times[idx] {
t.Errorf("Times at index %v do not match, Wanted %v, got %v", idx, wantedv.Times[idx], resultv.Times[idx])
}
if resultv.Values[idx] != wantedv.Values[idx] {
t.Errorf("Values at index %v do not match, Wanted %v, got %v", idx, wantedv.Values[idx], resultv.Values[idx])
}
}
default:
t.Fatalf("Response Values are invalid")
}
Expand Down
Loading

0 comments on commit e2f41cd

Please sign in to comment.