import { get , map } from 'lodash' ; import { schema } from '@osd/config-schema' ; import { IRouter , SharedGlobalConfig } from 'opensearch-dashboards/server' ; import { Observable } from 'rxjs' ; import { first } from 'rxjs/operators' ; import { IFieldType , Filter } from '../index' ; import { findIndexPatternById , getFieldByName } from '../index_patterns' ; import { getRequestAbortedSignal } from '../lib' ; export function registerValueSuggestionsRoute ( router , config$ ) { router . post ( { path : '/api/opensearch-dashboards/suggestions/values/{index}' , validate : { params : schema . object ( { index : schema . string ( ) , } , { unknowns : 'allow' } ) , body : schema . object ( { field : schema . string ( ) , query : schema . string ( ) , boolFilter : schema . maybe ( schema . any ( ) ) , } , { unknowns : 'allow' } ) , } , } , async ( context , request , response ) => { const config = await config$ . pipe ( first ( ) ) . toPromise ( ) ; const { field : fieldName , query , boolFilter } = request . body ; const { index } = request . params ; const { client } = context . core . opensearch . legacy ; const signal = getRequestAbortedSignal ( request . events . aborted$ ) ; const autocompleteSearchOptions = { timeout : `${ config . opensearchDashboards . autocompleteTimeout . asMilliseconds ( ) }ms` , terminate_after : config . opensearchDashboards . autocompleteTerminateAfter . asMilliseconds ( ) , } ; const indexPattern = await findIndexPatternById ( context . core . savedObjects . client , index ) ; const field = indexPattern && getFieldByName ( fieldName , indexPattern ) ; const body = await getBody ( autocompleteSearchOptions , field || fieldName , query , boolFilter ) ; try { const result = await client . callAsCurrentUser ( 'search' , { index , body } , { signal } ) ; const buckets = get ( result , 'aggregations.suggestions.buckets' ) || get ( result , 'aggregations.nestedSuggestions.suggestions.buckets' ) ; return response . ok ( { body : map ( buckets || [ ] , 'key' ) } ) ; } catch ( error ) { return response . internalError ( { body : error } ) ; } } ) ; } async function getBody ( { timeout , terminate_after } , field , query , boolFilter = [ ] ) { const isFieldObject = ( f ) : f is IFieldType => Boolean ( f && f . name ) ; const getEscapedQuery = ( q = '' ) => q . replace ( /[.?+*|{}[\]()"\\#@&<>~]/g , ( match ) => `\\${ match }` ) ; const executionHint = 'map' ; const shardSize = 10 ; const body = { size : 0 , timeout , terminate_after , query : { bool : { filter : boolFilter , } , } , aggs : { suggestions : { terms : { field : isFieldObject ( field ) ? field . name : field , include : `${ getEscapedQuery ( query ) }.*` , execution_hint : executionHint , shard_size : shardSize , } , } , } , } ; if ( isFieldObject ( field ) && field . subType && field . subType . nested ) { return { ... body , aggs : { nestedSuggestions : { nested : { path : field . subType . nested . path , } , aggs : body . aggs , } , } , } ; } return body ; }