import moment from 'moment-timezone' ; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey' ; import { useEffect , useMemo } from 'react' ; import { EuiDataGridCellValueElementProps , EuiDataGridSorting , EuiDataGridStyle , } from '@elastic/eui' ; import { i18n } from '@kbn/i18n' ; import { CoreSetup } from 'src/core/public' ; import { ES_FIELD_TYPES , KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public' ; import type { DataView , DataViewField } from '../../../../../../../src/plugins/data_views/common' ; import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants/data_frame_analytics' ; import { extractErrorMessage } from '../../../../common/util/errors' ; import { FeatureImportance , FeatureImportanceClassName , TopClasses , } from '../../../../common/types/feature_importance' ; import { BASIC_NUMERICAL_TYPES , EXTENDED_NUMERICAL_TYPES , } from '../../data_frame_analytics/common/fields' ; import { FEATURE_IMPORTANCE , FEATURE_INFLUENCE , OUTLIER_SCORE , TOP_CLASSES , } from '../../data_frame_analytics/common/constants' ; import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils' ; import { getNestedProperty } from '../../util/object_utils' ; import { mlFieldFormatService } from '../../services/field_format_service' ; import { DataGridItem , IndexPagination , RenderCellValue } from './types' ; import { RuntimeMappings } from '../../../../common/types/fields' ; import { isRuntimeMappings } from '../../../../common/util/runtime_field_utils' ; export const INIT_MAX_COLUMNS = 10 ; export const COLUMN_CHART_DEFAULT_VISIBILITY_ROWS_THRESHOLED = 10000 ; export const euiDataGridStyle = { border : 'all' , fontSize : 's' , cellPadding : 's' , stripes : false , rowHover : 'none' , header : 'shade' , } ; export const euiDataGridToolbarSettings = { showColumnSelector : true , showDisplaySelector : false , showSortSelector : true , showFullScreenSelector : false , } ; export const getFieldsFromKibanaIndexPattern = ( indexPattern ) : string [ ] => { const allFields = indexPattern . fields . map ( ( f ) => f . name ) ; const indexPatternFields = allFields . filter ( ( f ) => { if ( indexPattern . metaFields . includes ( f ) ) { return false ; } const fieldParts = f . split ( '.' ) ; const lastPart = fieldParts . pop ( ) ; if ( lastPart === 'keyword' && allFields . includes ( fieldParts . join ( '.' ) ) ) { return false ; } return true ; } ) ; return indexPatternFields ; } ; param indexPattern param RuntimeMappings export function getCombinedRuntimeMappings ( indexPattern , runtimeMappings ? ) { let combinedRuntimeMappings = { } ; if ( indexPattern ) { const computedFields = indexPattern ?. getComputedFields ( ) ; if ( computedFields ?. runtimeFields !== undefined ) { const indexPatternRuntimeMappings = computedFields . runtimeFields ; if ( isRuntimeMappings ( indexPatternRuntimeMappings ) ) { combinedRuntimeMappings = { ... combinedRuntimeMappings , ... indexPatternRuntimeMappings } ; } } } if ( isRuntimeMappings ( runtimeMappings ) ) { combinedRuntimeMappings = { ... combinedRuntimeMappings , ... runtimeMappings } ; } if ( isRuntimeMappings ( combinedRuntimeMappings ) ) { return combinedRuntimeMappings ; } } export interface FieldTypes { [ key ] : ES_FIELD_TYPES ; } export const getDataGridSchemasFromFieldTypes = ( fieldTypes , resultsField ) => { return Object . keys ( fieldTypes ) . map ( ( field ) => { let schema ; const isSortable = true ; const type = fieldTypes [ field ] ; const isNumber = type !== undefined && ( BASIC_NUMERICAL_TYPES . has ( type ) || EXTENDED_NUMERICAL_TYPES . has ( type ) ) ; if ( isNumber ) { schema = 'numeric' ; } switch ( type ) { case 'date' : schema = 'datetime' ; break ; case 'nested' : case 'geo_point' : schema = 'json' ; break ; case 'boolean' : schema = 'boolean' ; break ; case 'text' : schema = NON_AGGREGATABLE ; } if ( field === `${ resultsField }.${ OUTLIER_SCORE }` ) { schema = 'numeric' ; } if ( field . includes ( `${ resultsField }.${ TOP_CLASSES }` ) ) { schema = 'json' ; } if ( field . includes ( `${ resultsField }.${ FEATURE_IMPORTANCE }` ) ) { schema = 'featureImportance' ; } if ( field === `${ resultsField }.${ FEATURE_INFLUENCE }` ) { schema = 'featureInfluence' ; } return { id : field , schema , isSortable } ; } ) ; } ; export const NON_AGGREGATABLE = 'non-aggregatable' ; export const getDataGridSchemaFromESFieldType = ( fieldType ) : string | undefined => { let schema ; switch ( fieldType ) { case ES_FIELD_TYPES . GEO_POINT : case ES_FIELD_TYPES . GEO_SHAPE : schema = 'json' ; break ; case ES_FIELD_TYPES . BOOLEAN : schema = 'boolean' ; break ; case ES_FIELD_TYPES . DATE : case ES_FIELD_TYPES . DATE_NANOS : schema = 'datetime' ; break ; case ES_FIELD_TYPES . BYTE : case ES_FIELD_TYPES . DOUBLE : case ES_FIELD_TYPES . FLOAT : case ES_FIELD_TYPES . HALF_FLOAT : case ES_FIELD_TYPES . INTEGER : case ES_FIELD_TYPES . LONG : case ES_FIELD_TYPES . SCALED_FLOAT : case ES_FIELD_TYPES . SHORT : schema = 'numeric' ; break ; case ES_FIELD_TYPES . KEYWORD : case ES_FIELD_TYPES . TEXT : break ; } return schema ; } ; export const getDataGridSchemaFromKibanaFieldType = ( field ) : string | undefined => { let schema ; switch ( field ?. type ) { case KBN_FIELD_TYPES . BOOLEAN : schema = 'boolean' ; break ; case KBN_FIELD_TYPES . DATE : schema = 'datetime' ; break ; case KBN_FIELD_TYPES . GEO_POINT : case KBN_FIELD_TYPES . GEO_SHAPE : schema = 'json' ; break ; case KBN_FIELD_TYPES . NUMBER : schema = 'numeric' ; break ; case KBN_FIELD_TYPES . NESTED : schema = 'json' ; break ; } if ( schema === undefined && field ?. aggregatable === false ) { return NON_AGGREGATABLE ; } return schema ; } ; const getClassName = ( className , isClassTypeBoolean ) => { if ( isClassTypeBoolean ) { return className === 'true' ; } return className ; } ; param row param mlResultsField returns export const getFeatureImportance = ( row , mlResultsField , isClassTypeBoolean = false ) : FeatureImportance [ ] => { const featureImportance = row [ `${ mlResultsField }.feature_importance` ] ; if ( featureImportance === undefined ) return [ ] ; return featureImportance . map ( ( fi ) => ( { feature_name : Array . isArray ( fi . feature_name ) ? fi . feature_name [ 0 ] : fi . feature_name , classes : Array . isArray ( fi . classes ) ? fi . classes . map ( ( c ) => { const processedClass = getProcessedFields ( c ) ; return { importance : processedClass . importance , class_name : getClassName ( processedClass . class_name , isClassTypeBoolean ) , } ; } ) : fi . classes , importance : Array . isArray ( fi . importance ) ? fi . importance [ 0 ] : fi . importance , } ) ) ; } ; param row param mlResultsField returns export const getTopClasses = ( row , mlResultsField ) : TopClasses => { const topClasses = row [ `${ mlResultsField }.top_classes` ] ; if ( topClasses === undefined ) return [ ] ; return topClasses . map ( ( tc ) => getProcessedFields ( tc ) ) as TopClasses ; } ; export const useRenderCellValue = ( indexPattern , pagination , tableItems , resultsField ? , cellPropsCallback ? ) : RenderCellValue => { const renderCellValue = useMemo ( ( ) => { return ( { rowIndex , columnId , setCellProps , } ) => { const adjustedRowIndex = rowIndex - pagination . pageIndex * pagination . pageSize ; const fullItem = tableItems [ adjustedRowIndex ] ; if ( fullItem === undefined ) { return null ; } if ( indexPattern === undefined ) { return null ; } let format ; if ( indexPattern !== undefined ) { format = mlFieldFormatService . getFieldFormatFromIndexPattern ( indexPattern , columnId , '' ) ; } function getCellValue ( cId ) { if ( tableItems . hasOwnProperty ( adjustedRowIndex ) ) { const item = tableItems [ adjustedRowIndex ] ; if ( item . hasOwnProperty ( cId ) ) { return item [ cId ] ; } if ( cId === `${ resultsField }.feature_importance` ) { return getFeatureImportance ( fullItem , resultsField ?? DEFAULT_RESULTS_FIELD ) ; } if ( cId === `${ resultsField }.top_classes` ) { return getTopClasses ( fullItem , resultsField ?? DEFAULT_RESULTS_FIELD ) ; } return getNestedProperty ( tableItems [ adjustedRowIndex ] , cId , null ) ; } return null ; } const cellValue = getCellValue ( columnId ) ; useEffect ( ( ) => { if ( typeof cellPropsCallback === 'function' ) { cellPropsCallback ( columnId , cellValue , fullItem , setCellProps ) ; } } , [ columnId , cellValue ] ) ; if ( typeof cellValue === 'object' && cellValue !== null ) { return JSON . stringify ( cellValue ) ; } if ( cellValue === undefined || cellValue === null ) { return null ; } if ( format !== undefined ) { return format . convert ( cellValue , 'text' ) ; } if ( typeof cellValue === 'string' || cellValue === null ) { return cellValue ; } const field = indexPattern . fields . getByName ( columnId ) ; if ( field ?. type === KBN_FIELD_TYPES . DATE ) { return formatHumanReadableDateTimeSeconds ( moment ( cellValue ) . unix ( ) * 1000 ) ; } if ( typeof cellValue === 'boolean' ) { return cellValue ? 'true' : 'false' ; } return cellValue ; } ; } , [ indexPattern ?. fields , pagination . pageIndex , pagination . pageSize , tableItems ] ) ; return renderCellValue ; } ; param sortingColumns returns export const multiColumnSortFactory = ( sortingColumns ) => { const isString = ( arg ) : arg is string => { return typeof arg === 'string' ; } ; const sortFn = ( a , b , sortingColumnIndex = 0 ) : number => { const sort = sortingColumns [ sortingColumnIndex ] ; const aValue = getNestedProperty ( a , sort . id , null ) ; const bValue = getNestedProperty ( b , sort . id , null ) ; if ( typeof aValue === 'number' && typeof bValue === 'number' ) { if ( aValue < bValue ) { return sort . direction === 'asc' ? - 1 : 1 ; } if ( aValue > bValue ) { return sort . direction === 'asc' ? 1 : - 1 ; } } if ( isString ( aValue ) && isString ( bValue ) ) { if ( aValue . localeCompare ( bValue ) === - 1 ) { return sort . direction === 'asc' ? - 1 : 1 ; } if ( aValue . localeCompare ( bValue ) === 1 ) { return sort . direction === 'asc' ? 1 : - 1 ; } } if ( sortingColumnIndex + 1 < sortingColumns . length ) { return sortFn ( a , b , sortingColumnIndex + 1 ) ; } return 0 ; } ; return sortFn ; } ; export const showDataGridColumnChartErrorMessageToast = ( e , toastNotifications ) => { const error = extractErrorMessage ( e ) ; toastNotifications . addDanger ( i18n . translate ( 'xpack.ml.dataGrid.columnChart.ErrorMessageToast' , { defaultMessage : 'An error occurred fetching the histogram charts data: {error}' , values : { error : error !== '' ? error : e } , } ) ) ; } ; export const getProcessedFields = ( originalObj , omitBy ? ) => { const obj = { ... originalObj } ; for ( const key of Object . keys ( obj ) ) { if ( omitBy === undefined ) { if ( Array . isArray ( obj [ key ] ) && obj [ key ] . length === 1 ) { obj [ key ] = obj [ key ] [ 0 ] ; } } else { if ( omitBy ( key ) === false ) if ( Array . isArray ( obj [ key ] ) && obj [ key ] . length === 1 ) { obj [ key ] = obj [ key ] [ 0 ] ; } } } return obj ; } ;