renderer.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. ///<reference path="../../../headers/common.d.ts" />
  2. import _ from 'lodash';
  3. import moment from 'moment';
  4. import kbn from 'app/core/utils/kbn';
  5. export class TableRenderer {
  6. formatters: any[];
  7. colorState: any;
  8. constructor(private panel, private table, private isUtc, private sanitize, private templateSrv) {
  9. this.initColumns();
  10. }
  11. setTable(table) {
  12. this.table = table;
  13. this.initColumns();
  14. }
  15. initColumns() {
  16. this.formatters = [];
  17. this.colorState = {};
  18. for (let colIndex = 0; colIndex < this.table.columns.length; colIndex++) {
  19. let column = this.table.columns[colIndex];
  20. column.title = column.text;
  21. for (let i = 0; i < this.panel.styles.length; i++) {
  22. let style = this.panel.styles[i];
  23. var regex = kbn.stringToJsRegex(style.pattern);
  24. if (column.text.match(regex)) {
  25. column.style = style;
  26. if (style.alias) {
  27. column.title = column.text.replace(regex, style.alias);
  28. }
  29. break;
  30. }
  31. }
  32. this.formatters[colIndex] = this.createColumnFormatter(column);
  33. }
  34. }
  35. getColorForValue(value, style) {
  36. if (!style.thresholds) { return null; }
  37. for (var i = style.thresholds.length; i > 0; i--) {
  38. if (value >= style.thresholds[i - 1]) {
  39. return style.colors[i];
  40. }
  41. }
  42. return _.first(style.colors);
  43. }
  44. defaultCellFormatter(v, style) {
  45. if (v === null || v === void 0 || v === undefined) {
  46. return '';
  47. }
  48. if (_.isArray(v)) {
  49. v = v.join(', ');
  50. }
  51. if (style && style.sanitize) {
  52. return this.sanitize(v);
  53. } else {
  54. return _.escape(v);
  55. }
  56. }
  57. createColumnFormatter(column) {
  58. if (!column.style) {
  59. return this.defaultCellFormatter;
  60. }
  61. if (column.style.type === 'hidden') {
  62. return v => {
  63. return undefined;
  64. };
  65. }
  66. if (column.style.type === 'date') {
  67. return v => {
  68. if (v === undefined || v === null) {
  69. return '-';
  70. }
  71. if (_.isArray(v)) { v = v[0]; }
  72. var date = moment(v);
  73. if (this.isUtc) {
  74. date = date.utc();
  75. }
  76. return date.format(column.style.dateFormat);
  77. };
  78. }
  79. if (column.style.type === 'number') {
  80. let valueFormatter = kbn.valueFormats[column.unit || column.style.unit];
  81. return v => {
  82. if (v === null || v === void 0) {
  83. return '-';
  84. }
  85. if (_.isString(v)) {
  86. return this.defaultCellFormatter(v, column.style);
  87. }
  88. if (column.style.colorMode) {
  89. this.colorState[column.style.colorMode] = this.getColorForValue(v, column.style);
  90. }
  91. return valueFormatter(v, column.style.decimals, null);
  92. };
  93. }
  94. return (value) => {
  95. return this.defaultCellFormatter(value, column.style);
  96. };
  97. }
  98. renderRowVariables(rowIndex) {
  99. let scopedVars = {};
  100. let cell_variable;
  101. let row = this.table.rows[rowIndex];
  102. for (let i = 0; i < row.length; i++) {
  103. cell_variable = `__cell_${i}`;
  104. scopedVars[cell_variable] = { value: row[i] };
  105. }
  106. return scopedVars;
  107. }
  108. formatColumnValue(colIndex, value) {
  109. return this.formatters[colIndex] ? this.formatters[colIndex](value) : value;
  110. }
  111. renderCell(columnIndex, rowIndex, value, addWidthHack = false) {
  112. value = this.formatColumnValue(columnIndex, value);
  113. var column = this.table.columns[columnIndex];
  114. var style = '';
  115. var cellClasses = [];
  116. var cellClass = '';
  117. if (this.colorState.cell) {
  118. style = ' style="background-color:' + this.colorState.cell + ';color: white"';
  119. this.colorState.cell = null;
  120. } else if (this.colorState.value) {
  121. style = ' style="color:' + this.colorState.value + '"';
  122. this.colorState.value = null;
  123. }
  124. // because of the fixed table headers css only solution
  125. // there is an issue if header cell is wider the cell
  126. // this hack adds header content to cell (not visible)
  127. var columnHtml = '';
  128. if (addWidthHack) {
  129. columnHtml = '<div class="table-panel-width-hack">' + this.table.columns[columnIndex].title + '</div>';
  130. }
  131. if (value === undefined) {
  132. style = ' style="display:none;"';
  133. column.hidden = true;
  134. } else {
  135. column.hidden = false;
  136. }
  137. if (column.style && column.style.preserveFormat) {
  138. cellClasses.push("table-panel-cell-pre");
  139. }
  140. if (column.style && column.style.link) {
  141. // Render cell as link
  142. var scopedVars = this.renderRowVariables(rowIndex);
  143. scopedVars['__cell'] = { value: value };
  144. var cellLink = this.templateSrv.replace(column.style.linkUrl, scopedVars);
  145. var cellLinkTooltip = this.templateSrv.replace(column.style.linkTooltip, scopedVars);
  146. var cellTarget = column.style.linkTargetBlank ? '_blank' : '';
  147. cellClasses.push("table-panel-cell-link");
  148. columnHtml += `
  149. <a href="${cellLink}" target="${cellTarget}" data-link-tooltip data-original-title="${cellLinkTooltip}" data-placement="right">
  150. ${value}
  151. </a>
  152. `;
  153. } else {
  154. columnHtml += value;
  155. }
  156. if (column.filterable) {
  157. cellClasses.push("table-panel-cell-filterable");
  158. columnHtml += `
  159. <a class="table-panel-filter-link" data-link-tooltip data-original-title="Filter out value" data-placement="bottom"
  160. data-row="${rowIndex}" data-column="${columnIndex}" data-operator="!=">
  161. <i class="fa fa-search-minus"></i>
  162. </a>
  163. <a class="table-panel-filter-link" data-link-tooltip data-original-title="Filter for value" data-placement="bottom"
  164. data-row="${rowIndex}" data-column="${columnIndex}" data-operator="=">
  165. <i class="fa fa-search-plus"></i>
  166. </a>`;
  167. }
  168. if (cellClasses.length) {
  169. cellClass = ' class="' + cellClasses.join(' ') + '"';
  170. }
  171. columnHtml = '<td' + cellClass + style + '>' + columnHtml + '</td>';
  172. return columnHtml;
  173. }
  174. render(page) {
  175. let pageSize = this.panel.pageSize || 100;
  176. let startPos = page * pageSize;
  177. let endPos = Math.min(startPos + pageSize, this.table.rows.length);
  178. var html = "";
  179. for (var y = startPos; y < endPos; y++) {
  180. let row = this.table.rows[y];
  181. let cellHtml = '';
  182. let rowStyle = '';
  183. for (var i = 0; i < this.table.columns.length; i++) {
  184. cellHtml += this.renderCell(i, y, row[i], y === startPos);
  185. }
  186. if (this.colorState.row) {
  187. rowStyle = ' style="background-color:' + this.colorState.row + ';color: white"';
  188. this.colorState.row = null;
  189. }
  190. html += '<tr ' + rowStyle + '>' + cellHtml + '</tr>';
  191. }
  192. return html;
  193. }
  194. render_values() {
  195. let rows = [];
  196. for (var y = 0; y < this.table.rows.length; y++) {
  197. let row = this.table.rows[y];
  198. let new_row = [];
  199. for (var i = 0; i < this.table.columns.length; i++) {
  200. new_row.push(this.formatColumnValue(i, row[i]));
  201. }
  202. rows.push(new_row);
  203. }
  204. return {
  205. columns: this.table.columns,
  206. rows: rows,
  207. };
  208. }
  209. }