Skip to content

Commit 9f748ed

Browse files
Merge pull request #10 from NeedleInAJayStack/feature/noneval-variable-support
Feature/noneval variable support
2 parents e665410 + e9283a2 commit 9f748ed

File tree

8 files changed

+251
-168
lines changed

8 files changed

+251
-168
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "haystack-datasource",
3-
"version": "0.0.1",
3+
"version": "0.0.3",
44
"description": "A Grafana data source for Haystack tagged data",
55
"scripts": {
66
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production",

src/README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,8 @@ To use them, simply enter the value in the input string. Below is an example of
4949

5050
### Query Variables
5151

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.
52+
You can use the Haystack connector to source variables. Create a query and then enter the column that contains the
53+
variable values. If no column is specified, the first one is used.
5554

5655
The value injected by the variable exactly matches the displayed value, with the exception of Ref types, where the
5756
injected value is only the ID portion (i.e. the dis name is not included in the interpolation). Multiple-select values
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Icon, InlineField, Input } from '@grafana/ui';
2+
import React, { ChangeEvent } from 'react';
3+
import { DEFAULT_QUERY, HaystackQuery } from 'types';
4+
5+
export interface HaystackQueryInputProps {
6+
query: HaystackQuery;
7+
onChange: (query: string) => void;
8+
}
9+
10+
export function HaystackQueryInput({ query, onChange }: HaystackQueryInputProps) {
11+
const onQueryChange = (event: ChangeEvent<HTMLInputElement>) => {
12+
onChange(event.target.value);
13+
};
14+
15+
let width = 100;
16+
switch (query.type) {
17+
case "eval":
18+
return (
19+
<InlineField>
20+
<Input
21+
width={width}
22+
prefix={<Icon name="angle-right" />}
23+
onChange={onQueryChange}
24+
value={query.eval}
25+
placeholder={DEFAULT_QUERY.eval}
26+
/>
27+
</InlineField>
28+
);
29+
case "hisRead":
30+
return (
31+
<InlineField>
32+
<Input
33+
width={width}
34+
prefix={'@'}
35+
onChange={onQueryChange}
36+
value={query.hisRead}
37+
placeholder={DEFAULT_QUERY.hisRead}
38+
/>
39+
</InlineField>
40+
);
41+
case "read":
42+
return (
43+
<InlineField>
44+
<Input
45+
width={width}
46+
prefix={<Icon name="filter" />}
47+
onChange={onQueryChange}
48+
value={query.read}
49+
placeholder={DEFAULT_QUERY.read}
50+
/>
51+
</InlineField>
52+
);
53+
}
54+
return <p>Select a query type</p>;
55+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { AsyncSelect, InlineField } from '@grafana/ui';
2+
import React, { } from 'react';
3+
import { QueryType } from 'types';
4+
import { DataSource, queryTypes } from '../datasource';
5+
6+
export interface HaystackQueryTypeSelectorProps {
7+
datasource: DataSource | null;
8+
type: string;
9+
refId: string;
10+
onChange: (type: string) => void;
11+
}
12+
13+
export function HaystackQueryTypeSelector({ datasource, type, refId, onChange }: HaystackQueryTypeSelectorProps) {
14+
const onTypeChange = (event: QueryType | null) => {
15+
onChange(event?.value ?? queryTypeDefault.value!);
16+
};
17+
18+
const queryTypeDefault = queryTypes[0];
19+
function queryTypeFromValue(value: string): QueryType | null {
20+
return queryTypes.find((queryType) => queryType.value === value) ?? null;
21+
}
22+
23+
return (
24+
<InlineField label="Type">
25+
<AsyncSelect
26+
loadOptions={() => {return datasource?.loadOps(refId) ?? new Promise<QueryType[]>((resolve) => { resolve(queryTypes);})}}
27+
defaultOptions
28+
value={queryTypeFromValue(type)}
29+
width={30}
30+
onChange={(queryType) => {
31+
// QueryType comes back as a SelectableValue, so we just convert it to the QueryType
32+
onTypeChange(queryTypeFromValue(queryType.value ?? ""));
33+
}}
34+
/>
35+
</InlineField>
36+
);
37+
}

src/components/QueryEditor.tsx

Lines changed: 27 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,26 @@
1-
import React, { ChangeEvent, ReactNode } from 'react';
2-
import { AsyncSelect, Button, Form, Icon, InlineField, Input, VerticalGroup } from '@grafana/ui';
3-
import { DataFrame, DataQueryRequest, Field, getDefaultTimeRange, QueryEditorProps, SelectableValue, Vector } from '@grafana/data';
1+
import React, { } from 'react';
2+
import { Button, Form, VerticalGroup } from '@grafana/ui';
3+
import { QueryEditorProps } from '@grafana/data';
44
import { DataSource } from '../datasource';
5-
import { DEFAULT_QUERY, HaystackDataSourceOptions, HaystackQuery } from '../types';
5+
import { HaystackDataSourceOptions, HaystackQuery } from '../types';
6+
import { HaystackQueryTypeSelector } from './HaystackQueryTypeSelector';
7+
import { HaystackQueryInput } from './HaystackQueryInput';
68

79
type Props = QueryEditorProps<DataSource, HaystackQuery, HaystackDataSourceOptions>;
810

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! });
11+
export function QueryEditor({ datasource, query, onChange, onRunQuery }: Props) {
12+
const onTypeChange = (newType: string) => {
13+
onChange({ ...query, type: newType });
1214
};
13-
const onEvalChange = (event: ChangeEvent<HTMLInputElement>) => {
14-
onChange({ ...query, type: 'eval', eval: event.target.value });
15-
};
16-
const onHisReadChange = (event: ChangeEvent<HTMLInputElement>) => {
17-
onChange({ ...query, type: 'hisRead', hisRead: event.target.value });
18-
};
19-
const onReadChange = (event: ChangeEvent<HTMLInputElement>) => {
20-
onChange({ ...query, type: 'read', read: event.target.value });
21-
};
22-
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' },
31-
];
32-
const queryTypeDefault = queryTypes[0];
33-
function queryTypeFromLabel(label: string) {
34-
return queryTypes.find((queryType) => queryType.value === label);
35-
}
36-
37-
const SelectComponent = () => {
38-
return (
39-
<InlineField label="Type">
40-
<AsyncSelect
41-
loadOptions={loadOps}
42-
defaultOptions
43-
value={queryTypeFromLabel(query.type)}
44-
width={30}
45-
onChange={(queryType) => {
46-
onTypeChange(queryType);
47-
}}
48-
/>
49-
</InlineField>
50-
);
51-
};
52-
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,
15+
const onQueryChange = (newQuery: string) => {
16+
if (query.type === "hisRead") {
17+
onChange({ ...query, hisRead: newQuery });
18+
} else if (query.type === "eval") {
19+
onChange({ ...query, eval: newQuery });
20+
} else if (query.type === "read") {
21+
onChange({ ...query, read: newQuery });
6722
}
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-
96-
function renderQuery(): ReactNode {
97-
let width = 100;
98-
let queryType = queryTypeFromLabel(query.type);
99-
switch (queryType?.value) {
100-
case "eval":
101-
return (
102-
<InlineField>
103-
<Input
104-
width={width}
105-
prefix={<Icon name="angle-right" />}
106-
onChange={onEvalChange}
107-
value={query.eval}
108-
placeholder={DEFAULT_QUERY.eval}
109-
/>
110-
</InlineField>
111-
);
112-
case "hisRead":
113-
return (
114-
<InlineField>
115-
<Input
116-
width={width}
117-
prefix={'@'}
118-
onChange={onHisReadChange}
119-
value={query.hisRead}
120-
placeholder={DEFAULT_QUERY.hisRead}
121-
/>
122-
</InlineField>
123-
);
124-
case "read":
125-
return (
126-
<InlineField>
127-
<Input
128-
width={width}
129-
prefix={<Icon name="filter" />}
130-
onChange={onReadChange}
131-
value={query.read}
132-
placeholder={DEFAULT_QUERY.read}
133-
/>
134-
</InlineField>
135-
);
136-
}
137-
return <p>Select a query type</p>;
138-
}
23+
};
13924

