import { isNumber , keys , values , find , each , cloneDeep , flatten } from 'lodash' ; import { buildExistsFilter , buildPhrasesFilter , buildQueryFromFilters } from '../../../../common' ; import { AggGroupNames } from '../agg_groups' ; import { IAggConfigs } from '../agg_configs' ; import { IBucketAggConfig } from './bucket_agg_type' ; param aggNestedDsl param startFromId const getNestedAggDSL = ( aggNestedDsl , startFromAggId ) : any => { if ( aggNestedDsl [ startFromAggId ] ) { return aggNestedDsl [ startFromAggId ] ; } const nestedAggs = values ( aggNestedDsl ) ; let aggs ; for ( let i = 0 ; i < nestedAggs . length ; i ++ ) { if ( nestedAggs [ i ] . aggs && ( aggs = getNestedAggDSL ( nestedAggs [ i ] . aggs , startFromAggId ) ) ) { return aggs ; } } } ; param aggConfigs param response param aggWithOtherBucket param key const getAggResultBuckets = ( aggConfigs , response , aggWithOtherBucket , key ) => { const keyParts = key . split ( '-' ) ; let responseAgg = response ; for ( const i in keyParts ) { if ( keyParts [ i ] ) { const responseAggs = values ( responseAgg ) ; for ( let aggId = 0 ; aggId < responseAggs . length ; aggId ++ ) { const aggById = responseAggs [ aggId ] ; const aggKey = keys ( responseAgg ) [ aggId ] ; const aggConfig = find ( aggConfigs . aggs , ( agg ) => agg . id === aggKey ) ; if ( aggConfig ) { const aggResultBucket = find ( aggById . buckets , ( bucket , bucketObjKey ) => { const bucketKey = aggConfig . getKey ( bucket , isNumber ( bucketObjKey ) ? undefined : bucketObjKey ) . toString ( ) ; return bucketKey === keyParts [ i ] ; } ) ; if ( aggResultBucket ) { responseAgg = aggResultBucket ; break ; } } } } } if ( responseAgg [ aggWithOtherBucket . id ] ) { return responseAgg [ aggWithOtherBucket . id ] . buckets ; } return [ ] ; } ; param responseAggs param aggId const getAggConfigResultMissingBuckets = ( responseAggs , aggId ) => { const missingKey = '__missing__' ; let resultBuckets = [ ] ; if ( responseAggs [ aggId ] ) { const matchingBucket = responseAggs [ aggId ] . buckets . find ( ( bucket ) => bucket . key === missingKey ) ; if ( matchingBucket ) resultBuckets . push ( matchingBucket ) ; return resultBuckets ; } each ( responseAggs , ( agg ) => { if ( agg . buckets ) { each ( agg . buckets , ( bucket ) => { resultBuckets = [ ... resultBuckets , ... getAggConfigResultMissingBuckets ( bucket , aggId ) ] ; } ) ; } } ) ; return resultBuckets ; } ; param requestAgg param key param otherAgg const getOtherAggTerms = ( requestAgg , key , otherAgg ) => { return requestAgg [ 'other-filter' ] . filters . filters [ key ] . bool . must_not . filter ( ( filter ) => filter . match_phrase && filter . match_phrase [ otherAgg . params . field . name ] ) . map ( ( filter ) => filter . match_phrase [ otherAgg . params . field . name ] ) ; } ; export const buildOtherBucketAgg = ( aggConfigs , aggWithOtherBucket , response ) => { const bucketAggs = aggConfigs . aggs . filter ( ( agg ) => agg . type . type === AggGroupNames . Buckets ) ; const index = bucketAggs . findIndex ( ( agg ) => agg . id === aggWithOtherBucket . id ) ; const aggs = aggConfigs . toDsl ( ) ; const indexPattern = aggWithOtherBucket . aggConfigs . indexPattern ; const filterAgg = aggConfigs . createAggConfig ( { type : 'filters' , id : 'other' , params : { filters : [ ] , } , enabled : false , } , { addToAggConfigs : false , } ) ; const resultAgg = { aggs : getNestedAggDSL ( aggs , aggWithOtherBucket . id ) . aggs , filters : filterAgg . toDsl ( ) , } ; let noAggBucketResults = false ; const walkBucketTree = ( aggIndex , aggregations , aggId , filters , key ) => { if ( aggregations [ aggId ] . buckets . length < 1 ) { noAggBucketResults = true ; return ; } const agg = aggregations [ aggId ] ; const newAggIndex = aggIndex + 1 ; const newAgg = bucketAggs [ newAggIndex ] ; const currentAgg = bucketAggs [ aggIndex ] ; if ( aggIndex < index ) { each ( agg . buckets , ( bucket , bucketObjKey ) => { const bucketKey = currentAgg . getKey ( bucket , isNumber ( bucketObjKey ) ? undefined : bucketObjKey ) ; const filter = cloneDeep ( bucket . filters ) || currentAgg . createFilter ( bucketKey ) ; const newFilters = flatten ( [ ... filters , filter ] ) ; walkBucketTree ( newAggIndex , bucket , newAgg . id , newFilters , `${ key }-${ bucketKey . toString ( ) }` ) ; } ) ; return ; } const hasScriptedField = ! ! aggWithOtherBucket . params . field . scripted ; const hasMissingBucket = ! ! aggWithOtherBucket . params . missingBucket ; const hasMissingBucketKey = agg . buckets . some ( ( bucket ) => bucket . key === '__missing__' ) ; if ( ! hasScriptedField && ( ! hasMissingBucket || hasMissingBucketKey ) ) { filters . push ( buildExistsFilter ( aggWithOtherBucket . params . field , aggWithOtherBucket . aggConfigs . indexPattern ) ) ; } each ( agg . buckets , ( bucket ) => { if ( bucket . key === '__missing__' ) return ; const filter = currentAgg . createFilter ( bucket . key ) ; filter . meta . negate = true ; filters . push ( filter ) ; } ) ; resultAgg . filters . filters [ key ] = { bool : buildQueryFromFilters ( filters , indexPattern ) , } ; } ; walkBucketTree ( 0 , response . aggregations , bucketAggs [ 0 ] . id , [ ] , '' ) ; if ( noAggBucketResults ) { return false ; } return ( ) => { return { 'other-filter' : resultAgg , } ; } ; } ; export const mergeOtherBucketAggResponse = ( aggsConfig , response , otherResponse , otherAgg , requestAgg ) => { const updatedResponse = cloneDeep ( response ) ; each ( otherResponse . aggregations [ 'other-filter' ] . buckets , ( bucket , key ) => { if ( ! bucket . doc_count || key === undefined ) return ; const bucketKey = key . replace ( /^-/ , '' ) ; const aggResultBuckets = getAggResultBuckets ( aggsConfig , updatedResponse . aggregations , otherAgg , bucketKey ) ; const requestFilterTerms = getOtherAggTerms ( requestAgg , key , otherAgg ) ; const phraseFilter = buildPhrasesFilter ( otherAgg . params . field , requestFilterTerms , otherAgg . aggConfigs . indexPattern ) ; phraseFilter . meta . negate = true ; bucket . filters = [ phraseFilter ] ; bucket . key = '__other__' ; if ( aggResultBuckets . some ( ( aggResultBucket ) => aggResultBucket . key === '__missing__' ) ) { bucket . filters . push ( buildExistsFilter ( otherAgg . params . field , otherAgg . aggConfigs . indexPattern ) ) ; } aggResultBuckets . push ( bucket ) ; } ) ; return updatedResponse ; } ; export const updateMissingBucket = ( response , aggConfigs , agg ) => { const updatedResponse = cloneDeep ( response ) ; const aggResultBuckets = getAggConfigResultMissingBuckets ( updatedResponse . aggregations , agg . id ) ; aggResultBuckets . forEach ( ( bucket ) => { bucket . key = '__missing__' ; } ) ; return updatedResponse ; } ;