Skip to content

Commit c3dbe33

Browse files
Merge pull request #4 from NeedleInAJayStack/feature/variable-support
Feature: Adds variable support
2 parents 2172fb5 + 210ed5e commit c3dbe33

8 files changed

Lines changed: 125 additions & 11 deletions

File tree

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ Contributions are very welcome! For details on how to develop this plugin, see t
2424

2525
## Continuing Work
2626

27-
* [ ] Check variable support
2827
* [ ] Add alert support
2928
* [ ] Publish plugin
3029
* [ ] Consider enabling multi-point hisRead (through filters)

pkg/plugin/datasource.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,9 @@ func dataFrameFromGrid(grid haystack.Grid) (*data.Frame, error) {
306306
case haystack.Str:
307307
value := val.String()
308308
values = append(values, &value)
309+
case haystack.Marker:
310+
value := "✓"
311+
values = append(values, &value)
309312
default:
310313
value := val.ToZinc()
311314
values = append(values, &value)

pkg/plugin/datasource_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func TestQueryData_Read(t *testing.T) {
100100

101101
idVal := "@abcdefg-12345678 \"AHU-1\""
102102
disVal := "AHU-1"
103-
ahuVal := "M"
103+
ahuVal := ""
104104
expected := data.NewFrame("",
105105
data.NewField("id", nil, []*string{&idVal}).SetConfig(&data.FieldConfig{DisplayName: "id"}),
106106
data.NewField("dis", nil, []*string{&disVal}).SetConfig(&data.FieldConfig{DisplayName: "dis"}),

src/README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,36 @@ one by selecting `+ New Dashboard` from the Dashboard menu in the left panel.
2424
Once within the panel editor, select your Haystack data source in the Data Sources menu. Next, select the type of
2525
Haystack query that should be performed. The supported queries are:
2626

27-
- Eval: Evaluate a free-form Axon expression. Grafana variables may be injected into the query, with the supported
28-
variables listed below. *Note: Not all Haystack servers support this functionality*
27+
- Eval: Evaluate a free-form Axon expression. *Note: Not all Haystack servers support this functionality*
2928
- HisRead: Display the history of a single point over the selected time range.
3029
- Read: Display the records matching a filter. Since this is not timeseries data, it can only be viewed in Grafana's
3130
"Table" view.
3231

33-
#### Variables
32+
#### Variable Usage
3433

35-
Some queries support injecting values from the Grafana UI. The following variables are supported:
34+
Grafana template variables can be injected into queries using the ordinary syntax, e.g. `$varName`.
35+
36+
We also support injecting a few special variables from the time-range selector into the Eval and Read requests:
3637

3738
- `$__timeRange_start`: DateTime start of the selected Grafana time range
3839
- `$__timeRange_end`: DateTime end of the selected Grafana time range
3940
- `$__maxDataPoints`: Number representing the pixel width of Grafana's display panel.
4041
- `$__interval`: Number representing Grafana's recommended data interval. This is the duration of the time range,
4142
divided by the number of pixels, delivered in units of minutes.
4243

43-
To use them, simply use the value in the input string. Below is an example of using the variables in an Eval query:
44+
To use them, simply enter the value in the input string. Below is an example of using the variables in an Eval query:
4445

4546
```
4647
> [{ts: $__timeRange_start, v0: 0}, {ts: $__timeRange_end, v0: 10}].toGrid
4748
```
49+
50+
### Query Variables
51+
52+
You can use the Haystack connector to source variables. Currently, only "Eval"-style variable queries are supported,
53+
where an Axon string is used to retrieve a grid and the column that contains the variable values is specified. If no
54+
column is specified, the first one is used.
55+
56+
The value injected by the variable exactly matches the displayed value, with the exception of Ref types, where the
57+
injected value is only the ID portion (i.e. the dis name is not included in the interpolation). Multiple-select values
58+
are combined with commas, (`red,blue`), but this may be customized using the
59+
[advanced variable format options](https://grafana.com/docs/grafana/latest/dashboards/variables/variable-syntax/#advanced-variable-format-options).
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React, { useState } from 'react';
2+
import { HaystackVariableQuery } from '../types';
3+
4+
interface VariableQueryProps {
5+
query: HaystackVariableQuery;
6+
onChange: (query: HaystackVariableQuery, definition: string) => void;
7+
}
8+
9+
export const VariableQueryEditor: React.FC<VariableQueryProps> = ({ onChange, query }) => {
10+
const [state, setState] = useState(query);
11+
12+
const saveQuery = () => {
13+
onChange(state, `Eval: ${state.eval} Column: ${state.column}`);
14+
};
15+
16+
const handleChange = (event: React.FormEvent<HTMLInputElement>) =>
17+
setState({
18+
...state,
19+
[event.currentTarget.name]: event.currentTarget.value,
20+
});
21+
22+
return (
23+
<>
24+
<div className="gf-form">
25+
<span className="gf-form-label width-10">Eval</span>
26+
<input
27+
name="eval"
28+
className="gf-form-input"
29+
onBlur={saveQuery}
30+
onChange={handleChange}
31+
value={state.eval}
32+
/>
33+
</div>
34+
<div className="gf-form">
35+
<span className="gf-form-label width-10">Column</span>
36+
<input
37+
name="column"
38+
className="gf-form-input"
39+
onBlur={saveQuery}
40+
onChange={handleChange}
41+
value={state.column}
42+
/>
43+
</div>
44+
</>
45+
);
46+
};

src/datasource.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,60 @@
1-
import { DataSourceInstanceSettings, CoreApp } from '@grafana/data';
2-
import { DataSourceWithBackend } from '@grafana/runtime';
1+
import { DataSourceInstanceSettings, CoreApp, ScopedVars, DataQueryRequest, DataFrame, Field, MetricFindValue } from '@grafana/data';
2+
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
33

4-
import { HaystackQuery, HaystackDataSourceOptions, DEFAULT_QUERY } from './types';
4+
import { HaystackQuery, HaystackDataSourceOptions, DEFAULT_QUERY, HaystackVariableQuery } from './types';
55

66
export class DataSource extends DataSourceWithBackend<HaystackQuery, HaystackDataSourceOptions> {
77
constructor(instanceSettings: DataSourceInstanceSettings<HaystackDataSourceOptions>) {
88
super(instanceSettings);
99
}
1010

11+
applyTemplateVariables(query: HaystackQuery, scopedVars: ScopedVars): Record<string, any> {
12+
return {
13+
...query,
14+
eval: getTemplateSrv().replace(query.eval, scopedVars, "csv"),
15+
hisRead: getTemplateSrv().replace(query.hisRead, scopedVars, "csv"),
16+
read: getTemplateSrv().replace(query.read, scopedVars, "csv"),
17+
};
18+
}
19+
20+
// This is called when the user is selecting a variable value
21+
async metricFindQuery(query: HaystackVariableQuery, options?: any) {
22+
let request: HaystackQuery = {
23+
refId: "VariableQuery",
24+
type: "Eval",
25+
eval: query.eval,
26+
hisRead: "",
27+
read: ""
28+
};
29+
let response = await this.query({ targets: [request] } as DataQueryRequest<HaystackQuery>).toPromise()
30+
31+
if (response === undefined || response.data === undefined) {
32+
return [];
33+
}
34+
35+
return response.data.reduce((acc: MetricFindValue[], frame: DataFrame) => {
36+
let field = frame.fields[0];
37+
if (query.column !== undefined && query.column !== "") {
38+
// If a column was input, match the column name
39+
field = frame.fields.find((field: Field) => field.name === query.column) ?? field;
40+
}
41+
42+
let fieldVals = field.values.toArray().map((value) => {
43+
if (value.startsWith("@")) {
44+
// Detect ref using @ prefix, and adjust value to just the Ref
45+
let spaceIndex = value.indexOf(" ");
46+
let id = value.substring(0, spaceIndex);
47+
return {text: value, value: id};
48+
} else {
49+
// Otherwise, just use the value directly
50+
return {text: value, value: value};
51+
}
52+
});
53+
return acc.concat(fieldVals);
54+
}, []);
55+
}
56+
57+
1158
getDefaultQuery(_: CoreApp): Partial<HaystackQuery> {
1259
return DEFAULT_QUERY
1360
}

src/module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { DataSourcePlugin } from '@grafana/data';
22
import { DataSource } from './datasource';
33
import { ConfigEditor } from './components/ConfigEditor';
44
import { QueryEditor } from './components/QueryEditor';
5+
import { VariableQueryEditor } from './components/VariableQueryEditor';
56
import { HaystackQuery, HaystackDataSourceOptions } from './types';
67

78
export const plugin = new DataSourcePlugin<DataSource, HaystackQuery, HaystackDataSourceOptions>(DataSource)
89
.setConfigEditor(ConfigEditor)
9-
.setQueryEditor(QueryEditor);
10+
.setQueryEditor(QueryEditor)
11+
.setVariableQueryEditor(VariableQueryEditor);

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ export interface HaystackQuery extends DataQuery {
77
read: string;
88
}
99

10+
export interface HaystackVariableQuery {
11+
column: string;
12+
eval: string;
13+
}
14+
1015
export const DEFAULT_QUERY: Partial<HaystackQuery> = {
1116
type: "Eval",
1217
eval: "[{ts: $__timeRange_start, v0: 0}, {ts: $__timeRange_end, v0: 10}].toGrid",

0 commit comments

Comments
 (0)