Skip to content

Commit 03ad6cc

Browse files
refactor: Cleans up HaystackVariableSupport
This reworks the HaystackVariableSupport to align with the Loki plugin's use of CustomVariableSupport: https://github.com/grafana/grafana/blob/15293a2ceb083108c0f15490933db523aa915c7d/public/app/plugins/datasource/loki/LokiVariableSupport.ts#L10 Since I didn't really use any source example when implementing it initially, it ended up being kinda messy.
1 parent d975ab3 commit 03ad6cc

File tree

3 files changed

+113
-107
lines changed

3 files changed

+113
-107
lines changed

src/HaystackVariableSupport.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import {
2+
CustomVariableSupport,
3+
DataQueryRequest,
4+
DataQueryResponse,
5+
DataFrame,
6+
Field,
7+
FieldType,
8+
MetricFindValue,
9+
} from '@grafana/data';
10+
11+
import { Observable } from 'rxjs';
12+
import { map } from 'rxjs/operators';
13+
14+
import { HaystackVariableQueryEditor } from './components/HaystackVariableQueryEditor';
15+
import { DataSource } from './datasource';
16+
import { isRef, parseRef } from 'haystack';
17+
import { HaystackVariableQuery } from './types';
18+
19+
export class HaystackVariableSupport extends CustomVariableSupport<DataSource, HaystackVariableQuery> {
20+
editor = HaystackVariableQueryEditor;
21+
22+
constructor(private datasource: DataSource) {
23+
super();
24+
}
25+
26+
query(request: DataQueryRequest<HaystackVariableQuery>): Observable<DataQueryResponse> {
27+
let variableQuery = request.targets[0];
28+
let observable = this.datasource.query(request);
29+
return observable.pipe(
30+
map((response) => {
31+
if (response === undefined || response.errors !== undefined || response.data === undefined) {
32+
return response;
33+
}
34+
35+
let variableValues = response.data.reduce((acc: MetricFindValue[], frame: DataFrame) => {
36+
// Default to the first field
37+
let column = frame.fields[0];
38+
if (variableQuery.column !== undefined && variableQuery.column !== '') {
39+
// If a column was input, match the column name
40+
column = frame.fields.find((field: Field) => field.name === variableQuery.column) ?? column;
41+
} else if (frame.fields.some((field: Field) => field.name === 'id')) {
42+
// If there is an id column, use that
43+
column = frame.fields.find((field: Field) => field.name === 'id') ?? column;
44+
}
45+
46+
// Default to the selected column
47+
let displayColumn = column;
48+
if (variableQuery.displayColumn !== undefined && variableQuery.displayColumn !== '') {
49+
// If a column was input, match the column name
50+
displayColumn =
51+
frame.fields.find((field: Field) => {
52+
return field.name === variableQuery.displayColumn;
53+
}) ?? displayColumn;
54+
}
55+
56+
let variableValues = column.values.map((value, index) => {
57+
let variableValue = variableValueFromCell(value, column.type);
58+
59+
let displayValue = displayColumn.values[index];
60+
let variableText = variableTextFromCell(displayValue, displayColumn.type);
61+
62+
return { text: variableText, value: variableValue };
63+
});
64+
return acc.concat(variableValues);
65+
}, []);
66+
return { ...response, data: variableValues };
67+
})
68+
);
69+
}
70+
}
71+
72+
function variableValueFromCell(value: string, columnType: FieldType): string {
73+
switch (columnType) {
74+
case FieldType.string:
75+
if (isRef(value)) {
76+
return parseRef(value).id;
77+
}
78+
}
79+
return value;
80+
}
81+
82+
function variableTextFromCell(value: string, columnType: FieldType): string {
83+
switch (columnType) {
84+
case FieldType.string:
85+
if (isRef(value)) {
86+
let ref = parseRef(value);
87+
return ref.dis ?? ref.id;
88+
}
89+
}
90+
return value;
91+
}

src/components/VariableQueryEditor.tsx renamed to src/components/HaystackVariableQueryEditor.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import { QueryEditorProps } from '@grafana/data';
66
import { InlineField, Input, Stack } from '@grafana/ui';
77
import { DataSource } from 'datasource';
88

