import _ from 'lodash' ; import { i18n } from '@osd/i18n' ; import { Assign , Ensure } from '@osd/utility-types' ; import { ISearchSource } from 'src/plugins/data/public' ; import { ExpressionAstFunction , ExpressionAstArgument , SerializedFieldFormat , } from 'src/plugins/expressions/common' ; import { ISearchOptions } from '../opensearch_search' ; import { IAggType } from './agg_type' ; import { writeParams } from './agg_params' ; import { IAggConfigs } from './agg_configs' ; type State = string | number | boolean | null | undefined | SerializableState ; internal export interface SerializableState { [ key ] : State | State [ ] ; } internal export type AggConfigSerialized = Ensure < { type : string ; enabled ? : boolean ; id ? : string ; params ? : { } | SerializableState ; schema ? : string ; } , SerializableState > ; export type AggConfigOptions = Assign < AggConfigSerialized , { type : IAggType ; } > ; name description export type IAggConfig = AggConfig ; export class AggConfig { param { array [ object ] } list return { array } static ensureIds ( list ) { const have = [ ] ; const haveNot = [ ] ; list . forEach ( function ( obj ) { ( obj . id ? have : haveNot ) . push ( obj ) ; } ) ; let nextId = AggConfig . nextId ( have ) ; haveNot . forEach ( function ( obj ) { obj . id = String ( nextId ++ ) ; } ) ; return list ; } return { array } static nextId ( list ) { return ( 1 + list . reduce ( function ( max , obj ) { return Math . max ( max , + obj . id || 0 ) ; } , 0 ) ) ; } public aggConfigs : IAggConfigs ; public id : string ; public enabled : boolean ; public params : any ; public parent ? : IAggConfigs ; public brandNew ? : boolean ; public schema ? : string ; private __type : IAggType ; private __typeDecorations : any ; private subAggs : AggConfig [ ] = [ ] ; constructor ( aggConfigs , opts ) { this . aggConfigs = aggConfigs ; this . id = String ( opts . id || AggConfig . nextId ( aggConfigs . aggs as any ) ) ; this . enabled = typeof opts . enabled === 'boolean' ? opts . enabled : true ; this . params = { } ; this . setType ( opts . type ) ; if ( opts . schema ) { this . schema = opts . schema ; } this . setParams ( opts . params || { } ) ; this . __type = this . __type ; } param { object } from return { undefined } setParams ( from ) { from = from || this . params || { } ; const to = ( this . params = { } as any ) ; this . getAggParams ( ) . forEach ( ( aggParam ) => { let val = from [ aggParam . name ] ; if ( val == null ) { if ( aggParam . default == null ) return ; if ( ! _ . isFunction ( aggParam . default ) ) { val = aggParam . default ; } else { val = aggParam . default ( this ) ; if ( val == null ) return ; } } if ( aggParam . deserialize ) { const isTyped = _ . isFunction ( aggParam . valueType ) ; const isType = isTyped && val instanceof aggParam . valueType ; const isObject = ! isTyped && _ . isObject ( val ) ; const isDeserialized = isType || isObject ; if ( ! isDeserialized ) { val = aggParam . deserialize ( val , this ) ; } to [ aggParam . name ] = val ; return ; } to [ aggParam . name ] = _ . cloneDeep ( val ) ; } ) ; } getParam ( key ) { return _ . get ( this . params , key ) ; } write ( aggs ? ) { return writeParams < AggConfig > ( this . type . params , this , aggs ) ; } isFilterable ( ) { return _ . isFunction ( this . type . createFilter ) ; } createFilter ( key , params = { } ) { const createFilter = this . type . createFilter ; if ( ! createFilter ) { throw new TypeError ( `The "${ this . type . title }" aggregation does not support filtering.` ) ; } const field = this . getField ( ) ; const label = this . getFieldDisplayName ( ) ; if ( field && ! field . filterable ) { let message = `The "${ label }" field can not be used for filtering.` ; if ( field . scripted ) { message = `The "${ label }" field is scripted and can not be used for filtering.` ; } throw new TypeError ( message ) ; } return createFilter ( this , key , params ) ; } param { SearchSource } searchSource param { ISearchOptions } options return { Promise < undefined > } onSearchRequestStart ( searchSource , options ? ) { if ( ! this . type ) { return Promise . resolve ( ) ; } return Promise . all ( this . type . params . map ( ( param ) => param . modifyAggConfigOnSearchRequestStart ( this , searchSource , options ) ) ) ; } param { AggConfigs } aggConfigs return { void | Object } toDsl ( aggConfigs ? ) { if ( this . type . hasNoDsl ) return ; const output = this . write ( aggConfigs ) as any ; const configDsl = { } as any ; configDsl [ this . type . dslName || this . type . name ] = output . params ; if ( this . subAggs . length && ! output . subAggs ) output . subAggs = this . subAggs ; if ( output . subAggs ) { const subDslLvl = configDsl . aggs || ( configDsl . aggs = { } ) ; output . subAggs . forEach ( function nestAdhocSubAggs ( subAggConfig ) { subDslLvl [ subAggConfig . id ] = subAggConfig . toDsl ( aggConfigs ) ; } ) ; } if ( output . parentAggs ) { const subDslLvl = configDsl . parentAggs || ( configDsl . parentAggs = { } ) ; output . parentAggs . forEach ( function nestAdhocSubAggs ( subAggConfig ) { subDslLvl [ subAggConfig . id ] = subAggConfig . toDsl ( aggConfigs ) ; } ) ; } return configDsl ; } returns serialize ( ) { const params = this . params ; const outParams = _ . transform ( this . getAggParams ( ) , ( out , aggParam ) => { let val = params [ aggParam . name ] ; if ( val == null ) return ; if ( aggParam . serialize ) val = aggParam . serialize ( val , this ) ; if ( val == null ) return ; out [ aggParam . name ] = _ . cloneDeep ( val ) ; } , { } ) ; return { id : this . id , enabled : this . enabled , type : this . type && this . type . name , params : outParams as SerializableState , ... ( this . schema && { schema : this . schema } ) , } ; } deprecated toJSON ( ) { return this . serialize ( ) ; } public toSerializedFieldFormat ( ) { return this . type ? this . type . getSerializedFormat ( this ) : { } ; } returns toExpressionAst ( ) { const functionName = this . type && this . type . expressionName ; const { type , ... rest } = this . serialize ( ) ; if ( ! functionName || ! rest . params ) { return ; } const params = Object . entries ( rest . params ) . reduce ( ( acc , [ key , value ] ) => { const deserializedParam = this . getAggParams ( ) . find ( ( p ) => p . name === key ) ; if ( deserializedParam && deserializedParam . toExpressionAst ) { const paramExpressionAst = deserializedParam . toExpressionAst ( this . getParam ( key ) ) ; if ( paramExpressionAst ) { acc [ key ] = [ { type : 'expression' , chain : [ paramExpressionAst ] , } , ] ; } } else if ( typeof value === 'object' ) { acc [ key ] = [ JSON . stringify ( value ) ] ; } else if ( typeof value !== 'undefined' ) { acc [ key ] = [ value ] ; } return acc ; } , { } as Record < string , ExpressionAstArgument [ ] > ) ; return { type : 'function' , function : functionName , arguments : { ... params , id : [ this . id ] , enabled : [ this . enabled ] , ... ( this . schema ? { schema : [ this . schema ] } : { } ) , } , } ; } getAggParams ( ) { return [ ... ( _ . hasIn ( this , 'type.params' ) ? this . type . params : [ ] ) ] ; } getRequestAggs ( ) { return ( this . type && this . type . getRequestAggs ( this ) ) || [ this ] ; } getResponseAggs ( ) { return ( this . type && this . type . getResponseAggs ( this ) ) || [ this ] ; } getValue ( bucket ) { return this . type . getValue ( this , bucket ) ; } getKey ( bucket , key ? ) { if ( this . type . getKey ) { return this . type . getKey ( bucket , key , this ) ; } else { return '' ; } } getFieldDisplayName ( ) { const field = this . getField ( ) ; return field ? field . displayName || this . fieldName ( ) : '' ; } getField ( ) { return this . params . field ; } makeLabel ( percentageMode = false ) { if ( this . params . customLabel ) { return this . params . customLabel ; } if ( ! this . type ) return '' ; return percentageMode ? i18n . translate ( 'data.search.aggs.percentageOfLabel' , { defaultMessage : 'Percentage of {label}' , values : { label : this . type . makeLabel ( this ) } , } ) : `${ this . type . makeLabel ( this ) }` ; } getIndexPattern ( ) { return this . aggConfigs . indexPattern ; } getTimeRange ( ) { return this . aggConfigs . timeRange ; } fieldName ( ) { const field = this . getField ( ) ; return field ? field . name : '' ; } fieldIsTimeField ( ) { const indexPattern = this . getIndexPattern ( ) ; if ( ! indexPattern ) return false ; const timeFieldName = indexPattern . timeFieldName ; return timeFieldName && this . fieldName ( ) === timeFieldName ; } public get type ( ) { return this . __type ; } public set type ( type ) { if ( this . __typeDecorations ) { _ . forOwn ( this . __typeDecorations , ( prop , name ) => { delete this [ name ] ; } ) ; } if ( type && _ . isFunction ( type . decorateAggConfig ) ) { this . __typeDecorations = type . decorateAggConfig ( ) ; Object . defineProperties ( this , this . __typeDecorations ) ; } this . __type = type ; let availableFields = [ ] ; const fieldParam = this . type && this . type . params . find ( ( p ) => p . type === 'field' ) ; if ( fieldParam ) { availableFields = fieldParam . getAvailableFields ( this ) ; } this . setParams ( { field : availableFields . find ( ( field ) => field . name === this . getField ( ) ) , } ) ; } public setType ( type ) { this . type = type ; } }