Table.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. "use strict";
  2. exports.__esModule = true;
  3. exports.default = void 0;
  4. var _d = _interopRequireDefault(require("d3"));
  5. var _propTypes = _interopRequireDefault(require("prop-types"));
  6. var _dataTables = _interopRequireDefault(require("datatables.net-bs/js/dataTables.bootstrap"));
  7. var _dompurify = _interopRequireDefault(require("dompurify"));
  8. var _numberFormat = require("@superset-ui/number-format");
  9. var _timeFormat = require("@superset-ui/time-format");
  10. var _fixTableHeight = _interopRequireDefault(require("./utils/fixTableHeight"));
  11. require("datatables.net-bs/css/dataTables.bootstrap.css");
  12. require("./Table.css");
  13. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  14. /**
  15. * Licensed to the Apache Software Foundation (ASF) under one
  16. * or more contributor license agreements. See the NOTICE file
  17. * distributed with this work for additional information
  18. * regarding copyright ownership. The ASF licenses this file
  19. * to you under the Apache License, Version 2.0 (the
  20. * "License"); you may not use this file except in compliance
  21. * with the License. You may obtain a copy of the License at
  22. *
  23. * http://www.apache.org/licenses/LICENSE-2.0
  24. *
  25. * Unless required by applicable law or agreed to in writing,
  26. * software distributed under the License is distributed on an
  27. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  28. * KIND, either express or implied. See the License for the
  29. * specific language governing permissions and limitations
  30. * under the License.
  31. */
  32. /* eslint-disable func-names, no-negated-condition */
  33. /* eslint-disable prefer-destructuring */
  34. /* eslint-disable react/sort-prop-types */
  35. if (window.$) {
  36. (0, _dataTables.default)(window, window.$);
  37. }
  38. const $ = window.$ || _dataTables.default.$;
  39. const propTypes = {
  40. // Each object is { field1: value1, field2: value2 }
  41. data: _propTypes.default.arrayOf(_propTypes.default.object),
  42. height: _propTypes.default.number,
  43. alignPositiveNegative: _propTypes.default.bool,
  44. colorPositiveNegative: _propTypes.default.bool,
  45. columns: _propTypes.default.arrayOf(_propTypes.default.shape({
  46. key: _propTypes.default.string,
  47. label: _propTypes.default.string,
  48. format: _propTypes.default.string
  49. })),
  50. filters: _propTypes.default.object,
  51. includeSearch: _propTypes.default.bool,
  52. metrics: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.object])),
  53. onAddFilter: _propTypes.default.func,
  54. onRemoveFilter: _propTypes.default.func,
  55. orderDesc: _propTypes.default.bool,
  56. pageLength: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]),
  57. percentMetrics: _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.object])),
  58. tableFilter: _propTypes.default.bool,
  59. tableTimestampFormat: _propTypes.default.string,
  60. timeseriesLimitMetric: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.object])
  61. };
  62. const formatValue = (0, _numberFormat.getNumberFormatter)(_numberFormat.NumberFormats.INTEGER);
  63. const formatPercent = (0, _numberFormat.getNumberFormatter)(_numberFormat.NumberFormats.PERCENT_3_POINT);
  64. const NOOP = () => {};
  65. function TableVis(element, props) {
  66. const {
  67. data,
  68. height,
  69. alignPositiveNegative = false,
  70. colorPositiveNegative = false,
  71. columns,
  72. filters = {},
  73. includeSearch = false,
  74. metrics: rawMetrics,
  75. onAddFilter = NOOP,
  76. onRemoveFilter = NOOP,
  77. orderDesc,
  78. pageLength,
  79. percentMetrics,
  80. tableFilter,
  81. tableTimestampFormat,
  82. timeseriesLimitMetric
  83. } = props;
  84. const $container = $(element);
  85. $container.addClass('superset-legacy-chart-table');
  86. const metrics = (rawMetrics || []).map(m => m.label || m) // Add percent metrics
  87. .concat((percentMetrics || []).map(m => "%" + m)) // Removing metrics (aggregates) that are strings
  88. .filter(m => typeof data[0][m] === 'number');
  89. function col(c) {
  90. const arr = [];
  91. data.forEach(row => {
  92. arr.push(row[c]);
  93. });
  94. return arr;
  95. }
  96. const maxes = {};
  97. const mins = {};
  98. metrics.forEach(metric => {
  99. if (alignPositiveNegative) {
  100. maxes[metric] = _d.default.max(col(metric).map(Math.abs));
  101. } else {
  102. maxes[metric] = _d.default.max(col(metric));
  103. mins[metric] = _d.default.min(col(metric));
  104. }
  105. });
  106. const tsFormatter = (0, _timeFormat.getTimeFormatter)(tableTimestampFormat);
  107. const div = _d.default.select(element);
  108. div.html('');
  109. const table = div.append('table').classed('dataframe dataframe table table-striped ' + 'table-condensed table-hover dataTable no-footer', true).attr('width', '100%');
  110. table.append('thead').append('tr').selectAll('th').data(columns.map(c => c.label)).enter().append('th').text(d => d);
  111. table.append('tbody').selectAll('tr').data(data).enter().append('tr').selectAll('td').data(row => columns.map(({
  112. key,
  113. format
  114. }) => {
  115. const val = row[key];
  116. let html;
  117. const isMetric = metrics.includes(key);
  118. if (key === '__timestamp') {
  119. html = tsFormatter(val);
  120. }
  121. if (typeof val === 'string') {
  122. html = "<span class=\"like-pre\">" + _dompurify.default.sanitize(val) + "</span>";
  123. }
  124. if (isMetric) {
  125. html = (0, _numberFormat.getNumberFormatter)(format)(val);
  126. }
  127. if (key[0] === '%') {
  128. html = formatPercent(val);
  129. }
  130. return {
  131. col: key,
  132. val,
  133. html,
  134. isMetric
  135. };
  136. })).enter().append('td').style('background-image', d => {
  137. if (d.isMetric) {
  138. const r = colorPositiveNegative && d.val < 0 ? 150 : 0;
  139. if (alignPositiveNegative) {
  140. const perc = Math.abs(Math.round(d.val / maxes[d.col] * 100)); // The 0.01 to 0.001 is a workaround for what appears to be a
  141. // CSS rendering bug on flat, transparent colors
  142. return "linear-gradient(to right, rgba(" + r + ",0,0,0.2), rgba(" + r + ",0,0,0.2) " + perc + "%, " + ("rgba(0,0,0,0.01) " + perc + "%, rgba(0,0,0,0.001) 100%)");
  143. }
  144. const posExtent = Math.abs(Math.max(maxes[d.col], 0));
  145. const negExtent = Math.abs(Math.min(mins[d.col], 0));
  146. const tot = posExtent + negExtent;
  147. const perc1 = Math.round(Math.min(negExtent + d.val, negExtent) / tot * 100);
  148. const perc2 = Math.round(Math.abs(d.val) / tot * 100); // The 0.01 to 0.001 is a workaround for what appears to be a
  149. // CSS rendering bug on flat, transparent colors
  150. return "linear-gradient(to right, rgba(0,0,0,0.01), rgba(0,0,0,0.001) " + perc1 + "%, " + ("rgba(" + r + ",0,0,0.2) " + perc1 + "%, rgba(" + r + ",0,0,0.2) " + (perc1 + perc2) + "%, ") + ("rgba(0,0,0,0.01) " + (perc1 + perc2) + "%, rgba(0,0,0,0.001) 100%)");
  151. }
  152. return null;
  153. }).classed('text-right', d => d.isMetric).attr('title', d => {
  154. if (typeof d.val === 'string') {
  155. return d.val;
  156. }
  157. if (!Number.isNaN(d.val)) {
  158. return formatValue(d.val);
  159. }
  160. return null;
  161. }).attr('data-sort', d => d.isMetric ? d.val : null) // Check if the dashboard currently has a filter for each row
  162. .classed('filtered', d => filters && filters[d.col] && filters[d.col].includes(d.val)).on('click', function (d) {
  163. if (!d.isMetric && tableFilter) {
  164. const td = _d.default.select(this);
  165. if (td.classed('filtered')) {
  166. onRemoveFilter(d.col, [d.val]);
  167. _d.default.select(this).classed('filtered', false);
  168. } else {
  169. _d.default.select(this).classed('filtered', true);
  170. onAddFilter(d.col, [d.val]);
  171. }
  172. }
  173. }).style('cursor', d => !d.isMetric ? 'pointer' : '').html(d => d.html ? d.html : d.val);
  174. const paging = pageLength && pageLength > 0;
  175. const datatable = $container.find('.dataTable').DataTable({
  176. paging,
  177. pageLength,
  178. aaSorting: [],
  179. searching: includeSearch,
  180. bInfo: false,
  181. scrollY: height + "px",
  182. scrollCollapse: true,
  183. scrollX: true
  184. });
  185. (0, _fixTableHeight.default)($container.find('.dataTables_wrapper'), height); // Sorting table by main column
  186. let sortBy;
  187. const limitMetric = Array.isArray(timeseriesLimitMetric) ? timeseriesLimitMetric[0] : timeseriesLimitMetric;
  188. if (limitMetric) {
  189. // Sort by as specified
  190. sortBy = limitMetric.label || limitMetric;
  191. } else if (metrics.length > 0) {
  192. // If not specified, use the first metric from the list
  193. sortBy = metrics[0];
  194. }
  195. if (sortBy) {
  196. const keys = columns.map(c => c.key);
  197. const index = keys.indexOf(sortBy);
  198. datatable.column(index).order(orderDesc ? 'desc' : 'asc');
  199. if (!metrics.includes(sortBy)) {
  200. // Hiding the sortBy column if not in the metrics list
  201. datatable.column(index).visible(false);
  202. }
  203. }
  204. datatable.draw();
  205. }
  206. TableVis.displayName = 'TableVis';
  207. TableVis.propTypes = propTypes;
  208. var _default = TableVis;
  209. exports.default = _default;