9-
type Props = QueryEditorProps<DataSource, HaystackQuery, HaystackDataSourceOptions, HaystackVariableQuery>;
9+
export type Props = QueryEditorProps<DataSource, HaystackQuery, HaystackDataSourceOptions, HaystackVariableQuery>;
1010

11-
export const VariableQueryEditor = ({ onChange, query }: Props) => {
11+
const refId = 'HaystackVariableQueryEditor-VariableQuery';
12+
13+
export const HaystackVariableQueryEditor = ({ onChange, query }: Props) => {
1214
let variableInputWidth = 30;
1315

1416
// Computes the query string and calls the onChange function. Should be used instead of onChange for all mutating functions.
@@ -42,22 +44,28 @@ export const VariableQueryEditor = ({ onChange, query }: Props) => {
4244

4345
const onQueryChange = (newQuery: string) => {
4446
if (query.type === "eval") {
45-
onChangeAndSave({ ...query, eval: newQuery });
47+
onChangeAndSave({ ...query, refId: refId, eval: newQuery });
4648
} else if (query.type === "hisRead") {
47-
onChangeAndSave({ ...query, hisRead: newQuery });
49+
onChangeAndSave({ ...query, refId: refId, hisRead: newQuery });
4850
} else if (query.type === "hisReadFilter") {
49-
onChangeAndSave({ ...query, hisReadFilter: newQuery });
51+
onChangeAndSave({ ...query, refId: refId, hisReadFilter: newQuery });
5052
} else if (query.type === "read") {
51-
onChangeAndSave({ ...query, read: newQuery });
53+
onChangeAndSave({ ...query, refId: refId, read: newQuery });
5254
}
5355
};
5456

5557
const onColumnChange = (event: React.FormEvent<HTMLInputElement>) => {
56-
onChangeAndSave({...query, column: event.currentTarget.value,});
58+
onChangeAndSave({...query, column: event.currentTarget.value });
5759
};
5860

5961
const onDisplayColumnChange = (event: React.FormEvent<HTMLInputElement>) => {
60-
onChangeAndSave({...query, displayColumn: event.currentTarget.value,});
62+
onChangeAndSave({...query, displayColumn: event.currentTarget.value });
63+
};
64+
65+
const handleBlur = () => {
66+
if (query.query !== undefined && query.query !== "") {
67+
onChange({ ...query, refId: refId });
68+
}
6169
};
6270

6371
return (
@@ -78,6 +86,7 @@ export const VariableQueryEditor = ({ onChange, query }: Props) => {
7886
<InlineField label="Column">
7987
<Input
8088
width={variableInputWidth}
89+
onBlur={handleBlur}
8190
onChange={onColumnChange}
8291
value={query.column}
8392
placeholder="Defaults to 'id' or first column"
@@ -86,6 +95,7 @@ export const VariableQueryEditor = ({ onChange, query }: Props) => {
8695
<InlineField label="Display Column">
8796
<Input
8897
width={variableInputWidth}
98+
onBlur={handleBlur}
8999
onChange={onDisplayColumnChange}
90100
value={query.displayColumn}
91101
placeholder="Defaults to 'Column'"

src/datasource.ts

Lines changed: 4 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,13 @@ import {
44
DataQueryRequest,
55
DataFrame,
66
Field,
7-
MetricFindValue,
87
getDefaultTimeRange,
9-
FieldType,
10-
CustomVariableSupport,
11-
DataQueryResponse,
12-
QueryEditorProps,
138
} from '@grafana/data';
149
import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime';
1510

16-
import { HaystackQuery, OpsQuery, HaystackDataSourceOptions, HaystackVariableQuery, QueryType } from './types';
17-
import { firstValueFrom, map, Observable } from 'rxjs';
18-
import { isRef, parseRef } from 'haystack';
19-
import { ComponentType } from 'react';
20-
import { VariableQueryEditor } from 'components/VariableQueryEditor';
11+
import { HaystackQuery, OpsQuery, HaystackDataSourceOptions, QueryType } from './types';
12+
import { firstValueFrom } from 'rxjs';
13+
import { HaystackVariableSupport } from 'HaystackVariableSupport';
2114

2215
export const queryTypes: QueryType[] = [
2316
{ label: 'Eval', value: 'eval', apiRequirements: ['eval'], description: 'Evaluate an Axon expression' },
@@ -34,9 +27,7 @@ export const queryTypes: QueryType[] = [
3427
export class DataSource extends DataSourceWithBackend<HaystackQuery, HaystackDataSourceOptions> {
3528
constructor(instanceSettings: DataSourceInstanceSettings<HaystackDataSourceOptions>) {
3629
super(instanceSettings);
37-
this.variables = new HaystackVariableSupport((request) => {
38-
return this.query(request);
39-
});
30+
this.variables = new HaystackVariableSupport(this);
4031
}
4132

4233
// Queries the available ops from the datasource and returns the queryTypes that are supported.
@@ -115,89 +106,3 @@ export class DataSource extends DataSourceWithBackend<HaystackQuery, HaystackDat
115106
};
116107
}
117108
}
118-
119-
export class HaystackVariableSupport extends CustomVariableSupport<
120-
DataSource,
121-
HaystackVariableQuery,
122-
HaystackQuery,
123-
HaystackDataSourceOptions
124-
> {
125-
editor: ComponentType<QueryEditorProps<DataSource, HaystackQuery, HaystackDataSourceOptions, HaystackVariableQuery>>;
126-
127-
// Requests data from the backend. This allows this class to reuse the DataSource.query method to get data.
128-
onQuery: (request: DataQueryRequest<HaystackVariableQuery>) => Observable<DataQueryResponse>;
129-
130-
constructor(onQuery: (request: DataQueryRequest<HaystackVariableQuery>) => Observable<DataQueryResponse>) {
131-
super();
132-
this.editor = VariableQueryEditor;
133-
this.onQuery = onQuery;
134-
}
135-
136-
query(request: DataQueryRequest<HaystackVariableQuery>): Observable<DataQueryResponse> {
137-
let variableQuery = request.targets[0];
138-
// Setting the refId is required for Grafana to associate the response with the request.
139-
variableQuery.refId = 'HaystackVariableQuery';
140-
let observable = this.onQuery(request);
141-
return observable.pipe(
142-
map((response) => {
143-
if (response === undefined || response.errors !== undefined || response.data === undefined) {
144-
return response;
145-
}
146-
147-
let variableValues = response.data.reduce((acc: MetricFindValue[], frame: DataFrame) => {
148-
// Default to the first field
149-
let column = frame.fields[0];
150-
if (variableQuery.column !== undefined && variableQuery.column !== '') {
151-
// If a column was input, match the column name
152-
column = frame.fields.find((field: Field) => field.name === variableQuery.column) ?? column;
153-
} else if (frame.fields.some((field: Field) => field.name === 'id')) {
154-
// If there is an id column, use that
155-
column = frame.fields.find((field: Field) => field.name === 'id') ?? column;
156-
}
157-
158-
// Default to the selected column
159-
let displayColumn = column;
160-
if (variableQuery.displayColumn !== undefined && variableQuery.displayColumn !== '') {
161-
// If a column was input, match the column name
162-
displayColumn =
163-
frame.fields.find((field: Field) => {
164-
return field.name === variableQuery.displayColumn;
165-
}) ?? displayColumn;
166-
}
167-
168-
let variableValues = column.values.map((value, index) => {
169-
let variableValue = variableValueFromCell(value, column.type);
170-
171-
let displayValue = displayColumn.values[index];
172-
let variableText = variableTextFromCell(displayValue, displayColumn.type);
173-
174-
return { text: variableText, value: variableValue };
175-
});
176-
return acc.concat(variableValues);
177-
}, []);
178-
return { ...response, data: variableValues };
179-
})
180-
);
181-
}
182-
}
183-
184-
function variableValueFromCell(value: string, columnType: FieldType): string {
185-
switch (columnType) {
186-
case FieldType.string:
187-
if (isRef(value)) {
188-
return parseRef(value).id;
189-
}
190-
}
191-
return value;
192-
}
193-
194-
function variableTextFromCell(value: string, columnType: FieldType): string {
195-
switch (columnType) {
196-
case FieldType.string:
197-
if (isRef(value)) {
198-
let ref = parseRef(value);
199-
return ref.dis ?? ref.id;
200-
}
201-
}
202-
return value;
203-
}

0 commit comments

Comments
 (0)