diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx index e0e19c503c..72948d5584 100644 --- a/superset-frontend/src/components/ListView/ListView.tsx +++ b/superset-frontend/src/components/ListView/ListView.tsx @@ -17,6 +17,7 @@ * under the License. */ import { t, styled } from '@superset-ui/core'; +import { css } from '@emotion/core'; import React, { useState } from 'react'; import { Alert } from 'react-bootstrap'; import { Empty } from 'src/common/components'; @@ -37,7 +38,11 @@ import { } from './types'; import { ListViewError, useListViewState } from './utils'; -const ListViewStyles = styled.div` +interface ListViewStylesProps { + fullHeight: boolean; +} + +const ListViewStyles = styled.div` text-align: center; .superset-list-view { @@ -48,8 +53,12 @@ const ListViewStyles = styled.div` padding-bottom: 48px; .body { - overflow: scroll; - max-height: 64vh; + ${({ fullHeight }) => + !fullHeight && + css` + overflow: scroll; + max-height: 64vh; + `}; } } @@ -202,6 +211,9 @@ export interface ListViewProps { renderCard?: (row: T & { loading: boolean }) => React.ReactNode; cardSortSelectOptions?: Array; defaultViewMode?: ViewModeType; + sticky?: boolean; + fullHeight?: boolean; + manualSortBy?: boolean; } function ListView({ @@ -221,6 +233,9 @@ function ListView({ renderCard, cardSortSelectOptions, defaultViewMode = 'card', + sticky = true, + fullHeight = false, + manualSortBy = true, }: ListViewProps) { const { getTableProps, @@ -244,8 +259,10 @@ function ListView({ initialPageSize, initialSort, initialFilters: filters, + manualSortBy, }); const filterable = Boolean(filters.length); + const withPagination = Boolean(count); if (filterable) { const columnAccessors = columns.reduce( (acc, col) => ({ ...acc, [col.accessor || col.id]: true }), @@ -266,7 +283,7 @@ function ListView({ ); return ( - +
{cardViewEnabled && ( @@ -350,6 +367,7 @@ function ListView({ rows={rows} columns={columns} loading={loading} + sticky={sticky} /> )} {!loading && rows.length === 0 && ( @@ -360,23 +378,25 @@ function ListView({
-
- gotoPage(p - 1)} - hideFirstAndLastPageLinks - /> -
- {!loading && - t( - '%s-%s of %s', - pageSize * pageIndex + (rows.length && 1), - pageSize * pageIndex + rows.length, - count, - )} + {withPagination && ( +
+ gotoPage(p - 1)} + hideFirstAndLastPageLinks + /> +
+ {!loading && + t( + '%s-%s of %s', + pageSize * pageIndex + (rows.length && 1), + pageSize * pageIndex + rows.length, + count, + )} +
-
+ )} ); } diff --git a/superset-frontend/src/components/ListView/TableCollection.tsx b/superset-frontend/src/components/ListView/TableCollection.tsx index 4dbbc86bce..a68bcf4db0 100644 --- a/superset-frontend/src/components/ListView/TableCollection.tsx +++ b/superset-frontend/src/components/ListView/TableCollection.tsx @@ -30,14 +30,19 @@ interface TableCollectionProps { rows: TableInstance['rows']; columns: TableInstance['column'][]; loading: boolean; + sticky: boolean; } -const Table = styled.table` +interface TableProps { + sticky: boolean; +} + +const Table = styled.table` border-collapse: separate; th { background: ${({ theme }) => theme.colors.grayscale.light5}; - position: sticky; + position: ${({ sticky }) => sticky && 'sticky'}; top: 0; &:first-of-type { @@ -199,9 +204,10 @@ export default function TableCollection({ columns, rows, loading, + sticky, }: TableCollectionProps) { return ( - +
{headerGroups.map(headerGroup => ( diff --git a/superset-frontend/src/components/ListView/utils.ts b/superset-frontend/src/components/ListView/utils.ts index c8ecc95be5..92605d268f 100644 --- a/superset-frontend/src/components/ListView/utils.ts +++ b/superset-frontend/src/components/ListView/utils.ts @@ -110,6 +110,7 @@ interface UseListViewConfig { Header: (conf: any) => React.ReactNode; Cell: (conf: any) => React.ReactNode; }; + manualSortBy: boolean; } export function useListViewState({ @@ -122,6 +123,7 @@ export function useListViewState({ initialSort = [], bulkSelectMode = false, bulkSelectColumnConfig, + manualSortBy, }: UseListViewConfig) { const [query, setQuery] = useQueryParams({ filters: JsonParam, @@ -177,9 +179,9 @@ export function useListViewState({ initialState, manualFilters: true, manualPagination: true, - manualSortBy: true, autoResetFilters: false, pageCount: Math.ceil(count / initialPageSize), + manualSortBy, }, useFilters, useSortBy, diff --git a/superset-frontend/src/visualizations/TimeTable/TimeTable.jsx b/superset-frontend/src/visualizations/TimeTable/TimeTable.jsx index c384cb0acb..d27a1e80d1 100644 --- a/superset-frontend/src/visualizations/TimeTable/TimeTable.jsx +++ b/superset-frontend/src/visualizations/TimeTable/TimeTable.jsx @@ -20,14 +20,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import Mustache from 'mustache'; import { scaleLinear } from 'd3-scale'; -import { Table, Thead, Th, Tr, Td } from 'reactable-arc'; import { formatNumber, formatTime } from '@superset-ui/core'; import { InfoTooltipWithTrigger, MetricOption, } from '@superset-ui/chart-controls'; import moment from 'moment'; - +import ListView from 'src/components/ListView'; +import { memoize } from 'lodash-es'; import FormattedNumber from './FormattedNumber'; import SparklineCell from './SparklineCell'; import './TimeTable.less'; @@ -90,6 +90,68 @@ const defaultProps = { }; class TimeTable extends React.PureComponent { + memoizedColumns = memoize(() => [ + { accessor: 'metric', Header: 'Metric' }, + ...this.props.columnConfigs.map((columnConfig, i) => ({ + accessor: columnConfig.key, + cellProps: columnConfig.colType === 'spark' && { style: { width: '1%' } }, + Header: () => ( + <> + {columnConfig.label}{' '} + {columnConfig.tooltip && ( + + )} + + ), + sortType: (rowA, rowB, columnId) => { + const rowAVal = rowA.values[columnId].props['data-value']; + const rowBVal = rowB.values[columnId].props['data-value']; + return rowAVal - rowBVal; + }, + })), + ]); + + memoizedRows = memoize(() => { + const entries = Object.keys(this.props.data) + .sort() + .map(time => ({ ...this.props.data[time], time })); + const reversedEntries = entries.concat().reverse(); + + return this.props.rows.map(row => { + const valueField = row.label || row.metric_name; + const cellValues = this.props.columnConfigs.reduce( + (acc, columnConfig) => { + if (columnConfig.colType === 'spark') { + return { + ...acc, + [columnConfig.key]: this.renderSparklineCell( + valueField, + columnConfig, + entries, + ), + }; + } + return { + ...acc, + [columnConfig.key]: this.renderValueCell( + valueField, + columnConfig, + reversedEntries, + ), + }; + }, + {}, + ); + return { ...row, ...cellValues, metric: this.renderLeftCell(row) }; + }); + }); + + initialSort = [{ id: 'metric', desc: false }]; + renderLeftCell(row) { const { rowType, url } = this.props; const context = { metric: row }; @@ -107,10 +169,9 @@ class TimeTable extends React.PureComponent { return column.label; } - const metric = row; return ( - ( + ( +
+ {formatNumber(column.d3format, sparkData[index])}
- {formatNumber(column.d3format, sparkData[index])} -
- {formatTime( - column.dateFormat, - moment.utc(entries[index].time).toDate(), - )} -
+ {formatTime( + column.dateFormat, + moment.utc(entries[index].time).toDate(), + )}
- )} - /> - +
+ )} + /> ); } @@ -204,10 +260,9 @@ class TimeTable extends React.PureComponent { const color = colorFromBounds(v, column.bounds); return ( -
- ); - } - - renderRow(row, entries, reversedEntries) { - const { columnConfigs } = this.props; - const valueField = row.label || row.metric_name; - const leftCell = this.renderLeftCell(row); - - return ( - - - {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() - .map(time => ({ ...data[time], time })); - const reversedEntries = entries.concat().reverse(); - - const defaultSort = - rowType === 'column' && columnConfigs.length - ? { - column: columnConfigs[0].key, - direction: 'desc', - } - : false; + const { className, height } = this.props; return (
-
{errorMsg ? ( -
{errorMsg}
+ { errorMsg } ) : ( -
+ -
+ )} -
- {leftCell} -
c.key)} - > - - - {columnConfigs.map((c, i) => ( - - ))} - - {rows.map(row => this.renderRow(row, entries, reversedEntries))} -
Metric - {c.label}{' '} - {c.tooltip && ( - - )} -
+ {}} + loading={false} + sticky={false} + fullHeight + manualSortBy={false} + />
); }