Skip to content

Commit 6043581

Browse files
Merge pull request #7 from NeedleInAJayStack/feature/op-detection
Op detection
2 parents c7383b9 + 4db7455 commit 6043581

File tree

8 files changed

+123
-46
lines changed

8 files changed

+123
-46
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.19
55
require github.com/grafana/grafana-plugin-sdk-go v0.149.1
66

77
require (
8-
github.com/NeedleInAJayStack/haystack v0.1.6
8+
github.com/NeedleInAJayStack/haystack v0.1.7
99
github.com/google/go-cmp v0.5.9
1010
)
1111

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zum
3535
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
3636
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3737
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
38-
github.com/NeedleInAJayStack/haystack v0.1.6 h1:LhV4SO2Jgjq8YQPd/JcJdb/a3cq9+Lu548vTDj0NakU=
39-
github.com/NeedleInAJayStack/haystack v0.1.6/go.mod h1:Oho5sG64nQS27mApp6gWOUi7m4OgzpWllZrOtZn8qss=
38+
github.com/NeedleInAJayStack/haystack v0.1.7 h1:G0w/DdH6OiPv0pALTvaMYvBIorLaz3yhMupHTqn17Zc=
39+
github.com/NeedleInAJayStack/haystack v0.1.7/go.mod h1:Oho5sG64nQS27mApp6gWOUi7m4OgzpWllZrOtZn8qss=
4040
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
4141
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
4242
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=

