|
1 |
| -import defaults from 'lodash/defaults'; |
2 |
| -import React, { useState } from 'react'; |
3 |
| -import { InlineFieldRow, InlineField, Segment, RadioButtonGroup, CodeEditor, useTheme, InfoBox } from '@grafana/ui'; |
| 1 | +import React from 'react'; |
4 | 2 | import { QueryEditorProps } from '@grafana/data';
|
5 |
| -import { JsonApiQuery, JsonApiDataSourceOptions, defaultQuery } from '../types'; |
6 |
| -import { KeyValueEditor } from './KeyValueEditor'; |
7 |
| -import AutoSizer from 'react-virtualized-auto-sizer'; |
8 |
| -import { css } from 'emotion'; |
9 |
| -import { Pair } from '../types'; |
| 3 | +import { JsonApiQuery, JsonApiDataSourceOptions } from '../types'; |
10 | 4 | import { JsonDataSource } from 'datasource';
|
| 5 | +import { TabbedQueryEditor } from './TabbedQueryEditor'; |
11 | 6 | import { FieldEditor } from './FieldEditor';
|
12 |
| -import { PathEditor } from './PathEditor'; |
13 | 7 | import { ExperimentalEditor } from './ExperimentalEditor';
|
14 | 8 |
|
15 |
| -// Display a warning message when user adds any of the following headers. |
16 |
| -const sensitiveHeaders = ['authorization', 'proxy-authorization', 'x-api-key']; |
17 |
| - |
18 | 9 | interface Props extends QueryEditorProps<JsonDataSource, JsonApiQuery, JsonApiDataSourceOptions> {
|
19 | 10 | limitFields?: number;
|
20 | 11 | editorContext?: string;
|
21 | 12 | }
|
22 | 13 |
|
23 |
| -export const QueryEditor: React.FC<Props> = ({ |
24 |
| - onRunQuery, |
25 |
| - onChange, |
26 |
| - limitFields, |
27 |
| - datasource, |
28 |
| - range, |
29 |
| - editorContext = 'default', |
30 |
| - ...props |
31 |
| -}) => { |
32 |
| - const [bodyType, setBodyType] = useState('plaintext'); |
33 |
| - const [tabIndex, setTabIndex] = useState(0); |
34 |
| - const theme = useTheme(); |
35 |
| - |
36 |
| - const query = defaults(props.query, defaultQuery); |
37 |
| - |
38 |
| - // const onMethodChange = (method: string) => { |
39 |
| - // onChange({ ...query, method }); |
40 |
| - // onRunQuery(); |
41 |
| - // }; |
42 |
| - |
43 |
| - const onBodyChange = (body: string) => { |
44 |
| - onChange({ ...query, body }); |
45 |
| - onRunQuery(); |
46 |
| - }; |
47 |
| - |
48 |
| - const onParamsChange = (params: Array<Pair<string, string>>) => { |
49 |
| - onChange({ ...query, params }); |
50 |
| - onRunQuery(); |
51 |
| - }; |
| 14 | +export const QueryEditor: React.FC<Props> = (props) => { |
| 15 | + const { query, editorContext, onChange, onRunQuery } = props; |
52 | 16 |
|
53 |
| - const onHeadersChange = (headers: Array<Pair<string, string>>) => { |
54 |
| - onChange({ ...query, headers }); |
55 |
| - onRunQuery(); |
56 |
| - }; |
57 |
| - |
58 |
| - const tabs = [ |
59 |
| - { |
60 |
| - title: 'Fields', |
61 |
| - content: query.fields && ( |
| 17 | + return ( |
| 18 | + <TabbedQueryEditor |
| 19 | + {...props} |
| 20 | + editorContext={editorContext || 'default'} |
| 21 | + fieldsTab={ |
62 | 22 | <FieldEditor
|
63 | 23 | value={query.fields}
|
64 | 24 | onChange={(value) => {
|
65 | 25 | onChange({ ...query, fields: value });
|
66 | 26 | onRunQuery();
|
67 | 27 | }}
|
68 |
| - limit={limitFields} |
69 |
| - onComplete={() => datasource.metadataRequest(query, range)} |
70 |
| - /> |
71 |
| - ), |
72 |
| - }, |
73 |
| - { |
74 |
| - title: 'Path', |
75 |
| - content: ( |
76 |
| - <PathEditor |
77 |
| - method={query.method ?? 'GET'} |
78 |
| - onMethodChange={(method) => { |
79 |
| - onChange({ ...query, method }); |
80 |
| - onRunQuery(); |
81 |
| - }} |
82 |
| - path={query.urlPath ?? ''} |
83 |
| - onPathChange={(path) => { |
84 |
| - onChange({ ...query, urlPath: path }); |
85 |
| - onRunQuery(); |
86 |
| - }} |
| 28 | + limit={props.limitFields} |
| 29 | + onComplete={() => props.datasource.metadataRequest(props.query, props.range)} |
87 | 30 | />
|
88 |
| - ), |
89 |
| - }, |
90 |
| - { |
91 |
| - title: 'Params', |
92 |
| - content: ( |
93 |
| - <KeyValueEditor |
94 |
| - addRowLabel={'Add param'} |
95 |
| - columns={['Key', 'Value']} |
96 |
| - values={query.params ?? []} |
97 |
| - onChange={onParamsChange} |
98 |
| - onBlur={() => onRunQuery()} |
| 31 | + } |
| 32 | + experimentalTab={ |
| 33 | + <ExperimentalEditor |
| 34 | + query={query} |
| 35 | + onChange={onChange} |
| 36 | + onRunQuery={onRunQuery} |
| 37 | + editorContext={editorContext || 'default'} |
99 | 38 | />
|
100 |
| - ), |
101 |
| - }, |
102 |
| - { |
103 |
| - title: 'Headers', |
104 |
| - content: ( |
105 |
| - <KeyValueEditor |
106 |
| - addRowLabel={'Add header'} |
107 |
| - columns={['Key', 'Value']} |
108 |
| - values={query.headers ?? []} |
109 |
| - onChange={onHeadersChange} |
110 |
| - onBlur={() => onRunQuery()} |
111 |
| - /> |
112 |
| - ), |
113 |
| - }, |
114 |
| - { |
115 |
| - title: 'Body', |
116 |
| - content: ( |
117 |
| - <> |
118 |
| - <InlineFieldRow> |
119 |
| - <InlineField label="Syntax highlighting"> |
120 |
| - <RadioButtonGroup |
121 |
| - value={bodyType} |
122 |
| - onChange={(v) => setBodyType(v ?? 'plaintext')} |
123 |
| - options={[ |
124 |
| - { label: 'Text', value: 'plaintext' }, |
125 |
| - { label: 'JSON', value: 'json' }, |
126 |
| - { label: 'XML', value: 'xml' }, |
127 |
| - ]} |
128 |
| - /> |
129 |
| - </InlineField> |
130 |
| - </InlineFieldRow> |
131 |
| - <InlineFieldRow> |
132 |
| - <AutoSizer |
133 |
| - disableHeight |
134 |
| - className={css` |
135 |
| - margin-bottom: ${theme.spacing.sm}; |
136 |
| - `} |
137 |
| - > |
138 |
| - {({ width }) => ( |
139 |
| - <CodeEditor |
140 |
| - value={query.body || ''} |
141 |
| - language={bodyType} |
142 |
| - width={width} |
143 |
| - height="200px" |
144 |
| - showMiniMap={false} |
145 |
| - showLineNumbers={true} |
146 |
| - onBlur={onBodyChange} |
147 |
| - /> |
148 |
| - )} |
149 |
| - </AutoSizer> |
150 |
| - </InlineFieldRow> |
151 |
| - </> |
152 |
| - ), |
153 |
| - }, |
154 |
| - { |
155 |
| - title: 'Experimental', |
156 |
| - content: ( |
157 |
| - <ExperimentalEditor query={query} onChange={onChange} onRunQuery={onRunQuery} editorContext={editorContext} /> |
158 |
| - ), |
159 |
| - }, |
160 |
| - ]; |
161 |
| - |
162 |
| - return ( |
163 |
| - <> |
164 |
| - <InlineFieldRow> |
165 |
| - <InlineField> |
166 |
| - <RadioButtonGroup |
167 |
| - onChange={(e) => setTabIndex(e ?? 0)} |
168 |
| - value={tabIndex} |
169 |
| - options={tabs.map((tab, idx) => ({ label: tab.title, value: idx }))} |
170 |
| - /> |
171 |
| - </InlineField> |
172 |
| - <InlineField |
173 |
| - label="Cache Time" |
174 |
| - tooltip="Time in seconds that the response will be cached in Grafana after receiving it." |
175 |
| - > |
176 |
| - <Segment |
177 |
| - value={{ label: formatCacheTimeLabel(query.cacheDurationSeconds), value: query.cacheDurationSeconds }} |
178 |
| - options={[0, 5, 10, 30, 60, 60 * 2, 60 * 5, 60 * 10, 60 * 30, 3600, 3600 * 2, 3600 * 5].map((value) => ({ |
179 |
| - label: formatCacheTimeLabel(value), |
180 |
| - value, |
181 |
| - description: value ? '' : 'Response is not cached at all', |
182 |
| - }))} |
183 |
| - onChange={({ value }) => onChange({ ...query, cacheDurationSeconds: value! })} |
184 |
| - /> |
185 |
| - </InlineField> |
186 |
| - </InlineFieldRow> |
187 |
| - {query.method === 'GET' && query.body && ( |
188 |
| - <InfoBox severity="warning" style={{ maxWidth: '700px', whiteSpace: 'normal' }}> |
189 |
| - {"GET requests can't have a body. The body you've defined will be ignored."} |
190 |
| - </InfoBox> |
191 |
| - )} |
192 |
| - {(query.headers ?? []).map(([key, _]) => key.toLowerCase()).find((_) => sensitiveHeaders.includes(_)) && ( |
193 |
| - <InfoBox severity="warning" style={{ maxWidth: '700px', whiteSpace: 'normal' }}> |
194 |
| - { |
195 |
| - "It looks like you're adding credentials in the header. Since queries are stored unencrypted, it's strongly recommended that you add any secrets to the data source config instead." |
196 |
| - } |
197 |
| - </InfoBox> |
198 |
| - )} |
199 |
| - {tabs[tabIndex].content} |
200 |
| - </> |
| 39 | + } |
| 40 | + /> |
201 | 41 | );
|
202 | 42 | };
|
203 |
| - |
204 |
| -export const formatCacheTimeLabel = (s: number) => { |
205 |
| - if (s < 60) { |
206 |
| - return s + 's'; |
207 |
| - } else if (s < 3600) { |
208 |
| - return s / 60 + 'm'; |
209 |
| - } |
210 |
| - |
211 |
| - return s / 3600 + 'h'; |
212 |
| -}; |
0 commit comments