Skip to content

Commit 2b76483

Browse files
feature: Variable query support
1 parent 4416ab1 commit 2b76483

6 files changed

Lines changed: 109 additions & 9 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)

src/README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Haystack query that should be performed. The supported queries are:
2929
- Read: Display the records matching a filter. Since this is not timeseries data, it can only be viewed in Grafana's
3030
"Table" view.
3131

32-
#### Variables
32+
#### Variable Usage
3333

3434
Grafana template variables can be injected into queries using the ordinary syntax, e.g. `$varName`.
3535

@@ -46,3 +46,14 @@ To use them, simply enter the value in the input string. Below is an example of
4646
```
4747
> [{ts: $__timeRange_start, v0: 0}, {ts: $__timeRange_end, v0: 10}].toGrid
4848
```
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: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { DataSourceInstanceSettings, CoreApp, ScopedVars, DataQueryRequest, DataQueryResponse } from '@grafana/data';
1+
import { DataSourceInstanceSettings, CoreApp, ScopedVars, DataQueryRequest, DataFrame, Field, MetricFindValue } from '@grafana/data';
22
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
3-
import { Observable } from 'rxjs';
43

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

76
export class DataSource extends DataSourceWithBackend<HaystackQuery, HaystackDataSourceOptions> {
87
constructor(instanceSettings: DataSourceInstanceSettings<HaystackDataSourceOptions>) {
@@ -12,12 +11,50 @@ export class DataSource extends DataSourceWithBackend<HaystackQuery, HaystackDat
1211
applyTemplateVariables(query: HaystackQuery, scopedVars: ScopedVars): Record<string, any> {
1312
return {
1413
...query,
15-
eval: getTemplateSrv().replace(query.eval, scopedVars),
16-
hisRead: getTemplateSrv().replace(query.hisRead, scopedVars),
17-
read: getTemplateSrv().replace(query.read, scopedVars),
14+
eval: getTemplateSrv().replace(query.eval, scopedVars, "csv"),
15+
hisRead: getTemplateSrv().replace(query.hisRead, scopedVars, "csv"),
16+
read: getTemplateSrv().replace(query.read, scopedVars, "csv"),
1817
};
1918
}
2019

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+
2158
getDefaultQuery(_: CoreApp): Partial<HaystackQuery> {
2259
return DEFAULT_QUERY
2360
}

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)