Skip to content

Commit 6ab31e3

Browse files
committed
Extract tabbed editor into its own component
1 parent 37300f8 commit 6ab31e3

File tree

3 files changed

+215
-192
lines changed

3 files changed

+215
-192
lines changed

src/components/FieldEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface Props {
1313
value: JsonField[];
1414
}
1515

16-
export const FieldEditor = ({ value, onChange, limit, onComplete }: Props) => {
16+
export const FieldEditor = ({ value = [], onChange, limit, onComplete }: Props) => {
1717
const onChangePath = (i: number) => (e: string) => {
1818
onChange(value.map((field, n) => (i === n ? { ...value[i], jsonPath: e } : field)));
1919
};

src/components/QueryEditor.tsx

Lines changed: 21 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,212 +1,42 @@
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';
42
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';
104
import { JsonDataSource } from 'datasource';
5+
import { TabbedQueryEditor } from './TabbedQueryEditor';
116
import { FieldEditor } from './FieldEditor';
12-
import { PathEditor } from './PathEditor';
137
import { ExperimentalEditor } from './ExperimentalEditor';
148

15-
// Display a warning message when user adds any of the following headers.
16-
const sensitiveHeaders = ['authorization', 'proxy-authorization', 'x-api-key'];
17-
189
interface Props extends QueryEditorProps<JsonDataSource, JsonApiQuery, JsonApiDataSourceOptions> {
1910
limitFields?: number;
2011
editorContext?: string;
2112
}
2213

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;
5216

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={
6222
<FieldEditor
6323
value={query.fields}
6424
onChange={(value) => {
6525
onChange({ ...query, fields: value });
6626
onRunQuery();
6727
}}
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)}
8730
/>
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'}
9938
/>
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+
/>
20141
);
20242
};
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

Comments
 (0)