14025
function onSubmit(newQuery: Partial<HaystackQuery>) {
14126
query = { ...query, ...newQuery };
@@ -149,8 +34,16 @@ export function QueryEditor({ datasource, query, onChange, onRunQuery, range, ap
14934
{({ register, errors }) => {
15035
return (
15136
<VerticalGroup>
152-
<SelectComponent />
153-
{renderQuery()}
37+
<HaystackQueryTypeSelector
38+
datasource={datasource}
39+
type={query.type}
40+
refId={query.refId}
41+
onChange={onTypeChange}
42+
/>
43+
<HaystackQueryInput
44+
query={query}
45+
onChange={onQueryChange}
46+
/>
15447
<Button type="submit" >Run</Button>
15548
</VerticalGroup>
15649
);
Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,85 @@
11
import React, { useState } from 'react';
2-
import { HaystackVariableQuery } from '../types';
2+
import { HaystackQuery, HaystackVariableQuery } from '../types';
3+
import { HaystackQueryTypeSelector } from './HaystackQueryTypeSelector';
4+
import { HaystackQueryInput } from './HaystackQueryInput';
35

46
interface VariableQueryProps {
57
query: HaystackVariableQuery;
68
onChange: (query: HaystackVariableQuery, definition: string) => void;
79
}
810

9-
export const VariableQueryEditor: React.FC<VariableQueryProps> = ({ onChange, query }) => {
10-
const [state, setState] = useState(query);
11+
const blankQuery: Partial<HaystackQuery> = {
12+
refId: "variable",
13+
type: '',
14+
eval: '',
15+
hisRead: '',
16+
read: '',
17+
};
18+
19+
export const VariableQueryEditor: React.FC<VariableQueryProps> = ({ onChange, query: variableQuery }) => {
20+
const [state, setState] = useState(variableQuery);
1121

1222
const saveQuery = () => {
13-
onChange(state, `Eval: ${state.eval} Column: ${state.column}`);
23+
let query = state.query ?? blankQuery
24+
let type = query.type;
25+
let queryCmd = "";
26+
if (query.type === "hisRead") {
27+
queryCmd = query.hisRead
28+
} else if (query.type === "eval") {
29+
queryCmd = query.eval
30+
} else if (query.type === "read") {
31+
queryCmd = query.read
32+
}
33+
let column = "none";
34+
if (state.column !== undefined && state.column !== '') {
35+
column = `'${state.column}'`;
36+
}
37+
onChange(state, `Type: '${type}' Query: '${queryCmd}' Column: ${column}`);
1438
};
1539

16-
const handleChange = (event: React.FormEvent<HTMLInputElement>) =>
17-
setState({
18-
...state,
19-
[event.currentTarget.name]: event.currentTarget.value,
20-
});
40+
const onTypeChange = (newType: string) => {
41+
let query = {...state.query ?? blankQuery};
42+
query.type = newType;
43+
setState({ ...state, query: query});
44+
};
45+
46+
const onQueryChange = (newQuery: string) => {
47+
let query = {...state.query ?? blankQuery};
48+
if (state.query.type === "hisRead") {
49+
query.hisRead = newQuery
50+
} else if (state.query.type === "eval") {
51+
query.eval = newQuery
52+
} else if (state.query.type === "read") {
53+
query.read = newQuery
54+
}
55+
setState({ ...state, query: query});
56+
};
57+
58+
const onColumnChange = (event: React.FormEvent<HTMLInputElement>) => {
59+
setState({...state, column: event.currentTarget.value,});
60+
};
2161

2262
return (
23-
<>
24-
<div className="gf-form">
25-
<span className="gf-form-label width-10">Eval</span>
26-
<input name="eval" className="gf-form-input" onBlur={saveQuery} onChange={handleChange} value={state.eval} />
27-
</div>
63+
<div onBlur={saveQuery}>
64+
<HaystackQueryTypeSelector
65+
datasource={null}
66+
type={state.query?.type ?? blankQuery.type}
67+
refId={state.query?.refId ?? blankQuery.refId}
68+
onChange={onTypeChange}
69+
/>
70+
<HaystackQueryInput
71+
query={state.query ?? blankQuery}
72+
onChange={onQueryChange}
73+
/>
2874
<div className="gf-form">
2975
<span className="gf-form-label width-10">Column</span>
3076
<input
3177
name="column"
3278
className="gf-form-input"
33-
onBlur={saveQuery}
34-
onChange={handleChange}
79+
onChange={onColumnChange}
3580
value={state.column}
3681
/>
3782
</div>
38-
</>
83+
</div>
3984
);
4085
};

0 commit comments

Comments
 (0)