diff --git a/public/app/plugins/panel/table/column_options.html b/public/app/plugins/panel/table/column_options.html
index db4758ab8f3..53e3c477346 100644
--- a/public/app/plugins/panel/table/column_options.html
+++ b/public/app/plugins/panel/table/column_options.html
@@ -27,6 +27,7 @@
Column Header
+
@@ -91,6 +92,35 @@
+
+
diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts
index 8acf14f56ca..375d3a0a0ae 100644
--- a/public/app/plugins/panel/table/module.ts
+++ b/public/app/plugins/panel/table/module.ts
@@ -10,6 +10,7 @@ import {transformDataToTable} from './transformers';
import {tablePanelEditor} from './editor';
import {columnOptionsTab} from './column_options';
import {TableRenderer} from './renderer';
+import Drop from 'tether-drop';
class TablePanelCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
@@ -49,7 +50,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
};
/** @ngInject */
- constructor($scope, $injector, private annotationsSrv, private $sanitize) {
+ constructor($scope, $injector, templateSrv, private annotationsSrv, private $sanitize) {
super($scope, $injector);
this.pageIndex = 0;
@@ -123,7 +124,7 @@ class TablePanelCtrl extends MetricsPanelCtrl {
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.renderer = new TableRenderer(this.panel, this.table, this.dashboard.isTimezoneUtc(), this.$sanitize, this.templateSrv);
return super.render(this.table);
}
@@ -217,6 +218,11 @@ class TablePanelCtrl extends MetricsPanelCtrl {
rootElem.css({'max-height': panel.scroll ? getTableHeight() : '' });
}
+ // hook up link tooltips
+ elem.tooltip({
+ selector: '[data-link-tooltip]'
+ });
+
elem.on('click', '.table-panel-page-link', switchPage);
var unbindDestroy = scope.$on('$destroy', function() {
diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts
index dc031df8d1e..ef53b1d489b 100644
--- a/public/app/plugins/panel/table/renderer.ts
+++ b/public/app/plugins/panel/table/renderer.ts
@@ -8,7 +8,7 @@ export class TableRenderer {
formatters: any[];
colorState: any;
- constructor(private panel, private table, private isUtc, private sanitize) {
+ constructor(private panel, private table, private isUtc, private sanitize, private templateSrv) {
this.initColumns();
}
@@ -123,13 +123,25 @@ export class TableRenderer {
};
}
+ renderRowVariables(rowIndex) {
+ let scopedVars = {};
+ let cell_variable;
+ let row = this.table.rows[rowIndex];
+ for (let i = 0; i < row.length; i++) {
+ cell_variable = `__cell_${i}`;
+ scopedVars[cell_variable] = { value: row[i] };
+ }
+ return scopedVars;
+ }
+
formatColumnValue(colIndex, value) {
return this.formatters[colIndex] ? this.formatters[colIndex](value) : value;
}
- renderCell(columnIndex, value, addWidthHack = false) {
+ renderCell(columnIndex, rowIndex, value, addWidthHack = false) {
value = this.formatColumnValue(columnIndex, value);
var style = '';
+ var cellClasses = [];
var cellClass = '';
if (this.colorState.cell) {
style = ' style="background-color:' + this.colorState.cell + ';color: white"';
@@ -156,10 +168,34 @@ export class TableRenderer {
var columnStyle = this.table.columns[columnIndex].style;
if (columnStyle && columnStyle.preserveFormat) {
- cellClass = ' class="table-panel-cell-pre" ';
+ cellClasses.push("table-panel-cell-pre");
}
- return '' + value + widthHack + ' ';
+ var columnHtml = value + widthHack;
+
+ if (columnStyle && columnStyle.link) {
+ // Render cell as link
+ var scopedVars = this.renderRowVariables(rowIndex);
+ scopedVars['__cell'] = { value: value };
+
+ var cellLink = this.templateSrv.replace(columnStyle.linkUrl, scopedVars);
+ var cellLinkTooltip = this.templateSrv.replace(columnStyle.linkTooltip, scopedVars);
+ var cellTarget = columnStyle.linkTargetBlank ? '_blank' : '';
+
+ cellClasses.push("table-panel-cell-link");
+ columnHtml = `
+
+ ${columnHtml}
+
+ `;
+ }
+
+ if (cellClasses.length) {
+ cellClass = ' class="' + cellClasses.join(' ') + '"';
+ }
+
+ columnHtml = '' + columnHtml + ' ';
+ return columnHtml;
}
render(page) {
@@ -173,7 +209,7 @@ export class TableRenderer {
let cellHtml = '';
let rowStyle = '';
for (var i = 0; i < this.table.columns.length; i++) {
- cellHtml += this.renderCell(i, row[i], y === startPos);
+ cellHtml += this.renderCell(i, y, row[i], y === startPos);
}
if (this.colorState.row) {
diff --git a/public/app/plugins/panel/table/specs/renderer_specs.ts b/public/app/plugins/panel/table/specs/renderer_specs.ts
index 6b031d4bb91..71a5a776803 100644
--- a/public/app/plugins/panel/table/specs/renderer_specs.ts
+++ b/public/app/plugins/panel/table/specs/renderer_specs.ts
@@ -1,5 +1,6 @@
import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
+import _ from 'lodash';
import TableModel from 'app/core/table_model';
import {TableRenderer} from '../renderer';
@@ -14,6 +15,10 @@ describe('when rendering table', () => {
{text: 'String'},
{text: 'United', unit: 'bps'},
{text: 'Sanitized'},
+ {text: 'Link'},
+ ];
+ table.rows = [
+ [1388556366666, 1230, 40, undefined, "", "", "my.host.com", "host1"]
];
var panel = {
@@ -55,6 +60,14 @@ describe('when rendering table', () => {
pattern: 'Sanitized',
type: 'string',
sanitize: true,
+ },
+ {
+ pattern: 'Link',
+ type: 'string',
+ link: true,
+ linkUrl: "/dashboard?param=$__cell¶m_1=$__cell_1¶m_2=$__cell_2",
+ linkTooltip: "$__cell $__cell_1 $__cell_6",
+ linkTargetBlank: true
}
]
};
@@ -63,75 +76,87 @@ describe('when rendering table', () => {
return 'sanitized';
};
- var renderer = new TableRenderer(panel, table, 'utc', sanitize);
+ var templateSrv = {
+ replace: function(value, scopedVars) {
+ if (scopedVars) {
+ // For testing variables replacement in link
+ _.each(scopedVars, function(val, key) {
+ value = value.replace('$' + key, val.value);
+ });
+ }
+ return value;
+ }
+ };
+
+ var renderer = new TableRenderer(panel, table, 'utc', sanitize, templateSrv);
it('time column should be formated', () => {
- var html = renderer.renderCell(0, 1388556366666);
+ var html = renderer.renderCell(0, 0, 1388556366666);
expect(html).to.be('2014-01-01T06:06:06Z ');
});
it('undefined time column should be rendered as -', () => {
- var html = renderer.renderCell(0, undefined);
+ var html = renderer.renderCell(0, 0, undefined);
expect(html).to.be('- ');
});
it('null time column should be rendered as -', () => {
- var html = renderer.renderCell(0, null);
+ var html = renderer.renderCell(0, 0, null);
expect(html).to.be('- ');
});
it('number column with unit specified should ignore style unit', () => {
- var html = renderer.renderCell(5, 1230);
+ var html = renderer.renderCell(5, 0, 1230);
expect(html).to.be('1.23 kbps ');
});
it('number column should be formated', () => {
- var html = renderer.renderCell(1, 1230);
+ var html = renderer.renderCell(1, 0, 1230);
expect(html).to.be('1.230 s ');
});
it('number style should ignore string values', () => {
- var html = renderer.renderCell(1, 'asd');
+ var html = renderer.renderCell(1, 0, 'asd');
expect(html).to.be('asd ');
});
it('colored cell should have style', () => {
- var html = renderer.renderCell(2, 40);
+ var html = renderer.renderCell(2, 0, 40);
expect(html).to.be('40.0 ');
});
it('colored cell should have style', () => {
- var html = renderer.renderCell(2, 55);
+ var html = renderer.renderCell(2, 0, 55);
expect(html).to.be('55.0 ');
});
it('colored cell should have style', () => {
- var html = renderer.renderCell(2, 85);
+ var html = renderer.renderCell(2, 0, 85);
expect(html).to.be('85.0 ');
});
it('unformated undefined should be rendered as string', () => {
- var html = renderer.renderCell(3, 'value');
+ var html = renderer.renderCell(3, 0, 'value');
expect(html).to.be('value ');
});
it('string style with escape html should return escaped html', () => {
- var html = renderer.renderCell(4, "&breaking the row");
+ var html = renderer.renderCell(4, 0, "&breaking the row");
expect(html).to.be('&breaking <br /> the <br /> row ');
});
it('undefined formater should return escaped html', () => {
- var html = renderer.renderCell(3, "&breaking the row");
+ var html = renderer.renderCell(3, 0, "&breaking the row");
expect(html).to.be('&breaking <br /> the <br /> row ');
});
it('undefined value should render as -', () => {
- var html = renderer.renderCell(3, undefined);
+ var html = renderer.renderCell(3, 0, undefined);
expect(html).to.be(' ');
});
it('sanitized value should render as', () => {
- var html = renderer.renderCell(6, 'text link ');
+ var html = renderer.renderCell(6, 0, 'text link ');
expect(html).to.be('sanitized ');
});
@@ -146,7 +171,22 @@ describe('when rendering table', () => {
it('Colored column title should be Colored', () => {
expect(table.columns[2].title).to.be('Colored');
});
+
+ it('link should render as', () => {
+ var html = renderer.renderCell(7, 0, 'host1');
+ var expectedHtml = `
+
+
+ host1
+
+
+ `;
+ expect(normalize(html)).to.be(normalize(expectedHtml));
+ });
});
});
-
+function normalize(str) {
+ return str.replace(/\s+/gm, ' ').trim();
+}
diff --git a/public/sass/components/_panel_table.scss b/public/sass/components/_panel_table.scss
index 17dbc910b2d..073d8be4abf 100644
--- a/public/sass/components/_panel_table.scss
+++ b/public/sass/components/_panel_table.scss
@@ -76,6 +76,21 @@
&.table-panel-cell-pre {
white-space: pre;
}
+
+ &.table-panel-cell-link {
+ // Expand internal div to cell size (make all cell clickable)
+ padding: 0;
+
+ a {
+ padding: 0.45em 0 0.45em 1.1em;
+ height: 100%;
+ width: 100%;
+ }
+ }
+
+ &.cell-highlighted:hover {
+ background-color: $tight-form-func-bg;
+ }
}
}