The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/plugins/panel/table/module.ts

276 lines
7.2 KiB

import _ from 'lodash';
import $ from 'jquery';
import { MetricsPanelCtrl } from 'app/plugins/sdk';
import { transformDataToTable } from './transformers';
import { tablePanelEditor } from './editor';
import { columnOptionsTab } from './column_options';
import { TableRenderer } from './renderer';
class TablePanelCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
pageIndex: number;
dataRaw: any;
table: any;
renderer: any;
panelDefaults = {
targets: [{}],
transform: 'timeseries_to_columns',
pageSize: null,
showHeader: true,
styles: [
{
type: 'date',
pattern: 'Time',
alias: 'Time',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
},
{
unit: 'short',
type: 'number',
alias: '',
decimals: 2,
colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'],
colorMode: null,
pattern: '/.*/',
thresholds: [],
},
],
columns: [],
scroll: true,
fontSize: '100%',
sort: { col: 0, desc: true },
};
/** @ngInject */
constructor($scope, $injector, templateSrv, private annotationsSrv, private $sanitize, private variableSrv) {
super($scope, $injector);
this.pageIndex = 0;
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, this.panelDefaults);
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));
}
onInitEditMode() {
this.addEditorTab('Options', tablePanelEditor, 2);
this.addEditorTab('Column Styles', columnOptionsTab, 3);
}
onInitPanelActions(actions) {
actions.push({ text: 'Export CSV', click: 'ctrl.exportCsv()' });
}
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;
// automatically correct transform mode based on data
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.renderer = new TableRenderer(
this.panel,
this.table,
this.dashboard.isTimezoneUtc(),
this.$sanitize,
this.templateSrv
);
return super.render(this.table);
}
toggleColumnSort(col, colIndex) {
// remove sort flag from current column
if (this.table.columns[this.panel.sort.col]) {
this.table.columns[this.panel.sort.col].sort = false;
}
if (this.panel.sort.col === colIndex) {
if (this.panel.sort.desc) {
this.panel.sort.desc = false;
} else {
this.panel.sort.col = null;
}
} else {
this.panel.sort.col = colIndex;
this.panel.sort.desc = true;
}
this.render();
}
moveQuery(target, direction) {
super.moveQuery(target, direction);
super.refresh();
}
exportCsv() {
const scope = this.$scope.$new(true);
scope.tableData = this.renderer.render_values();
scope.panel = 'table';
this.publishAppEvent('show-modal', {
templateHtml: '<export-data-modal panel="panel" data="tableData"></export-data-modal>',
scope,
modalClass: 'modal--narrow',
});
}
link(scope, elem, attrs, ctrl: TablePanelCtrl) {
var data;
const panel = ctrl.panel;
var pageCount = 0;
function getTableHeight() {
var panelHeight = ctrl.height;
if (pageCount > 1) {
panelHeight -= 26;
}
return panelHeight - 31 + 'px';
}
function appendTableRows(tbodyElem) {
ctrl.renderer.setTable(data);
tbodyElem.empty();
tbodyElem.html(ctrl.renderer.render(ctrl.pageIndex));
}
function switchPage(e) {
const el = $(e.currentTarget);
ctrl.pageIndex = parseInt(el.text(), 10) - 1;
renderPanel();
}
function appendPaginationControls(footerElem) {
footerElem.empty();
const pageSize = panel.pageSize || 100;
pageCount = Math.ceil(data.rows.length / pageSize);
if (pageCount === 1) {
return;
}
const startPage = Math.max(ctrl.pageIndex - 3, 0);
const endPage = Math.min(pageCount, startPage + 9);
const paginationList = $('<ul></ul>');
for (var i = startPage; i < endPage; i++) {
const activeClass = i === ctrl.pageIndex ? 'active' : '';
const pageLinkElem = $(
'<li><a class="table-panel-page-link pointer ' + activeClass + '">' + (i + 1) + '</a></li>'
);
paginationList.append(pageLinkElem);
}
footerElem.append(paginationList);
}
function renderPanel() {
const panelElem = elem.parents('.panel-content');
const rootElem = elem.find('.table-panel-scroll');
const tbodyElem = elem.find('tbody');
const footerElem = elem.find('.table-panel-footer');
elem.css({ 'font-size': panel.fontSize });
panelElem.addClass('table-panel-content');
appendTableRows(tbodyElem);
appendPaginationControls(footerElem);
rootElem.css({ 'max-height': panel.scroll ? getTableHeight() : '' });
}
// hook up link tooltips
elem.tooltip({
selector: '[data-link-tooltip]',
});
function addFilterClicked(e) {
const filterData = $(e.currentTarget).data();
const options = {
datasource: panel.datasource,
key: data.columns[filterData.column].text,
value: data.rows[filterData.row][filterData.column],
operator: filterData.operator,
};
ctrl.variableSrv.setAdhocFilter(options);
}
elem.on('click', '.table-panel-page-link', switchPage);
elem.on('click', '.table-panel-filter-link', addFilterClicked);
const unbindDestroy = scope.$on('$destroy', function() {
elem.off('click', '.table-panel-page-link');
elem.off('click', '.table-panel-filter-link');
unbindDestroy();
});
ctrl.events.on('render', function(renderData) {
data = renderData || data;
if (data) {
renderPanel();
}
ctrl.renderingCompleted();
});
}
}
export { TablePanelCtrl, TablePanelCtrl as PanelCtrl };