import { BehaviorSubject , Observable } from 'rxjs' ; import { pick } from 'lodash' ; import { CoreSetup , CoreStart , OpenSearchDashboardsRequest , Logger , Plugin , PluginInitializerContext , RequestHandlerContext , SharedGlobalConfig , StartServicesAccessor , } from 'src/core/server' ; import { first } from 'rxjs/operators' ; import { ISearchSetup , ISearchStart , ISearchStrategy , SearchEnhancements } from './types' ; import { AggsService , AggsSetupDependencies } from './aggs' ; import { FieldFormatsStart } from '../field_formats' ; import { IndexPatternsServiceStart } from '../index_patterns' ; import { getCallMsearch , registerMsearchRoute , registerSearchRoute } from './routes' ; import { OPENSEARCH_SEARCH_STRATEGY , opensearchSearchStrategyProvider } from './opensearch_search' ; import { DataPluginStart } from '../plugin' ; import { UsageCollectionSetup } from '../../../usage_collection/server' ; import { registerUsageCollector } from './collectors/register' ; import { usageProvider } from './collectors/usage' ; import { searchTelemetry } from '../saved_objects' ; import { IOpenSearchDashboardsSearchRequest , IOpenSearchDashboardsSearchResponse , IOpenSearchSearchRequest , IOpenSearchSearchResponse , ISearchOptions , SearchSourceDependencies , SearchSourceService , searchSourceRequiredUiSettings , } from '../../common/search' ; import { getShardDelayBucketAgg , SHARD_DELAY_AGG_NAME , } from '../../common/search/aggs/buckets/shard_delay' ; import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn' ; import { ConfigSchema } from '../../config' ; type StrategyMap = Record < string , ISearchStrategy < any , any > > ; internal export interface SearchServiceSetupDependencies { registerFunction : AggsSetupDependencies [ 'registerFunction' ] ; usageCollection ? : UsageCollectionSetup ; } internal export interface SearchServiceStartDependencies { fieldFormats : FieldFormatsStart ; indexPatterns : IndexPatternsServiceStart ; } internal export interface SearchRouteDependencies { getStartServices : StartServicesAccessor < { } , DataPluginStart > ; globalConfig$ : Observable < SharedGlobalConfig > ; } export class SearchService implements Plugin < ISearchSetup , ISearchStart > { private readonly aggsService = new AggsService ( ) ; private readonly searchSourceService = new SearchSourceService ( ) ; private defaultSearchStrategyName : string = OPENSEARCH_SEARCH_STRATEGY ; private searchStrategies : StrategyMap = { } ; constructor ( private initializerContext , private readonly logger ) { } public setup ( core , { registerFunction , usageCollection } ) { const usage = usageCollection ? usageProvider ( core ) : undefined ; const router = core . http . createRouter ( ) ; const routeDependencies = { getStartServices : core . getStartServices , globalConfig$ : this . initializerContext . config . legacy . globalConfig$ , } ; registerSearchRoute ( router , routeDependencies ) ; registerMsearchRoute ( router , routeDependencies ) ; this . registerSearchStrategy ( OPENSEARCH_SEARCH_STRATEGY , opensearchSearchStrategyProvider ( this . initializerContext . config . legacy . globalConfig$ , this . logger , usage ) ) ; core . savedObjects . registerType ( searchTelemetry ) ; if ( usageCollection ) { registerUsageCollector ( usageCollection , this . initializerContext ) ; } const aggs = this . aggsService . setup ( { registerFunction } ) ; this . initializerContext . config . create < ConfigSchema > ( ) . pipe ( first ( ) ) . toPromise ( ) . then ( ( value ) => { if ( value ?. search ?. aggs ?. shardDelay ?. enabled ) { aggs . types . registerBucket ( SHARD_DELAY_AGG_NAME , getShardDelayBucketAgg ) ; registerFunction ( aggShardDelay ) ; } } ) ; return { __enhance : ( enhancements ) => { if ( this . searchStrategies . hasOwnProperty ( enhancements . defaultStrategy ) ) { this . defaultSearchStrategyName = enhancements . defaultStrategy ; } } , aggs , registerSearchStrategy : this . registerSearchStrategy , usage , } ; } public start ( { opensearch , savedObjects , uiSettings } , { fieldFormats , indexPatterns } ) { return { aggs : this . aggsService . start ( { fieldFormats , uiSettings } ) , getSearchStrategy : this . getSearchStrategy , search : ( context , searchRequest , options ) => { return this . search ( context , searchRequest , options ) ; } , searchSource : { asScoped : async ( request ) => { const opensearchClient = opensearch . client . asScoped ( request ) ; const savedObjectsClient = savedObjects . getScopedClient ( request ) ; const scopedIndexPatterns = await indexPatterns . indexPatternsServiceFactory ( request ) ; const uiSettingsClient = uiSettings . asScopedToClient ( savedObjectsClient ) ; const uiSettingsCache = pick ( await uiSettingsClient . getAll ( ) , searchSourceRequiredUiSettings ) ; const searchSourceDependencies = { getConfig : < T = any > ( key ) : T => uiSettingsCache [ key ] , search : ( searchRequest , options ) => { /**
                             * Unless we want all SearchSource users to provide both a OpenSearchDashboardsRequest
                             * (needed for index patterns) AND the RequestHandlerContext (needed for
                             * low-level search), we need to fake the context as it can be derived
                             * from the request object anyway. This will pose problems for folks who
                             * are registering custom search strategies as they are only getting a
                             * subset of the entire context. Ideally low-level search should be
                             * refactored to only require the needed dependencies: opensearchClient & uiSettings.
                             */ const fakeRequestHandlerContext = { core : { opensearch : { client : opensearchClient , } , uiSettings : { client : uiSettingsClient , } , } , } as RequestHandlerContext ; return this . search ( fakeRequestHandlerContext , searchRequest , options ) ; } , onResponse : ( req , res ) => res , legacy : { callMsearch : getCallMsearch ( { opensearchClient , globalConfig$ : this . initializerContext . config . legacy . globalConfig$ , uiSettings : uiSettingsClient , } ) , loadingCount$ : new BehaviorSubject ( 0 ) , } , } ; return this . searchSourceService . start ( scopedIndexPatterns , searchSourceDependencies ) ; } , } , } ; } public stop ( ) { this . aggsService . stop ( ) ; } private registerSearchStrategy = < SearchStrategyRequest extends IOpenSearchDashboardsSearchRequest = IOpenSearchSearchRequest , SearchStrategyResponse extends IOpenSearchDashboardsSearchResponse = IOpenSearchSearchResponse > ( name , strategy ) => { this . logger . debug ( `Register strategy ${ name }` ) ; this . searchStrategies [ name ] = strategy ; } ; private search = < SearchStrategyRequest extends IOpenSearchDashboardsSearchRequest = IOpenSearchSearchRequest , SearchStrategyResponse extends IOpenSearchDashboardsSearchResponse = IOpenSearchSearchResponse > ( context , searchRequest , options ) : Promise < SearchStrategyResponse > => { return this . getSearchStrategy < SearchStrategyRequest , SearchStrategyResponse > ( options . strategy || this . defaultSearchStrategyName ) . search ( context , searchRequest , options ) ; } ; private getSearchStrategy = < SearchStrategyRequest extends IOpenSearchDashboardsSearchRequest = IOpenSearchSearchRequest , SearchStrategyResponse extends IOpenSearchDashboardsSearchResponse = IOpenSearchSearchResponse > ( name ) : ISearchStrategy < SearchStrategyRequest , SearchStrategyResponse > => { this . logger . debug ( `Get strategy ${ name }` ) ; const strategy = this . searchStrategies [ name ] ; if ( ! strategy ) { throw new Error ( `Search strategy ${ name } not found` ) ; } return strategy ; } ; }