1
1
import React from 'react' ;
2
- import { ChangeEvent , FormEventHandler , useState } from 'react' ;
2
+ import { ChangeEvent , useState } from 'react' ;
3
3
import {
4
- Field ,
5
4
Button ,
6
5
InlineField ,
7
6
InlineFieldRow ,
8
7
Input ,
9
8
ControlledCollapse ,
10
9
InlineSwitch ,
11
- Stack ,
12
- FeatureBadge ,
13
- Switch ,
14
10
Modal ,
15
11
useTheme2 ,
16
12
Tooltip ,
17
13
} from '@grafana/ui' ;
18
14
import { EditorHeader , InlineSelect , FlexItem } from '@grafana/plugin-ui' ;
19
- import { CoreApp , FeatureState , QueryEditorProps , SelectableValue } from '@grafana/data' ;
15
+ import { CoreApp , QueryEditorProps , SelectableValue } from '@grafana/data' ;
20
16
import { DataSource } from '../datasource' ;
21
17
import { MongoDataSourceOptions , MongoQuery , QueryLanguage , QueryType , DEFAULT_QUERY } from '../types' ;
22
- import { parseJsQuery , parseJsQueryLegacy , validateJsonQueryText , validatePositiveNumber } from '../utils' ;
18
+ import { parseJsQuery , parseJsQueryLegacy } from '../utils' ;
23
19
import { QueryEditorRaw } from './QueryEditorRaw' ;
24
20
import { QueryToolbox } from './QueryToolbox' ;
25
21
import validator from 'validator' ;
@@ -47,24 +43,14 @@ const languageOptions: Array<SelectableValue<string>> = [
47
43
] ;
48
44
49
45
export function QueryEditor ( props : Props ) {
50
- const { query, onChange , app, onRunQuery } = props ;
46
+ const { query, app, onRunQuery } = props ;
51
47
52
48
const theme = useTheme2 ( ) ;
53
49
54
- const [ validationErrors , setValidationErrors ] = useState < { [ key : string ] : string } > ( { } ) ;
50
+ const [ queryTextError , setQueryTextError ] = useState < string | null > ( null ) ;
55
51
const [ isAggregateOptionExpanded , setIsAggregateOptionExpanded ] = useState ( false ) ;
56
52
const [ isEditorExpanded , setIsEditorExpanded ] = useState ( false ) ;
57
53
58
- const addValidationError = ( field : string , message : string ) => {
59
- setValidationErrors ( ( prev ) => ( { ...prev , [ field ] : message } ) ) ;
60
- } ;
61
-
62
- const removeValidationError = ( field : string ) => {
63
- setValidationErrors ( ( prev ) => {
64
- const { [ field ] : _ , ...rest } = prev ;
65
- return rest ;
66
- } ) ;
67
- } ;
68
54
69
55
const renderRunButton = ( isQueryRunnable : boolean ) => {
70
56
if ( isQueryRunnable ) {
@@ -93,7 +79,7 @@ export function QueryEditor(props: Props) {
93
79
value = { query . queryType }
94
80
placeholder = "Select format"
95
81
menuShouldPortal
96
- onChange = { ( val ) => onChange ( { ...query , queryType : val . value } ) }
82
+ onChange = { ( val ) => props . onChange ( { ...query , queryType : val . value } ) }
97
83
options = { queryTypes }
98
84
/>
99
85
< InlineSelect
@@ -102,7 +88,7 @@ export function QueryEditor(props: Props) {
102
88
placeholder = "Select query language"
103
89
options = { languageOptions }
104
90
value = { query . queryLanguage }
105
- onChange = { ( val ) => props . onChange ( { ...query , queryLanguage : val . value } ) }
91
+ onChange = { val => props . onChange ( { ...query , queryLanguage : val . value } ) }
106
92
/>
107
93
< FlexItem grow = { 1 } />
108
94
{ renderRunButton ( true ) }
@@ -115,7 +101,25 @@ export function QueryEditor(props: Props) {
115
101
? 'javascript'
116
102
: 'json'
117
103
}
118
- onBlur = { onQueryTextChange }
104
+ onBlur = { ( queryText : string ) => {
105
+ if ( query . queryLanguage === QueryLanguage . JAVASCRIPT || query . queryLanguage === QueryLanguage . JAVASCRIPT_SHADOW ) {
106
+ // parse the JavaScript query
107
+ const { error, collection } =
108
+ query . queryLanguage === QueryLanguage . JAVASCRIPT_SHADOW
109
+ ? parseJsQuery ( queryText )
110
+ : parseJsQueryLegacy ( queryText ) ;
111
+ // let the same query text as it is
112
+ props . onChange ( { ...query , queryText : queryText , ...( collection ? { collection } : { } ) } ) ;
113
+ setQueryTextError ( error ) ;
114
+ } else {
115
+ props . onChange ( { ...query , queryText : queryText } ) ;
116
+ if ( ! validator . isJSON ( queryText ) ) {
117
+ setQueryTextError ( "Query should be a valid JSON" )
118
+ } else {
119
+ setQueryTextError ( null ) ;
120
+ }
121
+ }
122
+ } }
119
123
width = { width }
120
124
height = { height }
121
125
fontSize = { 14 }
@@ -127,7 +131,7 @@ export function QueryEditor(props: Props) {
127
131
onExpand = { setIsEditorExpanded }
128
132
onFormatCode = { formatQuery }
129
133
showTools = { showTools }
130
- error = { validationErrors [ 'query' ] }
134
+ error = { queryTextError ?? undefined }
131
135
/>
132
136
) ;
133
137
} }
@@ -151,124 +155,38 @@ export function QueryEditor(props: Props) {
151
155
) ;
152
156
} ;
153
157
154
- const onQueryTextChange = ( queryText : string ) => {
155
- if ( query . queryLanguage === QueryLanguage . JAVASCRIPT || query . queryLanguage === QueryLanguage . JAVASCRIPT_SHADOW ) {
156
- // parse the JavaScript query
157
- const { error, collection } =
158
- query . queryLanguage === QueryLanguage . JAVASCRIPT_SHADOW
159
- ? parseJsQuery ( queryText )
160
- : parseJsQueryLegacy ( queryText ) ;
161
- // let the same query text as it is
162
- onChange ( { ...query , queryText : queryText , ...( collection ? { collection } : { } ) } ) ;
163
- addValidationError ( 'query' , error || '' ) ;
164
- } else {
165
- onChange ( { ...query , queryText : queryText } ) ;
166
- const error = validateJsonQueryText ( queryText ) ;
167
- addValidationError ( 'query' , error || '' ) ;
168
- }
169
- } ;
170
-
171
- const onCollectionChange = ( event : ChangeEvent < HTMLInputElement > ) => {
172
- onChange ( { ...query , collection : event . target . value } ) ;
173
- } ;
174
-
175
- const onMaxTimeMSChange = ( event : ChangeEvent < HTMLInputElement > ) => {
176
- setMaxTimeMSText ( event . target . value ) ;
177
-
178
- if ( ! event . target . value ) {
179
- onChange ( { ...query , aggregateMaxTimeMS : undefined } ) ;
180
- } else if ( validatePositiveNumber ( event . target . value ) ) {
181
- onChange ( { ...query , aggregateMaxTimeMS : parseInt ( event . target . value , 10 ) } ) ;
182
- }
183
- } ;
184
-
185
- const onMaxAwaitTimeMSChange = ( event : ChangeEvent < HTMLInputElement > ) => {
186
- setMaxAwaitTimeMSText ( event . target . value ) ;
187
- if ( ! event . target . value ) {
188
- onChange ( { ...query , aggregateMaxAwaitTime : undefined } ) ;
189
- } else if ( validatePositiveNumber ( event . target . value ) ) {
190
- onChange ( { ...query , aggregateMaxAwaitTime : parseInt ( event . target . value , 10 ) } ) ;
191
- }
192
- } ;
193
-
194
- const onAllowDiskUseChange = ( event : ChangeEvent < HTMLInputElement > ) => {
195
- onChange ( {
196
- ...query ,
197
- aggregateAllowDiskUse : event . target . checked ,
198
- } ) ;
199
- } ;
200
-
201
- const onBatchSizeChange = ( event : ChangeEvent < HTMLInputElement > ) => {
202
- setBatchSizeText ( event . target . value ) ;
203
- if ( ! event . target . value ) {
204
- onChange ( { ...query , aggregateBatchSize : undefined } ) ;
205
- } else if ( validatePositiveNumber ( event . target . value ) ) {
206
- onChange ( { ...query , aggregateBatchSize : parseInt ( event . target . value , 10 ) } ) ;
207
- }
208
- } ;
209
-
210
- const onBypassDocumentValidationChange = ( event : ChangeEvent < HTMLInputElement > ) => {
211
- onChange ( { ...query , aggregateBypassDocumentValidation : event . target . checked } ) ;
212
- } ;
213
-
214
- const onCommentChange = ( event : ChangeEvent < HTMLInputElement > ) => {
215
- onChange ( { ...query , aggregateComment : event . target . value } ) ;
216
- } ;
217
-
218
- const onIsStreamingChange : FormEventHandler < HTMLInputElement > = ( e ) => {
219
- onChange ( { ...query , isStreaming : e . currentTarget . checked } ) ;
220
- } ;
221
-
222
158
if ( ! query . queryLanguage ) {
223
159
query . queryLanguage = DEFAULT_QUERY . queryLanguage ;
224
160
}
225
161
226
162
return (
227
163
< >
228
- { app !== CoreApp . Explore && (
229
- < div className = "query-editor-collection-streaming-container" >
230
- < Field
231
- className = "query-editor-collection-streaming-field"
232
- label = {
233
- < >
234
- < Stack direction = "row" gap = { 1 } alignItems = "center" >
235
- < div className = "field-label" > Streaming</ div >
236
- < FeatureBadge featureState = { FeatureState . experimental } />
237
- </ Stack >
238
- </ >
239
- }
240
- horizontal = { true }
241
- >
242
- < Switch
243
- id = "query-editor-collection-streaming"
244
- value = { query . isStreaming === true }
245
- onChange = { onIsStreamingChange }
246
- />
247
- </ Field >
248
- < div className = "field-description" > Watch MongoDB Change Streams</ div >
249
- </ div >
250
- ) }
251
-
252
164
< InlineFieldRow >
253
165
< InlineField
254
- label = "Collection"
255
- error = "Collection is required"
166
+ label = "Collection" error = "Collection is required"
256
167
invalid = { query . queryLanguage !== QueryLanguage . JAVASCRIPT && ! query . collection }
257
- tooltip = "Name of the MongoDB collection to query"
168
+ tooltip = "Name of MongoDB collection to query"
258
169
>
259
170
< Input
260
- width = { 25 }
261
- id = "query-editor-collection"
262
- onChange = { onCollectionChange }
263
- value = { query . collection }
171
+ width = { 25 } id = "query-editor-collection" value = { query . collection }
172
+ onChange = { ( evt : ChangeEvent < HTMLInputElement > ) => props . onChange ( { ...query , collection : evt . target . value } ) }
264
173
disabled = { query . queryLanguage === QueryLanguage . JAVASCRIPT }
265
174
/>
266
175
</ InlineField >
176
+ { app !== CoreApp . Explore && < InlineField label = "Streaming" tooltip = "Watch MongoDB change streams" >
177
+ < InlineSwitch
178
+ id = "query-editor-collection-streaming"
179
+ value = { query . isStreaming === true }
180
+ onChange = { evt => props . onChange ( { ...query , isStreaming : evt . currentTarget . checked } ) }
181
+ />
182
+ </ InlineField >
183
+ }
184
+
267
185
</ InlineFieldRow >
268
186
{ isEditorExpanded ? renderPlaceholder ( ) : renderCodeEditor ( true , undefined , 300 ) }
269
187
270
188
< ControlledCollapse
271
- label = "Aggregate Options "
189
+ label = "Aggregate options "
272
190
isOpen = { isAggregateOptionExpanded }
273
191
onToggle = { ( ) => setIsAggregateOptionExpanded ( ! isAggregateOptionExpanded ) }
274
192
>
@@ -278,38 +196,53 @@ export function QueryEditor(props: Props) {
278
196
tooltip = "The maximum amount of time that the query can run on the server. The default value is nil, meaning that there is no time limit for query execution."
279
197
>
280
198
< Input
281
- id = "query-editor-max-time-ms"
199
+ id = "query-editor-max-time-ms" value = { query . aggregateMaxTimeMS }
282
200
onChange = { ( evt : ChangeEvent < HTMLInputElement > ) => {
283
201
if ( ! evt . target . value ) {
284
- onChange ( { ...query , aggregateMaxTimeMS : undefined } ) ;
285
- } else if ( validator . isInt ( evt . target . value , { gt : 1 } ) ) {
286
- onChange ( { ...query , aggregateMaxTimeMS : parseInt ( evt . target . value , 10 ) } ) ;
202
+ props . onChange ( { ...query , aggregateMaxTimeMS : undefined } ) ;
203
+ } else if ( validator . isInt ( evt . target . value , { gt : 0 } ) ) {
204
+ props . onChange ( { ...query , aggregateMaxTimeMS : parseInt ( evt . target . value , 10 ) } ) ;
287
205
}
288
206
} }
289
- value = { query . aggregateMaxTimeMS }
290
207
/>
291
208
</ InlineField >
292
209
< InlineField
293
210
label = "Max await time(ms)"
294
211
tooltip = "The maximum amount of time that the server should wait for new documents to satisfy a tailable cursor query."
295
212
>
296
- < Input id = "query-editor-max-await-time-ms" onChange = { onMaxAwaitTimeMSChange } value = { maxAwaitTimeMSText } />
213
+ < Input id = "query-editor-max-await-time-ms" value = { query . aggregateMaxAwaitTime }
214
+ onChange = { ( evt : ChangeEvent < HTMLInputElement > ) => {
215
+ if ( ! evt . target . value ) {
216
+ props . onChange ( { ...query , aggregateMaxAwaitTime : undefined } ) ;
217
+ } else if ( validator . isInt ( evt . target . value , { gt : 0 } ) ) {
218
+ props . onChange ( { ...query , aggregateMaxAwaitTime : parseInt ( evt . target . value , 10 ) } ) ;
219
+ }
220
+ } } />
297
221
</ InlineField >
298
222
</ InlineFieldRow >
299
223
< InlineFieldRow >
300
224
< InlineField
301
225
label = "Comment"
302
226
tooltip = "A string that will be included in server logs, profiling logs, and currentOp queries to help trace the operation."
303
227
>
304
- < Input id = "query-editor-comment" onChange = { onCommentChange } value = { query . aggregateComment } />
228
+ < Input id = "query-editor-comment" value = { query . aggregateComment }
229
+ onChange = { ( evt : ChangeEvent < HTMLInputElement > ) => {
230
+ if ( evt . target . value ) {
231
+ props . onChange ( { ...query , aggregateComment : evt . target . value } ) ;
232
+ }
233
+ } } />
305
234
</ InlineField >
306
235
< InlineField
307
236
label = "Batch size"
308
237
tooltip = "The maximum number of documents to be included in each batch returned by the server."
309
- error = "Invalid batch size"
310
- invalid = { batchSizeText !== '' && ! validatePositiveNumber ( batchSizeText ) }
311
238
>
312
- < Input id = "query-editor-batch-size" onChange = { onBatchSizeChange } value = { batchSizeText } />
239
+ < Input id = "query-editor-batch-size" value = { query . aggregateBatchSize }
240
+ onChange = { ( evt : ChangeEvent < HTMLInputElement > ) => {
241
+ if ( validator . isInt ( evt . target . value , { gt : 0 } ) ) {
242
+ props . onChange ( { ...query , aggregateBatchSize : parseInt ( evt . target . value , 10 ) } ) ;
243
+ }
244
+ } }
245
+ />
313
246
</ InlineField >
314
247
</ InlineFieldRow >
315
248
< InlineFieldRow >
@@ -318,19 +251,17 @@ export function QueryEditor(props: Props) {
318
251
tooltip = "If true, the operation can write to temporary files in the _tmp subdirectory of the database directory path on the server. The default value is false."
319
252
>
320
253
< InlineSwitch
321
- id = "query-editor-allow-disk-use"
322
- onChange = { onAllowDiskUseChange }
323
- value = { query . aggregateAllowDiskUse }
254
+ id = "query-editor-allow-disk-use" value = { query . aggregateAllowDiskUse }
255
+ onChange = { ( evt : ChangeEvent < HTMLInputElement > ) => props . onChange ( { ...query , aggregateAllowDiskUse : evt . target . checked } ) }
324
256
/>
325
257
</ InlineField >
326
258
< InlineField
327
259
label = "Bypass document validation"
328
260
tooltip = "If true, writes executed as part of the operation will opt out of document-level validation on the server. This option is valid for MongoDB versions >= 3.2 and is ignored for previous server versions. The default value is false."
329
261
>
330
262
< InlineSwitch
331
- id = "query-editor-bypass-document-validation"
332
- onChange = { onBypassDocumentValidationChange }
333
- value = { query . aggregateBypassDocumentValidation }
263
+ id = "query-editor-bypass-document-validation" value = { query . aggregateBypassDocumentValidation }
264
+ onChange = { ( evt : ChangeEvent < HTMLInputElement > ) => props . onChange ( { ...query , aggregateBypassDocumentValidation : evt . target . checked } ) }
334
265
/>
335
266
</ InlineField >
336
267
</ InlineFieldRow >
0 commit comments