1
- import { LanguageProvider , TimeRange } from '@grafana/data' ;
2
- import { CompletionItem , TypeaheadInput , TypeaheadOutput } from '@grafana/ui' ;
1
+ import { TimeRange } from '@grafana/data' ;
2
+ import { TypeaheadInput , TypeaheadOutput } from '@grafana/ui' ;
3
3
import { JSONPath } from 'jsonpath-plus' ;
4
4
5
5
import { JsonApiQuery } from 'types' ;
6
6
import { JsonDataSource } from 'datasource' ;
7
7
8
- export class JsonPathLanguageProvider extends LanguageProvider {
8
+ /**
9
+ * JsonPathLanguageProvider provides suggestions for the QueryField onTypeahead
10
+ * callback.
11
+ */
12
+ export class JsonPathLanguageProvider {
9
13
datasource : JsonDataSource ;
10
14
11
15
constructor ( datasource : JsonDataSource ) {
12
- super ( ) ;
13
16
this . datasource = datasource ;
14
17
}
15
18
19
+ // This is important if you don't want punctuation to interfere with your suggestions.
16
20
cleanText = ( s : string ) => s . replace ( / [ { } [ \] = " ( ) , ! ~ + \- * / ^ % \| \$ @ \. ] / g, '' ) . trim ( ) ;
17
21
18
- async provideCompletionItems (
19
- input : TypeaheadInput ,
20
- context : JsonApiQuery ,
21
- timeRange ?: TimeRange
22
- ) : Promise < TypeaheadOutput > {
22
+ /**
23
+ * getSuggestions returns completion items for the current JSON Path and
24
+ * cursor position.
25
+ *
26
+ * In addition to the typeahead input, this method accepts the current query
27
+ * and time range as a context for the actual data source request.
28
+ *
29
+ * Since the language provider has a reference to the data source instance,
30
+ * we can invoke methods that aren't part of the DataSourceApi interface. In this case, we call a `metadataRequest`
31
+ * method to query the data source on-demand.
32
+ *
33
+ * Normally you'd have to parse the query language here. This function
34
+ * simplifies this by instead using JSON Path for getting the actual values.
35
+ */
36
+ async getSuggestions ( input : TypeaheadInput , context : JsonApiQuery , timeRange ?: TimeRange ) : Promise < TypeaheadOutput > {
23
37
const { value } = input ;
24
38
25
39
const emptyResult : TypeaheadOutput = { suggestions : [ ] } ;
@@ -37,22 +51,32 @@ export class JsonPathLanguageProvider extends LanguageProvider {
37
51
38
52
const toCursor = currentLine . slice ( 0 , value . selection . anchor . offset ) ;
39
53
54
+ // $.dat|
40
55
const currIdentifier = / [ a - z A - Z 0 - 9 ] $ / ;
56
+
57
+ // $.data.|
41
58
const nextIdentifier = / [ \$ \] a - z A - Z 0 - 9 ] \. $ / ;
42
- const currentNodeIdentifier = / @ \. $ / ;
59
+
60
+ // $.data[|
43
61
const enterBrackets = / \[ $ / ;
44
62
45
- const isValid =
63
+ // $.data[?(@.|
64
+ const currentNodeIdentifier = / @ \. $ / ;
65
+
66
+ // Here we check whether the cursor is in a position where it should
67
+ // suggest.
68
+ const shouldSuggest =
46
69
currIdentifier . test ( toCursor ) ||
47
70
nextIdentifier . test ( toCursor ) ||
48
71
currentNodeIdentifier . test ( toCursor ) ||
49
72
enterBrackets . test ( toCursor ) ;
50
73
51
- if ( ! isValid ) {
74
+ if ( ! shouldSuggest ) {
52
75
return emptyResult ;
53
76
}
54
77
55
- const response : any = await this . datasource . metadataRequest ( context , timeRange ) ;
78
+ // Get the actual JSON for parsing.
79
+ const response = await this . datasource . metadataRequest ( context , timeRange ) ;
56
80
57
81
if ( enterBrackets . test ( toCursor ) ) {
58
82
return {
@@ -64,9 +88,9 @@ export class JsonPathLanguageProvider extends LanguageProvider {
64
88
{ label : ':' , documentation : 'Returns a slice of the array.' } ,
65
89
{
66
90
label : '?' ,
67
- documentation : 'Returns elements based on a filter expression.' ,
68
91
insertText : '?()' ,
69
92
move : - 1 ,
93
+ documentation : 'Returns elements based on a filter expression.' ,
70
94
} ,
71
95
] ,
72
96
} ,
@@ -76,30 +100,37 @@ export class JsonPathLanguageProvider extends LanguageProvider {
76
100
77
101
const insideBrackets = toCursor . lastIndexOf ( '[' ) > toCursor . lastIndexOf ( ']' ) ;
78
102
103
+ // Construct a JSON Path that returns the items in the current context.
79
104
const path = insideBrackets
80
105
? toCursor . slice ( 0 , toCursor . lastIndexOf ( '[' ) + 1 ) + ':]'
81
106
: currentLine . slice ( 0 , currentLine . lastIndexOf ( '.' ) ) ;
82
107
83
108
const values = JSONPath ( { path, json : response } ) ;
84
109
110
+ // Don't attempt to suggest if this is a leaf node, e.g. strings, numbers, and booleans.
85
111
if ( typeof values [ 0 ] !== 'object' ) {
86
112
return emptyResult ;
87
113
}
88
114
89
- const items : CompletionItem [ ] = Object . entries ( values [ 0 ] ) . map ( ( [ key , value ] ) => {
90
- return Array . isArray ( value )
91
- ? { label : key , insertText : key + '[]' , move : - 1 , documentation : `_array (${ value . length } )_` }
92
- : { label : key , documentation : `_${ typeof value } _\n\n**Preview:**\n\n\`${ value } \`` } ;
93
- } ) ;
94
-
95
- return { suggestions : [ { label : 'Elements' , items } ] } ;
115
+ return {
116
+ suggestions : [
117
+ {
118
+ label : 'Elements' , // Name of the suggestion group
119
+ items : Object . entries ( values [ 0 ] ) . map ( ( [ key , value ] ) => {
120
+ return Array . isArray ( value )
121
+ ? {
122
+ label : key , // Text to display in the suggestion list
123
+ insertText : key + '[]' , // When selecting an array, we automatically insert the brackets ...
124
+ move : - 1 , // ... and put the cursor between them
125
+ documentation : `_array (${ value . length } )_` , // Markdown documentation for the suggestion
126
+ }
127
+ : {
128
+ label : key ,
129
+ documentation : `_${ typeof value } _\n\n**Preview:**\n\n\`${ value } \`` ,
130
+ } ;
131
+ } ) ,
132
+ } ,
133
+ ] ,
134
+ } ;
96
135
}
97
-
98
- request = async ( url : string , params ?: any ) : Promise < any > => {
99
- return undefined ;
100
- } ;
101
-
102
- start = async ( ) : Promise < any [ ] > => {
103
- return [ ] ;
104
- } ;
105
136
}
0 commit comments