4
4
*/
5
5
6
6
import React , { useState } from 'react' ;
7
- import { useFormikContext } from 'formik' ;
7
+ import { useFormikContext , getIn } from 'formik' ;
8
+ import { isEmpty , get } from 'lodash' ;
9
+ import jsonpath from 'jsonpath' ;
8
10
import {
9
11
EuiButton ,
10
- EuiButtonEmpty ,
11
12
EuiCodeBlock ,
12
- EuiCodeEditor ,
13
13
EuiFlexGroup ,
14
14
EuiFlexItem ,
15
15
EuiModal ,
@@ -21,8 +21,10 @@ import {
21
21
EuiText ,
22
22
} from '@elastic/eui' ;
23
23
import {
24
+ IConfigField ,
24
25
IProcessorConfig ,
25
26
IngestPipelineConfig ,
27
+ JSONPATH_ROOT_SELECTOR ,
26
28
PROCESSOR_CONTEXT ,
27
29
SimulateIngestPipelineDoc ,
28
30
SimulateIngestPipelineResponse ,
@@ -32,13 +34,16 @@ import {
32
34
import { formikToIngestPipeline , generateId } from '../../../../utils' ;
33
35
import { simulatePipeline , useAppDispatch } from '../../../../store' ;
34
36
import { getCore } from '../../../../services' ;
37
+ import { MapField } from '../input_fields' ;
35
38
36
39
interface InputTransformModalProps {
37
40
uiConfig : WorkflowConfig ;
38
41
config : IProcessorConfig ;
39
42
context : PROCESSOR_CONTEXT ;
43
+ inputMapField : IConfigField ;
44
+ inputMapFieldPath : string ;
40
45
onClose : ( ) => void ;
41
- onConfirm : ( ) => void ;
46
+ onFormChange : ( ) => void ;
42
47
}
43
48
44
49
/**
@@ -50,13 +55,16 @@ export function InputTransformModal(props: InputTransformModalProps) {
50
55
51
56
// source input / transformed output state
52
57
const [ sourceInput , setSourceInput ] = useState < string > ( '[]' ) ;
53
- const [ transformedOutput , setTransformedOutput ] = useState < string > ( 'TODO' ) ;
58
+ const [ transformedOutput , setTransformedOutput ] = useState < string > ( '[]' ) ;
59
+
60
+ // parse out the values and determine if there are none/some/all valid jsonpaths
61
+ const mapValues = getIn ( values , `ingest.enrich.${ props . config . id } .inputMap` ) ;
54
62
55
63
return (
56
64
< EuiModal onClose = { props . onClose } style = { { width : '70vw' } } >
57
65
< EuiModalHeader >
58
66
< EuiModalHeaderTitle >
59
- < p > { `Configure input transform ` } </ p >
67
+ < p > { `Configure input` } </ p >
60
68
</ EuiModalHeaderTitle >
61
69
</ EuiModalHeader >
62
70
< EuiModalBody >
@@ -116,24 +124,105 @@ export function InputTransformModal(props: InputTransformModalProps) {
116
124
</ EuiFlexItem >
117
125
< EuiFlexItem >
118
126
< >
119
- < EuiText > Define transform with JSONPath</ EuiText >
127
+ < EuiText > Define transform</ EuiText >
128
+ < EuiText size = "s" color = "subdued" >
129
+ { `Dot notation is used by default. To explicitly use JSONPath, please ensure to prepend with the
130
+ root object selector "${ JSONPATH_ROOT_SELECTOR } "` }
131
+ </ EuiText >
120
132
< EuiSpacer size = "s" />
121
- < EuiCodeEditor
122
- mode = "json"
123
- theme = "textmate"
124
- value = { `TODO` }
125
- readOnly = { false }
126
- setOptions = { {
127
- fontSize : '12px' ,
128
- autoScrollEditorIntoView : true ,
129
- } }
130
- tabSize = { 2 }
133
+ < MapField
134
+ field = { props . inputMapField }
135
+ fieldPath = { props . inputMapFieldPath }
136
+ label = "Input map"
137
+ helpText = { `An array specifying how to map fields from the ingested document to the model’s input.` }
138
+ helpLink = {
139
+ 'https://opensearch.org/docs/latest/ingest-pipelines/processors/ml-inference/#configuration-parameters'
140
+ }
141
+ keyPlaceholder = "Model input field"
142
+ valuePlaceholder = "Document field"
143
+ onFormChange = { props . onFormChange }
131
144
/>
132
145
</ >
133
146
</ EuiFlexItem >
134
147
< EuiFlexItem >
135
148
< >
136
149
< EuiText > Expected output</ EuiText >
150
+ < EuiButton
151
+ style = { { width : '100px' } }
152
+ disabled = {
153
+ isEmpty ( mapValues ) || isEmpty ( JSON . parse ( sourceInput ) )
154
+ }
155
+ onClick = { async ( ) => {
156
+ switch ( props . context ) {
157
+ case PROCESSOR_CONTEXT . INGEST : {
158
+ if (
159
+ ! isEmpty ( mapValues ) &&
160
+ ! isEmpty ( JSON . parse ( sourceInput ) )
161
+ ) {
162
+ let output = { } ;
163
+ let sampleSourceInput = { } ;
164
+ try {
165
+ sampleSourceInput = JSON . parse ( sourceInput ) [ 0 ] ;
166
+ } catch { }
167
+
168
+ mapValues . forEach (
169
+ ( mapValue : { key : string ; value : string } ) => {
170
+ const path = mapValue . value ;
171
+ try {
172
+ let transformedResult = undefined ;
173
+ // ML inference processors will use standard dot notation or JSONPath depending on the input.
174
+ // We follow the same logic here to generate consistent results.
175
+ if (
176
+ mapValue . value . startsWith (
177
+ JSONPATH_ROOT_SELECTOR
178
+ )
179
+ ) {
180
+ // JSONPath transform
181
+ transformedResult = jsonpath . query (
182
+ sampleSourceInput ,
183
+ path
184
+ ) ;
185
+ // Bracket notation not supported - throw an error
186
+ } else if (
187
+ mapValue . value . includes ( ']' ) ||
188
+ mapValue . value . includes ( ']' )
189
+ ) {
190
+ throw new Error ( ) ;
191
+ // Standard dot notation
192
+ } else {
193
+ transformedResult = get (
194
+ sampleSourceInput ,
195
+ path
196
+ ) ;
197
+ }
198
+
199
+ output = {
200
+ ...output ,
201
+ [ mapValue . key ] : transformedResult || '' ,
202
+ } ;
203
+
204
+ setTransformedOutput (
205
+ JSON . stringify ( output , undefined , 2 )
206
+ ) ;
207
+ } catch ( e : any ) {
208
+ console . error ( e ) ;
209
+ getCore ( ) . notifications . toasts . addDanger (
210
+ 'Error generating expected output. Ensure your inputs are valid JSONPath or dot notation syntax.' ,
211
+ e
212
+ ) ;
213
+ }
214
+ }
215
+ ) ;
216
+ }
217
+
218
+ break ;
219
+ }
220
+ // TODO: complete for search request / search response contexts
221
+ }
222
+ } }
223
+ >
224
+ Generate
225
+ </ EuiButton >
137
226
< EuiSpacer size = "s" />
138
227
< EuiCodeBlock fontSize = "m" isCopyable = { false } >
139
228
{ transformedOutput }
@@ -143,9 +232,8 @@ export function InputTransformModal(props: InputTransformModalProps) {
143
232
</ EuiFlexGroup >
144
233
</ EuiModalBody >
145
234
< EuiModalFooter >
146
- < EuiButtonEmpty onClick = { props . onClose } > Cancel</ EuiButtonEmpty >
147
- < EuiButton onClick = { props . onConfirm } fill = { true } color = "primary" >
148
- Save
235
+ < EuiButton onClick = { props . onClose } fill = { false } color = "primary" >
236
+ Close
149
237
</ EuiButton >
150
238
</ EuiModalFooter >
151
239
</ EuiModal >
0 commit comments