Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2 improvement: Make time property optional #55

Merged
merged 16 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ services:
grafana_version: ${GRAFANA_VERSION:-10.2.0}
ports:
- 3000:3000/tcp
environment:
GF_LOG_LEVEL: 'debug'
jannikbend marked this conversation as resolved.
Show resolved Hide resolved
volumes:
- ./dist:/var/lib/grafana/plugins/dvelop-odata-datasource
- ./provisioning:/etc/grafana/provisioning
Expand Down
39 changes: 14 additions & 25 deletions pkg/plugin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ import (
"net/url"
"path"
"strings"
"time"

"github.com/d-velop/grafana-odata-datasource/pkg/plugin/odata"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)

type ODataClient interface {
GetServiceRoot() (*http.Response, error)
GetMetadata() (*http.Response, error)
Get(entitySet string, properties []property, timeProperty string, timeRange backend.TimeRange,
Get(entitySet string, properties []property,
filterConditions []filterCondition) (*http.Response, error)
}

Expand All @@ -38,29 +37,29 @@ func (client *ODataClientImpl) GetMetadata() (*http.Response, error) {
return client.httpClient.Get(requestUrl.String())
}

func (client *ODataClientImpl) Get(entitySet string, properties []property, timeProperty string,
timeRange backend.TimeRange, filterConditions []filterCondition) (*http.Response, error) {
requestUrl, err := buildQueryUrl(client.baseUrl, entitySet, properties, timeProperty, timeRange,
func (client *ODataClientImpl) Get(entitySet string, properties []property, filterConditions []filterCondition) (*http.Response, error) {
requestUrl, err := buildQueryUrl(client.baseUrl, entitySet, properties,
filterConditions, client.urlSpaceEncoding)
if err != nil {
return nil, err
}
return client.httpClient.Get(requestUrl.String())
urlString := requestUrl.String()
log.DefaultLogger.Debug("Constructed request url: ", urlString)
return client.httpClient.Get(urlString)
}

func buildQueryUrl(baseUrl string, entitySet string, properties []property, timeProperty string,
timeRange backend.TimeRange, filterConditions []filterCondition, urlSpaceEncoding string) (*url.URL, error) {
func buildQueryUrl(baseUrl string, entitySet string, properties []property, filterConditions []filterCondition, urlSpaceEncoding string) (*url.URL, error) {
requestUrl, err := url.Parse(baseUrl)
if err != nil {
return nil, err
}
requestUrl.Path = path.Join(requestUrl.Path, entitySet)
params, _ := url.ParseQuery(requestUrl.RawQuery)
filterParam := mapFilter(timeProperty, timeRange, filterConditions)
filterParam := mapFilter(filterConditions)
if len(filterParam) > 0 {
params.Add(odata.Filter, filterParam)
}
selectParam := mapSelect(properties, timeProperty)
selectParam := mapSelect(properties)
if len(selectParam) > 0 {
params.Add(odata.Select, selectParam)
}
Expand All @@ -72,25 +71,17 @@ func buildQueryUrl(baseUrl string, entitySet string, properties []property, time
return requestUrl, nil
}

func mapSelect(properties []property, timeProperty string) string {
func mapSelect(properties []property) string {
var result []string
if len(properties) > 0 {
for _, selectProp := range properties {
result = append(result, selectProp.Name)
}
}
if len(timeProperty) > 0 {
result = append(result, timeProperty)
}
return strings.Join(result[:], ",")
}

func mapFilter(timeProperty string, timeRange backend.TimeRange, filterConditions []filterCondition) string {
var filter string
if len(timeProperty) > 0 {
filter = fmt.Sprintf("%s ge %s and %s le %s", timeProperty, timeRange.From.UTC().Format(time.RFC3339),
timeProperty, timeRange.To.UTC().Format(time.RFC3339))
}
func mapFilter(filterConditions []filterCondition) string {
var customFilter = ""
jannikbend marked this conversation as resolved.
Show resolved Hide resolved
for index, element := range filterConditions {
if element.Property.Type == odata.EdmString {
Expand All @@ -102,8 +93,6 @@ func mapFilter(timeProperty string, timeRange backend.TimeRange, filterCondition
customFilter += " and "
}
}
if len(customFilter) > 0 {
filter += " and " + customFilter
}
return filter

return customFilter
jannikbend marked this conversation as resolved.
Show resolved Hide resolved
}
65 changes: 33 additions & 32 deletions pkg/plugin/client_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,51 @@ import (
"time"

"github.com/d-velop/grafana-odata-datasource/pkg/plugin/odata"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/assert"
)

func TestMapFilter(t *testing.T) {
tables := []struct {
name string
timeProperty string
timeRange backend.TimeRange
filterConditions []filterCondition
expected string
}{
{
name: "Time filter only",
timeProperty: "time",
timeRange: aOneDayTimeRange(),
filterConditions: someFilterConditions(int32Eq5),
expected: "time ge 2022-04-21T12:30:50Z and time le 2022-04-21T12:30:50Z and int32 eq 5",
name: "Time filter only",
filterConditions: someFilterConditions(
withFilterCondition(timeProp, "ge", aOneDayTimeRange().From.Format(time.RFC3339)),
withFilterCondition(timeProp, "le", aOneDayTimeRange().To.Format(time.RFC3339)),
int32Eq5),
expected: "time ge 2022-04-21T12:30:50Z and time le 2022-04-21T12:30:50Z and int32 eq 5",
},
{
name: "Time filter and int and string filter",
timeProperty: "time",
timeRange: aOneDayTimeRange(),
filterConditions: someFilterConditions(int32Eq5, withFilterCondition(stringProp, "eq", "Hello")),
expected: "time ge 2022-04-21T12:30:50Z and time le 2022-04-21T12:30:50Z and int32 eq 5 and string eq 'Hello'",
name: "Time filter and int and string filter",
filterConditions: someFilterConditions(
withFilterCondition(timeProp, "ge", aOneDayTimeRange().From.Format(time.RFC3339)),
withFilterCondition(timeProp, "le", aOneDayTimeRange().To.Format(time.RFC3339)),
int32Eq5,
withFilterCondition(stringProp, "eq", "Hello")),
expected: "time ge 2022-04-21T12:30:50Z and time le 2022-04-21T12:30:50Z and int32 eq 5 and string eq 'Hello'",
},
{
name: "Time filter and string filter",
timeProperty: "time",
timeRange: aOneDayTimeRange(),
filterConditions: someFilterConditions(withFilterCondition(stringProp, "eq", "")),
expected: "time ge 2022-04-21T12:30:50Z and time le 2022-04-21T12:30:50Z and string eq ''",
name: "Time filter and string filter",
filterConditions: someFilterConditions(
withFilterCondition(timeProp, "ge", aOneDayTimeRange().From.Format(time.RFC3339)),
withFilterCondition(timeProp, "le", aOneDayTimeRange().To.Format(time.RFC3339)),
withFilterCondition(stringProp, "eq", "")),
expected: "time ge 2022-04-21T12:30:50Z and time le 2022-04-21T12:30:50Z and string eq ''",
},
{
name: "String filter only",
filterConditions: someFilterConditions(withFilterCondition(stringProp, "eq", "")),
expected: " and string eq ''",
expected: "string eq ''",
},
}

for _, table := range tables {
t.Run(table.name, func(t *testing.T) {
// Act
var filterString = mapFilter(table.timeProperty, table.timeRange, table.filterConditions)
var filterString = mapFilter(table.filterConditions)

// Assert
assert.Equal(t, table.expected, filterString)
Expand All @@ -65,27 +66,27 @@ func TestBuildQueryUrl(t *testing.T) {
entitySet string
properties []property
timeProperty string
timeRange backend.TimeRange
timeRange []filterCondition
filterConditions []filterCondition
expected string
}{
{
name: "Success",
baseUrl: "http://localhost:5000",
entitySet: "Temperatures",
properties: []property{aProperty(int32Prop)},
timeProperty: "time",
timeRange: aOneDayTimeRange(),
filterConditions: someFilterConditions(withFilterCondition(stringProp, "eq", "")),
expected: "http://localhost:5000/Temperatures?%24filter=time+ge+2022-04-21T12%3A30%3A50Z+and+time+le+2022-04-21T12%3A30%3A50Z+and+string+eq+%27%27&%24select=int32%2Ctime",
name: "Success",
baseUrl: "http://localhost:5000",
entitySet: "Temperatures",
properties: []property{aProperty(int32Prop), aProperty(timeProp)},
filterConditions: someFilterConditions(
withFilterCondition(timeProp, "ge", aOneDayTimeRange().From.Format(time.RFC3339)),
withFilterCondition(timeProp, "le", aOneDayTimeRange().To.Format(time.RFC3339)),
withFilterCondition(stringProp, "eq", "")),
expected: "http://localhost:5000/Temperatures?%24filter=time+ge+2022-04-21T12%3A30%3A50Z+and+time+le+2022-04-21T12%3A30%3A50Z+and+string+eq+%27%27&%24select=int32%2Ctime",
},
}

for _, table := range tables {
t.Run(table.name, func(t *testing.T) {
// Act
var builtUrl, err = buildQueryUrl(table.baseUrl, table.entitySet, table.properties, table.timeProperty,
table.timeRange, table.filterConditions, "+")
var builtUrl, err = buildQueryUrl(table.baseUrl, table.entitySet, table.properties, table.filterConditions, "+")

// Assert
assert.NoError(t, err)
Expand Down Expand Up @@ -131,7 +132,7 @@ func TestGetEntities(t *testing.T) {
client := GetOC("*", table.handlerCallback)

// Act
resp, err := client.Get("Temperatures", []property{aProperty(int32Prop)}, "time", aOneDayTimeRange(), someFilterConditions(int32Eq5))
resp, err := client.Get("Temperatures", []property{aProperty(int32Prop)}, someFilterConditions(int32Eq5))

// Assert
if table.expectedError == nil {
Expand Down
46 changes: 26 additions & 20 deletions pkg/plugin/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"fmt"
"io"
"net/http"
"time"

"github.com/d-velop/grafana-odata-datasource/pkg/plugin/odata"
"github.com/grafana/grafana-plugin-sdk-go/backend"
Expand Down Expand Up @@ -130,27 +129,34 @@ func (ds *ODataSource) query(clientInstance ODataClient, query backend.DataQuery
return response
}

timeProperty := qm.TimeProperty.Name
// Prevent empty queries from being executed
if qm.TimeProperty == nil && len(qm.Properties) == 0 {
return response
}

frame := data.NewFrame("response")
frame.Name = query.RefID
if frame.Meta == nil {
frame.Meta = &data.FrameMeta{}
}
frame.Meta.PreferredVisualization = data.VisTypeTable
labels, err := data.LabelsFromString("time=" + timeProperty)
carroe marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
response.Error = err
return response

if qm.TimeProperty != nil {
log.DefaultLogger.Debug("Time property configured", "name", qm.TimeProperty.Name)
field := data.NewField(qm.TimeProperty.Name, nil, odata.ToArray(qm.TimeProperty.Type))
frame.Fields = append(frame.Fields, field)
}
frame.Fields = append(frame.Fields,
data.NewField("time", labels, []*time.Time{}),
)
for _, prop := range qm.Properties {
frame.Fields = append(frame.Fields, data.NewField(prop.Name, nil, odata.ToArray(prop.Type)))
field := data.NewField(prop.Name, nil, odata.ToArray(prop.Type))
frame.Fields = append(frame.Fields, field)
}

resp, err := clientInstance.Get(qm.EntitySet.Name, qm.Properties, timeProperty,
query.TimeRange, qm.FilterConditions)
props := qm.Properties
if qm.TimeProperty != nil {
props = append(props, *qm.TimeProperty)
}
resp, err := clientInstance.Get(qm.EntitySet.Name, props,
append(qm.FilterConditions, BackendTimeRangeToODataFilter(query.TimeRange, qm.TimeProperty)...))
if err != nil {
response.Error = err
return response
Expand Down Expand Up @@ -178,17 +184,17 @@ func (ds *ODataSource) query(clientInstance ODataClient, query backend.DataQuery
log.DefaultLogger.Debug("query complete", "noOfEntities", len(result.Value))

for _, entry := range result.Value {
values := make([]interface{}, len(qm.Properties)+1)
if timeValue, err := time.Parse(time.RFC3339Nano, fmt.Sprint(entry[timeProperty])); err == nil {
values[0] = &timeValue
} else {
values[0] = nil
values := make([]interface{}, 0)
jannikbend marked this conversation as resolved.
Show resolved Hide resolved

if qm.TimeProperty != nil {
values = append(values, odata.MapValue(entry[qm.TimeProperty.Name], qm.TimeProperty.Type))
}
for i, prop := range qm.Properties {

for _, prop := range qm.Properties {
if value, ok := entry[prop.Name]; ok {
values[i+1] = odata.MapValue(value, prop.Type)
values = append(values, odata.MapValue(value, prop.Type))
} else {
values[i+1] = nil
values = append(values, nil)
}
}
frame.AppendRow(values...)
Expand Down
Loading
Loading