Skip to content

Commit 87de672

Browse files
feature: Adds hisRead support
1 parent 2cb16a1 commit 87de672

File tree

5 files changed

+104
-24
lines changed

5 files changed

+104
-24
lines changed

DEV.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,5 @@ Alternatively, nearly every Axon query can be visualized using the table view.
4848

4949
# TODO
5050

51-
* [ ] Add support for non-eval queries (read, hisRead, etc)
51+
* [ ] Add support for non-eval queries (read, hisRead, etc)
52+
* [ ] Improve login/logout and add auth/retry logic

pkg/plugin/datasource.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ func (datasource *Datasource) QueryData(ctx context.Context, req *backend.QueryD
9696
}
9797

9898
type QueryModel struct {
99-
Expr string `json:"expr"`
99+
Type string `json:"type"`
100+
Eval string `json:"eval"`
101+
HisRead string `json:"hisRead"`
100102
}
101103

102104
func (datasource *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) backend.DataResponse {
@@ -118,15 +120,28 @@ func (datasource *Datasource) query(ctx context.Context, pCtx backend.PluginCont
118120
"$__interval": haystack.NewNumber(query.Interval.Minutes(), "min").ToZinc(),
119121
}
120122

121-
expr := model.Expr
122-
123-
eval, evalErr := datasource.eval(expr, variables)
124-
if evalErr != nil {
125-
log.DefaultLogger.Error(evalErr.Error())
126-
return backend.ErrDataResponse(backend.StatusBadRequest, fmt.Sprintf("Axon eval failure: %v", evalErr.Error()))
123+
var grid haystack.Grid
124+
switch model.Type {
125+
case "Eval":
126+
eval, err := datasource.eval(model.Eval, variables)
127+
if err != nil {
128+
log.DefaultLogger.Error(err.Error())
129+
return backend.ErrDataResponse(backend.StatusBadRequest, fmt.Sprintf("Axon eval failure: %v", err.Error()))
130+
}
131+
grid = eval
132+
case "HisRead":
133+
hisRead, err := datasource.hisRead(model.HisRead, query.TimeRange)
134+
if err != nil {
135+
log.DefaultLogger.Error(err.Error())
136+
return backend.ErrDataResponse(backend.StatusBadRequest, fmt.Sprintf("HisRead failure: %v", err.Error()))
137+
}
138+
grid = hisRead
139+
default:
140+
log.DefaultLogger.Warn("No valid input, returning empty Grid")
141+
grid = haystack.EmptyGrid()
127142
}
128143

129-
frame, frameErr := dataFrameFromGrid(eval)
144+
frame, frameErr := dataFrameFromGrid(grid)
130145
if frameErr != nil {
131146
log.DefaultLogger.Error(frameErr.Error())
132147
return backend.ErrDataResponse(backend.StatusBadRequest, fmt.Sprintf("Frame conversion failure: %v", frameErr.Error()))
@@ -169,6 +184,13 @@ func (datasource *Datasource) eval(expr string, variables map[string]string) (ha
169184
return datasource.client.Eval(expr)
170185
}
171186

187+
func (datasource *Datasource) hisRead(id string, timeRange backend.TimeRange) (haystack.Grid, error) {
188+
ref := haystack.NewRef(id, "")
189+
start := haystack.NewDateTimeFromGo(timeRange.From.UTC())
190+
end := haystack.NewDateTimeFromGo(timeRange.To.UTC())
191+
return datasource.client.HisReadAbsDateTime(ref, start, end)
192+
}
193+
172194
func dataFrameFromGrid(grid haystack.Grid) (*data.Frame, error) {
173195
fields := []*data.Field{}
174196

pkg/plugin/datasource_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import (
2222
func TestQueryData_Eval(t *testing.T) {
2323
data := getResponse(
2424
&QueryModel{
25-
Expr: "[{ts: now()-1hr, v0: 0}, {ts: now(), v0: 10}].toGrid",
25+
Type: "Eval",
26+
Eval: "[{ts: now()-1hr, v0: 0}, {ts: now(), v0: 10}].toGrid",
2627
},
2728
backend.TimeRange{},
2829
t,
@@ -34,7 +35,8 @@ func TestQueryData_Eval(t *testing.T) {
3435
func TestQueryData_Eval_Variables(t *testing.T) {
3536
data := getResponse(
3637
&QueryModel{
37-
Expr: "[{ts: $__timeRange_start, v0: 0}, {ts: $__timeRange_end, v0: 10}].toGrid",
38+
Type: "Eval",
39+
Eval: "[{ts: $__timeRange_start, v0: 0}, {ts: $__timeRange_end, v0: 10}].toGrid",
3840
},
3941
backend.TimeRange{
4042
From: time.Now().Add(-1 * time.Hour),

src/components/QueryEditor.tsx

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,75 @@
1-
import React, { ChangeEvent } from 'react';
2-
import { Button, Icon, InlineField, Input } from '@grafana/ui';
3-
import { QueryEditorProps } from '@grafana/data';
1+
import React, { ChangeEvent, ReactNode } from 'react';
2+
import { Button, Form, Icon, InlineField, Input, Select } from '@grafana/ui';
3+
import { QueryEditorProps, SelectableValue } from '@grafana/data';
44
import { DataSource } from '../datasource';
5-
import { MyDataSourceOptions, MyQuery } from '../types';
5+
import { DEFAULT_QUERY, MyDataSourceOptions, MyQuery } from '../types';
66

77
type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>;
88

99
export function QueryEditor({ query, onChange, onRunQuery }: Props) {
10-
const onExprChange = (event: ChangeEvent<HTMLInputElement>) => {
11-
onChange({ ...query, expr: event.target.value });
10+
const onTypeChange = (event: SelectableValue<number>) => {
11+
let queryTypeIndex = event.value ?? queryTypeDefault.value;
12+
onChange({ ...query, type: queryTypes[queryTypeIndex].label });
13+
};
14+
const onEvalChange = (event: ChangeEvent<HTMLInputElement>) => {
15+
onChange({ ...query, eval: event.target.value });
16+
};
17+
const onHisReadChange = (event: ChangeEvent<HTMLInputElement>) => {
18+
onChange({ ...query, hisRead: event.target.value });
19+
};
20+
21+
const queryTypes = [
22+
{ label: 'Eval', value: 0, description: 'Evaluate an Axon expression' },
23+
{ label: 'HisRead', value: 1, description: 'Read the histories of a list of points' }
24+
];
25+
const queryTypeDefault = queryTypes[0];
26+
function queryTypeFromLabel(label: string) {
27+
return queryTypes.find(queryType => queryType.label == label) ?? queryTypeDefault
28+
}
29+
30+
const SelectComponent = () => {
31+
return (
32+
<Select
33+
options={queryTypes}
34+
value={queryTypeFromLabel(query.type)}
35+
defaultValue={queryTypeDefault}
36+
width={30}
37+
onChange={ queryType => {
38+
onTypeChange(queryType);
39+
}}
40+
/>
41+
);
1242
};
1343

14-
const { expr } = query;
44+
function renderQuery(): ReactNode {
45+
let queryType = queryTypeFromLabel(query.type);
46+
switch(queryType.value) {
47+
case 0: // Eval
48+
return <InlineField label="Axon" labelWidth="auto" tooltip="An Axon expression to evaluate on the Haystack server">
49+
<Input width={100} prefix={<Icon name="angle-right" />} onChange={onEvalChange} value={query.eval ?? DEFAULT_QUERY.eval} />
50+
</InlineField>
51+
case 1: // HisRead
52+
return <InlineField label="Point ID" labelWidth="auto" tooltip="The ID of the point to read">
53+
<Input width={100} prefix={<Icon name="angle-right" />} onChange={onHisReadChange} value={query.hisRead ?? DEFAULT_QUERY.hisRead} />
54+
</InlineField>
55+
}
56+
return <p>Select a query type</p>
57+
}
1558

1659
return (
1760
<div className="gf-form">
18-
<InlineField label="Axon" labelWidth="auto" tooltip="An Axon expression to evaluate on the Haystack server">
19-
<Input width={100} prefix={<Icon name="angle-right" />} onChange={onExprChange} value={expr || ''} />
20-
</InlineField>
21-
<Button type="submit" onClick={onRunQuery}>Run</Button>
61+
<Form
62+
defaultValues={DEFAULT_QUERY}
63+
onSubmit={(newQuery: Partial<MyQuery>) => query = { ...query, ...newQuery }}
64+
>{({register, errors}) => {
65+
return (
66+
<div>
67+
<SelectComponent/>
68+
{renderQuery()}
69+
<Button type="submit" onClick={onRunQuery}>Run</Button>
70+
</div>
71+
)
72+
}}</Form>
2273
</div>
2374
);
2475
}

src/types.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { DataQuery, DataSourceJsonData } from '@grafana/data';
22

33
export interface MyQuery extends DataQuery {
4-
expr: string;
4+
type: string; // Defines the type of query that should be executed
5+
eval: string;
6+
hisRead: string;
57
}
68

79
export const DEFAULT_QUERY: Partial<MyQuery> = {
8-
expr: "[{ts: $__timeRange_start, v0: 0}, {ts: $__timeRange_end, v0: 10}].toGrid",
10+
type: "Eval",
11+
eval: "[{ts: $__timeRange_start, v0: 0}, {ts: $__timeRange_end, v0: 10}].toGrid",
12+
hisRead: "abcdef-123456"
913
};
1014

1115
/**

0 commit comments

Comments
 (0)