import _ from 'lodash' ; import { Column , TableData } from '@grafana/data' ; /**
 * Extends the standard Column class with variables that get
 * mutated in the angular table panel.
 */ interface MutableColumn extends Column { title ? : string ; sort ? : boolean ; desc ? : boolean ; type ? : string ; } export default class TableModel implements TableData { columns : MutableColumn [ ] ; rows : any [ ] ; type : string ; columnMap : any ; refId : string ; constructor ( table ? ) { this . columns = [ ] ; this . columnMap = { } ; this . rows = [ ] ; this . type = 'table' ; this . refId = 'A' ; if ( table ) { if ( table . columns ) { for ( const col of table . columns ) { this . addColumn ( col ) ; } } if ( table . rows ) { for ( const row of table . rows ) { this . addRow ( row ) ; } } } } sort ( options ) { if ( options . col === null || this . columns . length <= options . col ) { return ; } this . rows . sort ( ( a , b ) => { a = a [ options . col ] ; b = b [ options . col ] ; return + ( a == null ) - + ( b == null ) || + ( a > b ) || - ( a < b ) ; } ) ; if ( options . desc ) { this . rows . reverse ( ) ; } this . columns [ options . col ] . sort = true ; this . columns [ options . col ] . desc = options . desc ; } addColumn ( col ) { if ( ! this . columnMap [ col . text ] ) { this . columns . push ( col ) ; this . columnMap [ col . text ] = col ; } } addRow ( row ) { this . rows . push ( row ) ; } } function areRowsMatching ( columns , row , otherRow ) { let foundFieldToMatch = false ; for ( let columnIndex = 0 ; columnIndex < columns . length ; columnIndex ++ ) { if ( row [ columnIndex ] !== undefined && otherRow [ columnIndex ] !== undefined ) { if ( row [ columnIndex ] !== otherRow [ columnIndex ] ) { return false ; } } else if ( row [ columnIndex ] === undefined || otherRow [ columnIndex ] === undefined ) { foundFieldToMatch = true ; } } return foundFieldToMatch ; } export function mergeTablesIntoModel ( dst ? , ... tables ) { const model = dst || new TableModel ( ) ; if ( arguments . length === 1 ) { return model ; } if ( arguments . length === 2 ) { model . columns = tables [ 0 ] . hasOwnProperty ( 'columns' ) ? [ ... tables [ 0 ] . columns ] : [ ] ; model . rows = tables [ 0 ] . hasOwnProperty ( 'rows' ) ? [ ... tables [ 0 ] . rows ] : [ ] ; return model ; } const tableDataTables = tables . filter ( table => ! ! table . columns ) ; const columnNames = { } ; const columnsUnion = tableDataTables . slice ( ) . reduce ( ( acc , series ) => { series . columns . forEach ( col => { const { text } = col ; if ( columnNames [ text ] === undefined ) { columnNames [ text ] = acc . length ; acc . push ( col ) ; } } ) ; return acc ; } , [ ] as MutableColumn [ ] ) ; const columnIndexMapper = tableDataTables . map ( series => series . columns . map ( col => columnNames [ col . text ] ) ) ; const flattenedRows = tableDataTables . reduce ( ( acc , series , seriesIndex ) => { const mapper = columnIndexMapper [ seriesIndex ] ; series . rows . forEach ( row => { const alteredRow = [ ] ; mapper . forEach ( ( to , from ) => { alteredRow [ to ] = row [ from ] ; } ) ; acc . push ( alteredRow ) ; } ) ; return acc ; } , [ ] as MutableColumn [ ] [ ] ) ; const mergedRows = { } ; const compactedRows = flattenedRows . reduce ( ( acc , row , rowIndex ) => { if ( ! mergedRows [ rowIndex ] ) { let offset = rowIndex + 1 ; while ( offset < flattenedRows . length ) { const match = _ . findIndex ( flattenedRows , otherRow => areRowsMatching ( columnsUnion , row , otherRow ) , offset ) ; if ( match > - 1 ) { const matchedRow = flattenedRows [ match ] ; for ( let columnIndex = 0 ; columnIndex < columnsUnion . length ; columnIndex ++ ) { if ( row [ columnIndex ] === undefined && matchedRow [ columnIndex ] !== undefined ) { row [ columnIndex ] = matchedRow [ columnIndex ] ; } } mergedRows [ match ] = matchedRow ; offset = match + 1 ; } else { break ; } } acc . push ( row ) ; } return acc ; } , [ ] as MutableColumn [ ] [ ] ) ; model . columns = columnsUnion ; model . rows = compactedRows ; return model ; }