UNPKG

@superset-ui/legacy-plugin-chart-time-table

Version:
295 lines (251 loc) 9.63 kB
"use strict"; exports.__esModule = true; exports.default = void 0; var _propTypes = _interopRequireDefault(require("prop-types")); var _react = _interopRequireDefault(require("react")); var _mustache = _interopRequireDefault(require("mustache")); var _d3Scale = require("d3-scale"); var _reactableArc = require("reactable-arc"); var _core = require("@superset-ui/core"); var _chartControls = require("@superset-ui/chart-controls"); var _moment = _interopRequireDefault(require("moment")); var _FormattedNumber = _interopRequireDefault(require("./FormattedNumber")); var _SparklineCell = _interopRequireDefault(require("./SparklineCell")); var _react2 = require("@emotion/react"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ const ACCESSIBLE_COLOR_BOUNDS = ['#ca0020', '#0571b0']; function colorFromBounds(value, bounds, colorBounds = ACCESSIBLE_COLOR_BOUNDS) { if (bounds) { const [min, max] = bounds; const [minColor, maxColor] = colorBounds; if (min !== null && max !== null) { const colorScale = (0, _d3Scale.scaleLinear)().domain([min, (max + min) / 2, max]) // @ts-ignore .range([minColor, 'grey', maxColor]); // @ts-ignore return colorScale(value); } if (min !== null) { // @ts-ignore return value >= min ? maxColor : minColor; } if (max !== null) { // @ts-ignore return value < max ? maxColor : minColor; } } return null; } class TimeTable extends _react.default.PureComponent { renderLeftCell(row) { const { rowType, url } = this.props; const context = { metric: row }; const fullUrl = url ? _mustache.default.render(url, context) : null; if (rowType === 'column') { const column = row; if (fullUrl) { return (0, _react2.jsx)("a", { href: fullUrl, rel: "noopener noreferrer", target: "_blank" }, column.label); } return column.label; } return (0, _react2.jsx)(_chartControls.MetricOption, { openInNewWindow: true, metric: row, url: fullUrl, showFormula: false }); } // eslint-disable-next-line class-methods-use-this renderSparklineCell(valueField, column, entries) { let sparkData; if (column.timeRatio) { // Period ratio sparkline sparkData = []; for (let i = column.timeRatio; i < entries.length; i += 1) { const prevData = entries[i - column.timeRatio][valueField]; if (prevData && prevData !== 0) { sparkData.push(entries[i][valueField] / prevData); } else { // @ts-ignore sparkData.push(null); } } } else { sparkData = entries.map(d => d[valueField]); } return (0, _react2.jsx)(_reactableArc.Td, { key: column.key, column: column.key, value: sparkData[sparkData.length - 1] }, (0, _react2.jsx)(_SparklineCell.default, { width: parseInt(column.width, 10) || 300, height: parseInt(column.height, 10) || 50, data: sparkData, ariaLabel: `spark-${valueField}`, numberFormat: column.d3format, yAxisBounds: column.yAxisBounds, showYAxis: column.showYAxis, renderTooltip: ({ index }) => (0, _react2.jsx)("div", null, (0, _react2.jsx)("strong", null, (0, _core.formatNumber)(column.d3format, sparkData[index])), (0, _react2.jsx)("div", null, (0, _core.formatTime)(column.dateFormat, _moment.default.utc(entries[index].time).toDate()))) })); } // eslint-disable-next-line class-methods-use-this renderValueCell(valueField, column, reversedEntries) { const recent = reversedEntries[0][valueField]; let v = 0; let errorMsg; if (column.colType === 'time') { // Time lag ratio const timeLag = column.timeLag || 0; const totalLag = Object.keys(reversedEntries).length; if (timeLag >= totalLag) { errorMsg = `The time lag set at ${timeLag} is too large for the length of data at ${reversedEntries.length}. No data available.`; } else { v = reversedEntries[timeLag][valueField]; } if (column.comparisonType === 'diff') { v = recent - v; } else if (column.comparisonType === 'perc') { v = recent / v; } else if (column.comparisonType === 'perc_change') { v = recent / v - 1; } v = v || 0; } else if (column.colType === 'contrib') { // contribution to column total v = recent / Object.keys(reversedEntries[0]).map(k => k === 'time' ? 0 : reversedEntries[0][k]).reduce((a, b) => a + b); } else if (column.colType === 'avg') { // Average over the last {timeLag} v = reversedEntries.map((k, i) => i < column.timeLag ? k[valueField] : 0).reduce((a, b) => a + b) / column.timeLag; } const color = colorFromBounds(v, column.bounds); return (0, _react2.jsx)(_reactableArc.Td, { key: column.key, column: column.key, value: v, style: color && { boxShadow: `inset 0px -2.5px 0px 0px ${color}`, borderRight: '2px solid #fff' } }, errorMsg ? (0, _react2.jsx)("div", null, errorMsg) : // @ts-ignore (0, _react2.jsx)("div", { style: { color } }, (0, _react2.jsx)(_FormattedNumber.default, { num: v, format: column.d3format }))); } renderRow(row, entries, reversedEntries) { const { columnConfigs } = this.props; const valueField = row.label || row.metric_name; const leftCell = this.renderLeftCell(row); return (0, _react2.jsx)(_reactableArc.Tr, { key: leftCell }, (0, _react2.jsx)(_reactableArc.Td, { column: "metric", data: leftCell }, leftCell), columnConfigs.map(c => c.colType === 'spark' ? this.renderSparklineCell(valueField, c, entries) : this.renderValueCell(valueField, c, reversedEntries))); } render() { const { className, height, data, columnConfigs, rowType, rows } = this.props; const entries = Object.keys(data).sort() // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unsafe-return .map(time => ({ ...data[time], time })); const reversedEntries = entries.concat().reverse(); const defaultSort = rowType === 'column' && columnConfigs.length > 0 ? { column: columnConfigs[0].key, direction: 'desc' } : false; return (0, _react2.jsx)("div", { className: `time-table ${className}`, style: { height } }, (0, _react2.jsx)(_reactableArc.Table, { className: "table table-no-hover", defaultSort: defaultSort, sortBy: defaultSort, sortable: columnConfigs.map(c => c.key) }, (0, _react2.jsx)(_reactableArc.Thead, null, (0, _react2.jsx)(_reactableArc.Th, { column: "metric" }, "Metric"), columnConfigs.map((c, i) => (0, _react2.jsx)(_reactableArc.Th, { key: c.key, column: c.key, width: c.colType === 'spark' ? '1%' : null }, c == null ? void 0 : c.label, ' ', (c == null ? void 0 : c.tooltip) && (0, _react2.jsx)(_chartControls.InfoTooltipWithTrigger, { tooltip: c == null ? void 0 : c.tooltip, label: `tt-col-${i}`, placement: "top" })))), rows.map(row => this.renderRow(row, entries, reversedEntries)))); } } TimeTable.propTypes = { columnConfigs: _propTypes.default.arrayOf(_propTypes.default.shape({ colType: _propTypes.default.string.isRequired, comparisonType: _propTypes.default.string.isRequired, d3format: _propTypes.default.string.isRequired, key: _propTypes.default.string.isRequired, label: _propTypes.default.string.isRequired, timeLag: _propTypes.default.number.isRequired, tooltip: _propTypes.default.any.isRequired, bounds: _propTypes.default.arrayOf(_propTypes.default.number).isRequired, dateFormat: _propTypes.default.string.isRequired, width: _propTypes.default.string.isRequired, height: _propTypes.default.string.isRequired, yAxisBounds: _propTypes.default.arrayOf(_propTypes.default.number).isRequired, showYAxis: _propTypes.default.bool.isRequired, timeRatio: _propTypes.default.number.isRequired })).isRequired, data: _propTypes.default.object.isRequired, height: _propTypes.default.number.isRequired, rows: _propTypes.default.arrayOf(_propTypes.default.shape({ label: _propTypes.default.string.isRequired, metric_name: _propTypes.default.string.isRequired })).isRequired, rowType: _propTypes.default.string.isRequired, url: _propTypes.default.string.isRequired, row: _propTypes.default.arrayOf(_propTypes.default.any).isRequired }; var _default = (0, _core.styled)(TimeTable)` .time-table { overflow: auto; } `; exports.default = _default;