import { MetricsPanelCtrl } from 'grafana/app/plugins/sdk' ; import _ from 'lodash' ; import angular from 'angular' ; import kbn from 'grafana/app/core/utils/kbn' ; import * as FileExport from './file_export' ; import 'datatables.net/js/jquery.dataTables.min' ; import { panelDefaults , dateFormats , columnTypes , columnStyleDefaults , colorModes , fontSizes } from './Defaults' ; import { transformDataToTable , transformers } from './transformers' ; import { DatatableRenderer } from './DatatableRenderer' ; import { PanelEvents } from '@grafana/data' ; export class DatatablePanelCtrl extends MetricsPanelCtrl { static templateUrl = 'partials/template.html' ; dataLoaded : boolean ; pageIndex : number ; table : any ; dataRaw : any [ ] ; transformers : any ; annotationsSrv : any ; uiSegmentSrv : any ; addColumnSegment : any ; mappingTypes : any ; columnSortMethods : any ; fontSizes : any ; colorModes : any ; columnTypes : any ; unitFormats : any ; dateFormats : any ; $q : any ; http : any ; getColumnNames : ( ) => any [ ] ; ngInject constructor ( $scope , $injector , $http , $location , $q , uiSegmentSrv , annotationsSrv , private $sanitize , timeSrv ) { super ( $scope , $injector ) ; this . pageIndex = 0 ; this . table = null ; this . dataRaw = [ ] ; this . $q = $q ; this . transformers = transformers ; this . annotationsSrv = annotationsSrv ; this . uiSegmentSrv = uiSegmentSrv ; this . addColumnSegment = uiSegmentSrv . newPlusButton ( ) ; this . mappingTypes = [ { text : 'Value to text' , value : 1 } , { text : 'Range to text' , value : 2 } , ] ; this . columnSortMethods = [ { text : 'Ascending' , value : 'asc' , } , { text : 'Descending' , value : 'desc' , } , ] ; this . fontSizes = fontSizes ; this . colorModes = colorModes ; this . columnTypes = columnTypes ; this . unitFormats = kbn . getUnitFormats ( ) ; this . dateFormats = dateFormats ; this . getColumnNames = ( ) => { if ( ! this . table ) { return [ ] ; } return _ . map ( this . table . columns , col => { return col . text ; } ) ; } ; if ( this . panel . styles === void 0 ) { this . panel . styles = this . panel . columns ; this . panel . columns = this . panel . fields ; delete this . panel . columns ; delete this . panel . fields ; } _ . defaults ( this . panel , panelDefaults ) ; this . dataLoaded = true ; this . http = $http ; if ( typeof PanelEvents === 'undefined' ) { this . events . on ( 'data-received' , this . onDataReceived . bind ( this ) ) ; this . events . on ( 'data-error' , this . onDataError . bind ( this ) ) ; this . events . on ( 'data-snapshot-load' , this . onDataReceived . bind ( this ) ) ; this . events . on ( 'init-edit-mode' , this . onInitEditMode . bind ( this ) ) ; this . events . on ( 'init-panel-actions' , this . onInitPanelActions . bind ( this ) ) ; } else { this . events . on ( PanelEvents . dataReceived , this . onDataReceived . bind ( this ) ) ; this . events . on ( PanelEvents . dataError , this . onDataError . bind ( this ) ) ; this . events . on ( PanelEvents . dataSnapshotLoad , this . onDataReceived . bind ( this ) ) ; this . events . on ( PanelEvents . editModeInitialized , this . onInitEditMode . bind ( this ) ) ; this . events . on ( PanelEvents . initPanelActions , this . onInitPanelActions . bind ( this ) ) ; } } onInitPanelActions ( actions ) { actions . push ( { text : 'Export CSV' , click : 'ctrl.exportCsv()' , } ) ; } onInitEditMode ( ) { const thisPanelPath = 'public/plugins/' + this . panel . type + '/' ; const optionsPath = thisPanelPath + 'partials/editor.options.html' ; this . addEditorTab ( 'Options' , optionsPath , 2 ) ; const datatableOptionsPath = thisPanelPath + 'partials/datatables.options.html' ; this . addEditorTab ( 'Datatable Options' , datatableOptionsPath , 3 ) ; } issueQueries ( datasource ) { this . pageIndex = 0 ; if ( this . panel . transform === 'annotations' ) { this . setTimeQueryStart ( ) ; return this . annotationsSrv . getAnnotations ( { dashboard : this . dashboard , panel : this . panel , range : this . range , } ) . then ( ( annotations ) => { return { data : annotations , } ; } ) ; } return super . issueQueries ( datasource ) ; } onDataError ( err ) { this . dataRaw = [ ] ; this . render ( ) ; } onDataReceived ( dataList ) { this . dataRaw = dataList ; this . pageIndex = 0 ; if ( this . dataRaw && this . dataRaw . length ) { if ( this . dataRaw [ 0 ] . type === 'table' ) { this . panel . transform = 'table' ; } else { if ( this . dataRaw [ 0 ] . type === 'docs' ) { this . panel . transform = 'json' ; } else { if ( this . panel . transform === 'table' || this . panel . transform === 'json' ) { this . panel . transform = 'timeseries_to_rows' ; } } } } this . render ( ) ; } render ( ) { this . table = transformDataToTable ( this . dataRaw , this . panel ) ; this . table . sort ( this . panel . sort ) ; this . panel . emptyData = this . table . rows . length === 0 || this . table . columns . length === 0 ; return super . render ( this . table ) ; } changeRowNumbersEnabled ( ) { this . panel . sortByColumnsData . map ( ( sortData ) => [ this . panel . rowNumbersEnabled ? sortData [ 0 ] ++ : sortData [ 0 ] -- , sortData [ 1 ] , ] ) ; this . render ( ) ; } getPanelHeight ( ) { let tmpPanelHeight = this . panel . height ; if ( typeof tmpPanelHeight === 'undefined' || tmpPanelHeight === '' ) { tmpPanelHeight = String ( this . height ) ; if ( typeof this . panel . span !== 'undefined' ) { let panelTitleOffset = 20 ; if ( this . panel . title !== '' ) { panelTitleOffset = 42 ; } tmpPanelHeight = String ( this . containerHeight - panelTitleOffset ) ; } if ( typeof tmpPanelHeight === 'undefined' ) { tmpPanelHeight = this . row . height ; if ( typeof tmpPanelHeight === 'undefined' ) { tmpPanelHeight = '250' ; } } } tmpPanelHeight = tmpPanelHeight . replace ( 'px' , '' ) ; const actualHeight = parseInt ( tmpPanelHeight , 10 ) ; return actualHeight ; } exportCsv ( ) { let isInUTC = false ; if ( this . dashboard && this . dashboard . hasOwnProperty ( 'isTimezoneUtc' ) ) { isInUTC = this . dashboard . isTimezoneUtc ( ) ; } const renderer = new DatatableRenderer ( this . panel , this . table , isInUTC , this . $sanitize , this . timeSrv ) ; FileExport . exportTableDataToCsv ( renderer . render_values ( ) ) ; } link ( scope , elem , attrs , ctrl ) { let data ; const panel = ctrl . panel ; const _this = this ; return { [ type ] } function renderPanel ( ) { let isInUTC = false ; if ( ctrl . dashboard && ctrl . dashboard . hasOwnProperty ( 'isTimezoneUtc' ) ) { isInUTC = ctrl . dashboard . isTimezoneUtc ( ) ; } const renderer = new DatatableRenderer ( panel , ctrl . table , isInUTC , ctrl . $sanitize , _this . timeSrv ) ; renderer . render ( ) ; _this . dataLoaded = true ; } ctrl . panel . panelHeight = this . getPanelHeight ( ) ; ctrl . events . on ( 'render' , ( renderData ) => { data = renderData || data ; if ( data ) { renderPanel ( ) ; } ctrl . renderingCompleted ( ) ; } ) ; } showCellBordersChanged ( ) { if ( this . panel . showCellBorders ) { this . panel . showRowBorders = false ; } this . render ( ) ; } themeChanged ( ) { this . render ( ) ; } transformChanged ( ) { this . panel . columns = [ ] ; this . render ( ) ; } removeColumn ( column ) { this . panel . columns = _ . without ( this . panel . columns , column ) ; this . render ( ) ; } getColumnOptions ( ) { if ( ! this . dataRaw ) { return this . $q . when ( [ ] ) ; } const columns = this . transformers [ this . panel . transform ] . getColumns ( this . dataRaw ) ; const segments = _ . map ( columns , c => this . uiSegmentSrv . newSegment ( { value : c . text , } ) ) ; return this . $q . when ( segments ) ; } addColumn ( ) { const columns = transformers [ this . panel . transform ] . getColumns ( this . dataRaw ) ; const column = _ . find ( columns , { text : this . addColumnSegment . value , } ) ; if ( column ) { this . panel . columns . push ( column ) ; this . render ( ) ; } const plusButton = this . uiSegmentSrv . newPlusButton ( ) ; this . addColumnSegment . html = plusButton . html ; this . addColumnSegment . value = plusButton . value ; } addColumnStyle ( ) { this . panel . styles . push ( angular . copy ( columnStyleDefaults ) ) ; } removeColumnStyle ( style ) { this . panel . styles = _ . without ( this . panel . styles , style ) ; } setUnitFormat ( column , subItem ) { column . unit = subItem . value ; this . render ( ) ; } invertColorOrder ( index ) { const ref = this . panel . styles [ index ] . colors ; const copy = ref [ 0 ] ; ref [ 0 ] = ref [ 2 ] ; ref [ 2 ] = copy ; this . render ( ) ; } addColumnSortingRule ( ) { const defaultRule = { columnData : 0 , sortMethod : 'desc' , } ; this . panel . sortByColumns . push ( angular . copy ( defaultRule ) ) ; this . columnSortChanged ( ) ; } removeSortByColumn ( column ) { this . panel . sortByColumns = _ . without ( this . panel . sortByColumns , column ) ; this . columnSortChanged ( ) ; } columnSortChanged ( ) { const data = [ ] ; if ( this . panel . sortByColumns . length > 0 ) { for ( let i = 0 ; i < this . panel . sortByColumns . length ; i ++ ) { const columnData = this . panel . sortByColumns [ i ] . columnData ; let columnNumber = 0 ; columnNumber = parseInt ( columnData , 10 ) ; if ( Number . isNaN ( columnNumber ) ) { columnNumber = 0 ; for ( let j = 0 ; j < this . table . columns . length ; j ++ ) { if ( this . table . columns [ j ] . text === columnData ) { columnNumber = j ; break ; } } } const sortDirection = this . panel . sortByColumns [ i ] . sortMethod ; data . push ( [ columnNumber , sortDirection ] ) ; } } else { data . push ( [ 0 , 'desc' ] ) ; } if ( this . panel . rowNumbersEnabled ) { data . map ( ( sortData ) => [ sortData [ 0 ] ++ , sortData [ 1 ] ] ) ; } this . panel . sortByColumnsData = data ; this . render ( ) ; } addColumnAlias ( ) { const defaultAlias = { name : '' , alias : '' , } ; this . panel . columnAliases . push ( angular . copy ( defaultAlias ) ) ; this . columnAliasChanged ( ) ; } removeColumnAlias ( column ) { this . panel . columnAliases = _ . without ( this . panel . columnAliases , column ) ; this . columnAliasChanged ( ) ; } columnAliasChanged ( ) { this . render ( ) ; } addColumnWidthHint ( ) { const defaultHint = { name : '' , width : '80px' , } ; this . panel . columnWidthHints . push ( angular . copy ( defaultHint ) ) ; this . columnWidthHintsChanged ( ) ; } removeColumnWidthHint ( column ) { this . panel . columnWidthHints = _ . without ( this . panel . columnWidthHints , column ) ; this . columnWidthHintsChanged ( ) ; } columnWidthHintsChanged ( ) { this . render ( ) ; } addValueMap ( style ) { if ( ! style . valueMaps ) { style . valueMaps = [ ] ; } style . valueMaps . push ( { value : '' , text : '' } ) ; this . render ( ) ; } removeValueMap ( style , index ) { style . valueMaps . splice ( index , 1 ) ; this . render ( ) ; } addRangeMap ( style ) { if ( ! style . rangeMaps ) { style . rangeMaps = [ ] ; } style . rangeMaps . push ( { from : '' , to : '' , text : '' } ) ; this . render ( ) ; } removeRangeMap ( style , index ) { style . rangeMaps . splice ( index , 1 ) ; this . render ( ) ; } }