pkg/plugin/datasource.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,21 +123,28 @@ func (datasource *Datasource) query(ctx context.Context, pCtx backend.PluginCont
123123

124124
var grid haystack.Grid
125125
switch model.Type {
126-
case "Eval":
126+
case "ops":
127+
ops, err := datasource.ops()
128+
if err != nil {
129+
log.DefaultLogger.Error(err.Error())
130+
return backend.ErrDataResponse(backend.StatusBadRequest, fmt.Sprintf("Ops eval failure: %v", err.Error()))
131+
}
132+
grid = ops
133+
case "eval":
127134
eval, err := datasource.eval(model.Eval, variables)
128135
if err != nil {
129136
log.DefaultLogger.Error(err.Error())
130137
return backend.ErrDataResponse(backend.StatusBadRequest, fmt.Sprintf("Axon eval failure: %v", err.Error()))
131138
}
132139
grid = eval
133-
case "HisRead":
140+
case "hisRead":
134141
hisRead, err := datasource.hisRead(model.HisRead, query.TimeRange)
135142
if err != nil {
136143
log.DefaultLogger.Error(err.Error())
137144
return backend.ErrDataResponse(backend.StatusBadRequest, fmt.Sprintf("HisRead failure: %v", err.Error()))
138145
}
139146
grid = hisRead
140-
case "Read":
147+
case "read":
141148
read, err := datasource.read(model.Read, variables)
142149
if err != nil {
143150
log.DefaultLogger.Error(err.Error())
@@ -185,6 +192,22 @@ func (datasource *Datasource) CheckHealth(_ context.Context, req *backend.CheckH
185192
}, nil
186193
}
187194

195+
func (datasource *Datasource) ops() (haystack.Grid, error) {
196+
result, err := datasource.client.Ops()
197+
// If the error is a 404, try to reconnect and try again
198+
switch error := err.(type) {
199+
case client.HTTPError:
200+
if error.Code == 404 {
201+
datasource.client.Open()
202+
return datasource.client.Ops()
203+
} else {
204+
return result, err
205+
}
206+
default:
207+
return result, err
208+
}
209+
}
210+
188211
func (datasource *Datasource) eval(expr string, variables map[string]string) (haystack.Grid, error) {
189212
for name, val := range variables {
190213
expr = strings.ReplaceAll(expr, name, val)

pkg/plugin/datasource_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestQueryData_Eval(t *testing.T) {
2525
actual := getResponse(
2626
client,
2727
&QueryModel{
28-
Type: "Eval",
28+
Type: "eval",
2929
Eval: "{a: \"a\", b: \"b\"}",
3030
},
3131
t,
@@ -56,7 +56,7 @@ func TestQueryData_HisRead(t *testing.T) {
5656
actual := getResponse(
5757
client,
5858
&QueryModel{
59-
Type: "HisRead",
59+
Type: "hisRead",
6060
HisRead: "abcdefg-12345678",
6161
},
6262
t,
@@ -92,7 +92,7 @@ func TestQueryData_Read(t *testing.T) {
9292
actual := getResponse(
9393
client,
9494
&QueryModel{
95-
Type: "Read",
95+
Type: "read",
9696
Read: "ahu",
9797
},
9898
t,
@@ -185,6 +185,11 @@ func (c *testHaystackClient) About() (haystack.Dict, error) {
185185
return haystack.Dict{}, nil
186186
}
187187

188+
// Ops returns an empty grid
189+
func (c *testHaystackClient) Ops() (haystack.Grid, error) {
190+
return haystack.EmptyGrid(), nil
191+
}
192+
188193
// Eval returns the EvalResponse
189194
func (c *testHaystackClient) Eval(query string) (haystack.Grid, error) {
190195
return c.evalResponse, nil

pkg/plugin/haystackClient.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type HaystackClient interface {
99
Open() error
1010
Close() error
1111
About() (haystack.Dict, error)
12+
Ops() (haystack.Grid, error)
1213
Eval(string) (haystack.Grid, error)
1314
HisReadAbsDateTime(haystack.Ref, haystack.DateTime, haystack.DateTime) (haystack.Grid, error)
1415
Read(string) (haystack.Grid, error)

src/components/QueryEditor.tsx

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,145 @@
11
import React, { ChangeEvent, ReactNode } from 'react';
2-
import { Button, Field, Form, Icon, Input, Select } from '@grafana/ui';
3-
import { QueryEditorProps, SelectableValue } from '@grafana/data';
2+
import { AsyncSelect, Button, Form, Icon, InlineField, Input, VerticalGroup } from '@grafana/ui';
3+
import { DataFrame, DataQueryRequest, Field, getDefaultTimeRange, QueryEditorProps, SelectableValue, Vector } from '@grafana/data';
44
import { DataSource } from '../datasource';
55
import { DEFAULT_QUERY, HaystackDataSourceOptions, HaystackQuery } from '../types';
66

77
type Props = QueryEditorProps<DataSource, HaystackQuery, HaystackDataSourceOptions>;
88

9-
export function QueryEditor({ query, onChange, onRunQuery }: Props) {
10-
const onTypeChange = (event: SelectableValue<number>) => {
11-
let queryTypeIndex = event.value ?? queryTypeDefault.value;
12-
onChange({ ...query, type: queryTypes[queryTypeIndex].label });
9+
export function QueryEditor({ datasource, query, onChange, onRunQuery, range, app }: Props) {
10+
const onTypeChange = (event: SelectableValue<string>) => {
11+
onChange({ ...query, type: event.value ?? queryTypeDefault.value! });
1312
};
1413
const onEvalChange = (event: ChangeEvent<HTMLInputElement>) => {
15-
onChange({ ...query, type: 'Eval', eval: event.target.value });
14+
onChange({ ...query, type: 'eval', eval: event.target.value });
1615
};
1716
const onHisReadChange = (event: ChangeEvent<HTMLInputElement>) => {
18-
onChange({ ...query, type: 'HisRead', hisRead: event.target.value });
17+
onChange({ ...query, type: 'hisRead', hisRead: event.target.value });
1918
};
2019
const onReadChange = (event: ChangeEvent<HTMLInputElement>) => {
21-
onChange({ ...query, type: 'Read', read: event.target.value });
20+
onChange({ ...query, type: 'read', read: event.target.value });
2221
};
2322

24-
const queryTypes = [
25-
{ label: 'Eval', value: 0, description: 'Evaluate an Axon expression' },
26-
{ label: 'HisRead', value: 1, description: 'Read the history of a point' },
27-
{ label: 'Read', value: 2, description: 'Read the records matched by a filter' },
23+
interface QueryType extends SelectableValue<string> {
24+
apiRequirements: string[];
25+
}
26+
27+
const queryTypes: QueryType[] = [
28+
{ label: 'Read', value: "read", apiRequirements: ["read"], description: 'Read the records matched by a filter' },
29+
{ label: 'HisRead', value: "hisRead", apiRequirements: ["hisRead"], description: 'Read the history of a point' },
30+
{ label: 'Eval', value: "eval", apiRequirements: ["eval"], description: 'Evaluate an Axon expression' },
2831
];
2932
const queryTypeDefault = queryTypes[0];
3033
function queryTypeFromLabel(label: string) {
31-
return queryTypes.find((queryType) => queryType.label === label) ?? queryTypeDefault;
34+
return queryTypes.find((queryType) => queryType.value === label);
3235
}
3336

3437
const SelectComponent = () => {
3538
return (
36-
<Field>
37-
<Select
38-
options={queryTypes}
39+
<InlineField label="Type">
40+
<AsyncSelect
41+
loadOptions={loadOps}
42+
defaultOptions
3943
value={queryTypeFromLabel(query.type)}
40-
defaultValue={queryTypeDefault}
4144
width={30}
4245
onChange={(queryType) => {
4346
onTypeChange(queryType);
4447
}}
4548
/>
46-
</Field>
49+
</InlineField>
4750
);
4851
};
4952

53+
// Queries the available ops from the datasource on only returns the ones that are supported.
54+
const loadOps = () => {
55+
let opsRequest: DataQueryRequest<HaystackQuery> = {
56+
requestId: 'ops',
57+
dashboardId: 0,
58+
interval: '0',
59+
intervalMs: 0,
60+
panelId: 0,
61+
range: range ?? getDefaultTimeRange(),
62+
scopedVars: {},
63+
targets: [{ type: 'ops' , eval: "", read: "", hisRead: "", refId: query.refId}],
64+
timezone: 'UTC',
65+
app: 'ops',
66+
startTime: 0,
67+
}
68+
return datasource.query(opsRequest).toPromise().then((result) => {
69+
if(result?.state === 'Error') {
70+
return [];
71+
}
72+
let frame = result?.data?.find((frame: DataFrame) => {
73+
return frame.refId === query.refId
74+
})
75+
let opSymbols = frame?.fields?.find((field: Field<any, Vector<string>>) => {
76+
return field.name === 'def'
77+
}).values ?? [];
78+
let ops: string[] = opSymbols.map((opSymbol: string) => {
79+
if (opSymbol.startsWith('^op:')) {
80+
return opSymbol.substring(4);
81+
} else {
82+
return opSymbol;
83+
}
84+
});
85+
86+
return queryTypes.filter((queryType) => {
87+
return queryType.apiRequirements.every((apiRequirement) => {
88+
return ops.find((op) => {
89+
return op === apiRequirement
90+
}) !== undefined;
91+
});
92+
});
93+
});
94+
}
95+
5096
function renderQuery(): ReactNode {
97+
let width = 100;
5198
let queryType = queryTypeFromLabel(query.type);
52-
switch (queryType.value) {
53-
case 0: // Eval
99+
switch (queryType?.value) {
100+
case "eval":
54101
return (
55-
<Field>
102+
<InlineField>
56103
<Input
57-
width={100}
104+
width={width}
58105
prefix={<Icon name="angle-right" />}
59106
onChange={onEvalChange}
60107
value={query.eval}
61108
placeholder={DEFAULT_QUERY.eval}
62109
/>
63-
</Field>
110+
</InlineField>
64111
);
65-
case 1: // HisRead
112+
case "hisRead":
66113
return (
67-
<Field>
114+
<InlineField>
68115
<Input
69-
width={30}
116+
width={width}
70117
prefix={'@'}
71118
onChange={onHisReadChange}
72119
value={query.hisRead}
73120
placeholder={DEFAULT_QUERY.hisRead}
74121
/>
75-
</Field>
122+
</InlineField>
76123
);
77-
case 2: // Read
124+
case "read":
78125
return (
79-
<Field>
126+
<InlineField>
80127
<Input
81-
width={75}
128+
width={width}
82129
prefix={<Icon name="filter" />}
83130
onChange={onReadChange}
84131
value={query.read}
85132
placeholder={DEFAULT_QUERY.read}
86133
/>
87-
</Field>
134+
</InlineField>
88135
);
89136
}
90137
return <p>Select a query type</p>;
91138
}
92139

93140
function onSubmit(newQuery: Partial<HaystackQuery>) {
94141
query = { ...query, ...newQuery };
142+
console.info('onSubmit', query);
95143
onRunQuery();
96144
}
97145

@@ -100,11 +148,11 @@ export function QueryEditor({ query, onChange, onRunQuery }: Props) {
100148
<Form onSubmit={onSubmit}>
101149
{({ register, errors }) => {
102150
return (
103-
<div>
151+
<VerticalGroup>
104152
<SelectComponent />
105153
{renderQuery()}
106-
<Button type="submit">Run</Button>
107-
</div>
154+
<Button type="submit" >Run</Button>
155+
</VerticalGroup>
108156
);
109157
}}
110158
</Form>

src/datasource.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class DataSource extends DataSourceWithBackend<HaystackQuery, HaystackDat
2929
async metricFindQuery(query: HaystackVariableQuery, options?: any) {
3030
let request: HaystackQuery = {
3131
refId: 'VariableQuery',
32-
type: 'Eval',
32+
type: 'eval',
3333
eval: query.eval,
3434
hisRead: '',
3535
read: '',

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface HaystackVariableQuery {
1313
}
1414

1515
export const DEFAULT_QUERY: Partial<HaystackQuery> = {
16-
type: 'Eval',
16+
type: 'eval',
1717
eval: '[{ts: $__timeRange_start, v0: 0}, {ts: $__timeRange_end, v0: 10}].toGrid',
1818
hisRead: 'abcdef-123456',
1919
read: 'point and temp and air and outside',

0 commit comments

Comments
 (0)