import { get } from 'lodash' ; import moment from 'moment' ; import { formatExpression , SerializedFieldFormat } from '../../../../plugins/expressions/public' ; import { IAggConfig , search , TimefilterContract } from '../../../../plugins/data/public' ; import { Vis , VisParams } from '../types' ; const { isDateHistogramBucketAggConfig } = search . aggs ; interface SchemaConfigParams { precision ? : number ; useGeocentroid ? : boolean ; } export interface SchemaConfig { accessor : number ; label : string ; format : SerializedFieldFormat ; params : SchemaConfigParams ; aggType : string ; } export interface Schemas { metric : SchemaConfig [ ] ; bucket ? : any [ ] ; geo_centroid ? : any [ ] ; group ? : any [ ] ; params ? : any [ ] ; radius ? : any [ ] ; segment ? : any [ ] ; split_column ? : any [ ] ; split_row ? : any [ ] ; width ? : any [ ] ; [ key ] : any [ ] | undefined ; } type BuildVisFunction = ( params : VisParams , schemas : Schemas , uiState : any , meta ? : { savedObjectId ? : string ; } ) => string ; type buildVisConfigFunction = ( schemas : Schemas , visParams ? : VisParams ) => VisParams ; interface BuildPipelineVisFunction { [ key ] : BuildVisFunction ; } interface BuildVisConfigFunction { [ key ] : buildVisConfigFunction ; } export interface BuildPipelineParams { timefilter : TimefilterContract ; timeRange ? : any ; abortSignal ? : AbortSignal ; } const vislibCharts = [ 'area' , 'gauge' , 'goal' , 'heatmap' , 'histogram' , 'horizontal_bar' , 'line' , ] ; export const getSchemas = < TVisParams > ( vis , { timeRange , timefilter } ) : Schemas => { const createSchemaConfig = ( accessor , agg ) : SchemaConfig => { if ( isDateHistogramBucketAggConfig ( agg ) ) { agg . params . timeRange = timeRange ; const bounds = agg . params . timeRange && agg . fieldIsTimeField ( ) ? timefilter . calculateBounds ( agg . params . timeRange ) : undefined ; agg . buckets . setBounds ( bounds ) ; agg . buckets . setInterval ( agg . params . interval ) ; } const hasSubAgg = [ 'derivative' , 'moving_avg' , 'serial_diff' , 'cumulative_sum' , 'sum_bucket' , 'avg_bucket' , 'min_bucket' , 'max_bucket' , ] . includes ( agg . type . name ) ; const formatAgg = hasSubAgg ? agg . params . customMetric || agg . aggConfigs . getRequestAggById ( agg . params . metricAgg ) : agg ; const params = { } ; if ( agg . type . name === 'geohash_grid' ) { params . precision = agg . params . precision ; params . useGeocentroid = agg . params . useGeocentroid ; } const label = agg . makeLabel && agg . makeLabel ( ) ; return { accessor , format : formatAgg . toSerializedFieldFormat ( ) , params , label , aggType : agg . type . name , } ; } ; let cnt = 0 ; const schemas = { metric : [ ] , } ; if ( ! vis . data . aggs ) { return schemas ; } const responseAggs = vis . data . aggs . getResponseAggs ( ) . filter ( ( agg ) => agg . enabled ) ; const isHierarchical = vis . isHierarchical ( ) ; const metrics = responseAggs . filter ( ( agg ) => agg . type . type === 'metrics' ) ; responseAggs . forEach ( ( agg ) => { let skipMetrics = false ; let schemaName = agg . schema ; if ( ! schemaName ) { if ( agg . type . name === 'geo_centroid' ) { schemaName = 'geo_centroid' ; } else { cnt ++ ; return ; } } if ( schemaName === 'split' ) { schemaName = `split_${ ( vis . params as any ) . row ? 'row' : 'column' }` ; skipMetrics = responseAggs . length - metrics . length > 1 ; } if ( ! schemas [ schemaName ] ) { schemas [ schemaName ] = [ ] ; } if ( ! isHierarchical || agg . type . type !== 'metrics' ) { schemas [ schemaName ] ! . push ( createSchemaConfig ( cnt ++ , agg ) ) ; } if ( isHierarchical && ( agg . type . type !== 'metrics' || metrics . length === responseAggs . length ) ) { metrics . forEach ( ( metric ) => { const schemaConfig = createSchemaConfig ( cnt ++ , metric ) ; if ( ! skipMetrics ) { schemas . metric . push ( schemaConfig ) ; } } ) ; } } ) ; return schemas ; } ; export const prepareJson = ( variable , data ? ) : string => { if ( data === undefined ) { return '' ; } return `${ variable }='${ JSON . stringify ( data ) . replace ( /\\/g , `\\\\` ) . replace ( /'/g , `\\'` ) }' ` ; } ; export const escapeString = ( data ) : string => { return data . replace ( /\\/g , `\\\\` ) . replace ( /'/g , `\\'` ) ; } ; export const prepareString = ( variable , data ? ) : string => { if ( data === undefined ) { return '' ; } return `${ variable }='${ escapeString ( data ) }' ` ; } ; export const prepareValue = ( variable , data , raw = false ) => { if ( data === undefined ) { return '' ; } if ( raw ) { return `${ variable }=${ data } ` ; } switch ( typeof data ) { case 'string' : return prepareString ( variable , data ) ; case 'object' : return prepareJson ( variable , data ) ; default : return `${ variable }=${ data } ` ; } } ; export const prepareDimension = ( variable , data ) => { if ( data === undefined ) { return '' ; } let expr = `${ variable }={visdimension ${ data . accessor } ` ; if ( data . format ) { expr += prepareValue ( 'format' , data . format . id ) ; expr += prepareJson ( 'formatParams' , data . format . params ) ; } expr += '} ' ; return expr ; } ; const adjustVislibDimensionFormmaters = ( vis , dimensions ) : void => { const visConfig = vis . params ; const responseAggs = vis . data . aggs ! . getResponseAggs ( ) . filter ( ( agg ) => agg . enabled ) ; ( dimensions . y || [ ] ) . forEach ( ( yDimension ) => { const yAgg = responseAggs [ yDimension . accessor ] ; const seriesParam = ( visConfig . seriesParams || [ ] ) . find ( ( param ) => param . data . id === yAgg . id ) ; if ( seriesParam ) { const usedValueAxis = ( visConfig . valueAxes || [ ] ) . find ( ( valueAxis ) => valueAxis . id === seriesParam . valueAxis ) ; if ( get ( usedValueAxis , 'scale.mode' ) === 'percentage' ) { yDimension . format = { id : 'percent' } ; } } if ( get ( visConfig , 'gauge.percentageMode' ) === true ) { yDimension . format = { id : 'percent' } ; } } ) ; } ; export const buildPipelineVisFunction = { vega : ( params ) => { return `vega ${ prepareString ( 'spec' , params . spec ) }` ; } , input_control_vis : ( params ) => { return `input_control_vis ${ prepareJson ( 'visConfig' , params ) }` ; } , metrics : ( { title , ... params } , schemas , uiState = { } ) => { const paramsJson = prepareJson ( 'params' , params ) ; const uiStateJson = prepareJson ( 'uiState' , uiState ) ; const paramsArray = [ paramsJson , uiStateJson ] . filter ( ( param ) => Boolean ( param ) ) ; return `tsvb ${ paramsArray . join ( ' ' ) }` ; } , table : ( params , schemas ) => { const visConfig = { ... params , ... buildVisConfig . table ( schemas , params ) , } ; return `opensearch_dashboards_table ${ prepareJson ( 'visConfig' , visConfig ) }` ; } , region_map : ( params , schemas ) => { const visConfig = { ... params , ... buildVisConfig . region_map ( schemas ) , } ; return `regionmap ${ prepareJson ( 'visConfig' , visConfig ) }` ; } , tile_map : ( params , schemas ) => { const visConfig = { ... params , ... buildVisConfig . tile_map ( schemas ) , } ; return `tilemap ${ prepareJson ( 'visConfig' , visConfig ) }` ; } , pie : ( params , schemas ) => { const visConfig = { ... params , ... buildVisConfig . pie ( schemas ) , } ; return `opensearch_dashboards_pie ${ prepareJson ( 'visConfig' , visConfig ) }` ; } , } ; const buildVisConfig = { table : ( schemas , visParams = { } ) => { const visConfig = { } as any ; const metrics = schemas . metric ; const buckets = schemas . bucket || [ ] ; visConfig . dimensions = { metrics , buckets , splitRow : schemas . split_row , splitColumn : schemas . split_column , } ; if ( visParams . showMetricsAtAllLevels === false && visParams . showPartialRows === true ) { const metricsPerBucket = metrics . length / buckets . length ; visConfig . dimensions . metrics . splice ( 0 , metricsPerBucket * buckets . length - metricsPerBucket ) ; } return visConfig ; } , region_map : ( schemas ) => { const visConfig = { } as any ; visConfig . metric = schemas . metric [ 0 ] ; if ( schemas . segment ) { visConfig . bucket = schemas . segment [ 0 ] ; } return visConfig ; } , tile_map : ( schemas ) => { const visConfig = { } as any ; visConfig . dimensions = { metric : schemas . metric [ 0 ] , geohash : schemas . segment ? schemas . segment [ 0 ] : null , geocentroid : schemas . geo_centroid ? schemas . geo_centroid [ 0 ] : null , } ; return visConfig ; } , pie : ( schemas ) => { const visConfig = { } as any ; visConfig . dimensions = { metric : schemas . metric [ 0 ] , buckets : schemas . segment , splitRow : schemas . split_row , splitColumn : schemas . split_column , } ; return visConfig ; } , } ; export const buildVislibDimensions = async ( vis , params ) => { const schemas = getSchemas ( vis , { timeRange : params . timeRange , timefilter : params . timefilter , } ) ; const dimensions = { x : schemas . segment ? schemas . segment [ 0 ] : null , y : schemas . metric , z : schemas . radius , width : schemas . width , series : schemas . group , splitRow : schemas . split_row , splitColumn : schemas . split_column , } ; if ( schemas . segment ) { const xAgg = vis . data . aggs . getResponseAggs ( ) [ dimensions . x . accessor ] ; if ( xAgg . type . name === 'date_histogram' ) { dimensions . x . params . date = true ; const { opensearchUnit , opensearchValue } = xAgg . buckets . getInterval ( ) ; dimensions . x . params . interval = moment . duration ( opensearchValue , opensearchUnit ) ; dimensions . x . params . intervalESValue = opensearchValue ; dimensions . x . params . intervalESUnit = opensearchUnit ; dimensions . x . params . format = xAgg . buckets . getScaledDateFormat ( ) ; dimensions . x . params . bounds = xAgg . buckets . getBounds ( ) ; } else if ( xAgg . type . name === 'histogram' ) { const intervalParam = xAgg . type . paramByName ( 'interval' ) ; const output = { params : { } as any } ; await intervalParam . modifyAggConfigOnSearchRequestStart ( xAgg , vis . data . searchSource , { abortSignal : params . abortSignal , } ) ; intervalParam . write ( xAgg , output ) ; dimensions . x . params . interval = output . params . interval ; } } adjustVislibDimensionFormmaters ( vis , dimensions ) ; return dimensions ; } ; export const buildPipeline = async ( vis , params ) => { const { indexPattern , searchSource } = vis . data ; const query = searchSource ! . getField ( 'query' ) ; const filters = searchSource ! . getField ( 'filter' ) ; const { uiState , title } = vis ; let pipeline = `opensearchDashboards | opensearch_dashboards_context ` ; if ( query ) { pipeline += prepareJson ( 'query' , query ) ; } if ( filters ) { pipeline += prepareJson ( 'filters' , filters ) ; } if ( vis . data . savedSearchId ) { pipeline += prepareString ( 'savedSearchId' , vis . data . savedSearchId ) ; } pipeline += '| ' ; if ( vis . type . toExpressionAst ) { const visAst = await vis . type . toExpressionAst ( vis , params ) ; pipeline += formatExpression ( visAst ) ; } else { if ( vis . type . requestHandler === 'courier' ) { pipeline += `opensearchaggs
    ${ prepareString ( 'index' , indexPattern ! . id ) }
    metricsAtAllLevels=${ vis . isHierarchical ( ) }
    partialRows=${ vis . params . showPartialRows || false }
    ${ prepareJson ( 'aggConfigs' , vis . data . aggs ! . aggs ) } | ` ; } const schemas = getSchemas ( vis , params ) ; if ( buildPipelineVisFunction [ vis . type . name ] ) { pipeline += buildPipelineVisFunction [ vis . type . name ] ( { title , ... vis . params } , schemas , uiState ) ; } else if ( vislibCharts . includes ( vis . type . name ) ) { const visConfig = { ... vis . params } ; visConfig . dimensions = await buildVislibDimensions ( vis , params ) ; pipeline += `vislib type='${ vis . type . name }' ${ prepareJson ( 'visConfig' , visConfig ) }` ; } else { const visConfig = { ... vis . params } ; visConfig . dimensions = schemas ; pipeline += `visualization type='${ vis . type . name }'
    ${ prepareJson ( 'visConfig' , visConfig ) }
    metricsAtAllLevels=${ vis . isHierarchical ( ) }
    partialRows=${ vis . params . showPartialRows || false } ` ; if ( indexPattern ) { pipeline += `${ prepareString ( 'index' , indexPattern . id ) } ` ; if ( vis . data . aggs ) { pipeline += `${ prepareJson ( 'aggConfigs' , vis . data . aggs ! . aggs ) }` ; } } } } return pipeline ; } ;