diff --git a/packages/superset-ui-plugin-chart-table/package.json b/packages/superset-ui-plugin-chart-table/package.json index 7033619cc..927b704a3 100644 --- a/packages/superset-ui-plugin-chart-table/package.json +++ b/packages/superset-ui-plugin-chart-table/package.json @@ -26,7 +26,7 @@ "access": "public" }, "dependencies": { - "@airbnb/lunar": "^1.8.1", + "@airbnb/lunar": "^1.11.0", "@airbnb/lunar-icons": "^1.1.1" }, "peerDependencies": { diff --git a/packages/superset-ui-plugin-chart-table/src/Table.tsx b/packages/superset-ui-plugin-chart-table/src/Table.tsx index 293a737e5..71055cfaa 100644 --- a/packages/superset-ui-plugin-chart-table/src/Table.tsx +++ b/packages/superset-ui-plugin-chart-table/src/Table.tsx @@ -1,8 +1,9 @@ import React from 'react'; import DataTable from '@airbnb/lunar/lib/components/DataTable'; +import Text from '@airbnb/lunar/lib/components/Text'; import Input from '@airbnb/lunar/lib/components/Input'; import withStyles, { css, WithStylesProps } from '@airbnb/lunar/lib/composers/withStyles'; -import { Renderers, ParentRow } from '@airbnb/lunar/lib/components/DataTable/types'; +import { Renderers, ParentRow, ColumnMetadata } from '@airbnb/lunar/lib/components/DataTable/types'; import { getRenderer, ColumnType, heightType, Cell } from './renderer'; type Props = { @@ -84,7 +85,6 @@ class TableVis extends React.PureComponent { }; handleSearch = (value: string) => { - console.log(value); const { searchKeyword } = this.state; const { data } = this.props; if (searchKeyword !== value) { @@ -94,7 +94,6 @@ class TableVis extends React.PureComponent { .toLowerCase(); return content.indexOf(value) >= 0; }); - console.log(filteredRows); this.setState({ searchKeyword: value, filteredRows, @@ -142,8 +141,7 @@ class TableVis extends React.PureComponent { const renderers: Renderers = {}; const dataToRender = searchKeyword !== '' ? filteredRows : data; - - console.log(dataToRender); + const columnMetadata: ColumnMetadata = {}; columns.forEach(column => { renderers[column.key] = getRenderer({ @@ -154,24 +152,35 @@ class TableVis extends React.PureComponent { isSelected: this.isSelected, handleCellSelected: this.handleCellSelected, }); + if (column.type == 'metric') { + columnMetadata[column.key] = { + rightAlign: 1, + }; + } }); return ( {includeSearch && (
- +
+ +
+ + Showing {dataToRender.length} out of {data.length} rows +
)} { } } -export default withStyles(() => ({ +export default withStyles(({ unit }) => ({ searchBar: { display: 'flex', + flexGrow: 0, flexDirection: 'row-reverse', + marginBottom: unit, + alignItems: 'baseline', + }, + searchBox: { + width: 25 * unit, + marginLeft: unit, }, }))(TableVis); diff --git a/packages/superset-ui-plugin-chart-table/src/legacy/transformProps.ts b/packages/superset-ui-plugin-chart-table/src/legacy/transformProps.ts index c8385ba52..3ee0d7c6d 100644 --- a/packages/superset-ui-plugin-chart-table/src/legacy/transformProps.ts +++ b/packages/superset-ui-plugin-chart-table/src/legacy/transformProps.ts @@ -18,15 +18,10 @@ */ /* eslint-disable sort-keys */ -import { ChartProps, FormDataMetric, Metric } from '@superset-ui/chart'; -import { getNumberFormatter, NumberFormats, NumberFormatter } from '@superset-ui/number-format'; -import { getTimeFormatter, TimeFormatter } from '@superset-ui/time-format'; - -const DTTM_ALIAS = '__timestamp'; - -type PlainObject = { - [key: string]: any; -}; +import { ChartProps } from '@superset-ui/chart'; +import processColumns from '../processColumns'; +import processMetrics from '../processMetrics'; +import processData from '../processData'; export default function transformProps(chartProps: ChartProps) { const { height, datasource, filters, formData, onAddFilter, payload } = chartProps; @@ -42,116 +37,32 @@ export default function transformProps(chartProps: ChartProps) { tableTimestampFormat, timeseriesLimitMetric, } = formData; - const { columnFormats, verboseMap } = datasource; const { records, columns } = payload.data; - const metrics = ((rawMetrics as FormDataMetric[]) || []) - .map(m => (m as Metric).label || (m as string)) - // Add percent metrics - .concat(((percentMetrics as string[]) || []).map(m => `%${m}`)) - // Removing metrics (aggregates) that are strings - .filter(m => typeof records[0][m as string] === 'number'); - - const dataArray: { - [key: string]: any[]; - } = {}; - - const sortByKey = - timeseriesLimitMetric && - ((timeseriesLimitMetric as Metric).label || (timeseriesLimitMetric as string)); - - let formattedData: { - data: PlainObject; - }[] = records.map((row: PlainObject) => ({ - data: row, - })); - - if (sortByKey) { - formattedData = formattedData.sort((a, b) => { - const delta = a.data[sortByKey] - b.data[sortByKey]; - if (orderDesc) { - return -delta; - } - return delta; - }); - if (metrics.indexOf(sortByKey) < 0) { - formattedData = formattedData.map(row => { - const data = { ...row.data }; - delete data[sortByKey]; - return { - data, - }; - }); - } - } - - metrics.forEach(metric => { - const arr = []; - for (let i = 0; i < records.length; i += 1) { - arr.push(records[i][metric]); - } - - dataArray[metric] = arr; + const metrics = processMetrics({ + metrics: rawMetrics, + percentMetrics, + records, }); - const maxes: { - [key: string]: number; - } = {}; - const mins: { - [key: string]: number; - } = {}; - - for (let i = 0; i < metrics.length; i += 1) { - maxes[metrics[i]] = Math.max(...dataArray[metrics[i]]); - mins[metrics[i]] = Math.min(...dataArray[metrics[i]]); - } - - const formatPercent = getNumberFormatter(NumberFormats.PERCENT_3_POINT); - const tsFormatter = getTimeFormatter(tableTimestampFormat); - - const processedColumns = columns.map((key: string) => { - let label = verboseMap[key]; - let formatString = columnFormats && columnFormats[key]; - let formatFunction: NumberFormatter | TimeFormatter | undefined; - let type = 'string'; - - // Handle verbose names for percents - if (!label) { - if (key[0] === '%') { - const cleanedKey = key.substring(1); - label = `% ${verboseMap[cleanedKey] || cleanedKey}`; - formatFunction = formatPercent; - } else { - label = key; - } - } - - if (key === DTTM_ALIAS) { - formatFunction = tsFormatter; - } - - const extraField: { - [key: string]: any; - } = {}; + const processedData = processData({ + timeseriesLimitMetric, + orderDesc, + records, + metrics, + }); - if (metrics.indexOf(key) >= 0) { - formatFunction = getNumberFormatter(formatString); - type = 'metric'; - extraField['maxValue'] = maxes[key]; - extraField['minValue'] = mins[key]; - } - return { - key, - label, - format: formatFunction, - type, - ...extraField, - }; + const processedColumns = processColumns({ + columns, + metrics, + records, + tableTimestampFormat, + datasource, }); return { height, - data: formattedData, + data: processedData, alignPositiveNegative: alignPn, colorPositiveNegative: colorPn, columns: processedColumns, diff --git a/packages/superset-ui-plugin-chart-table/src/processColumns.ts b/packages/superset-ui-plugin-chart-table/src/processColumns.ts new file mode 100644 index 000000000..350a51677 --- /dev/null +++ b/packages/superset-ui-plugin-chart-table/src/processColumns.ts @@ -0,0 +1,92 @@ +import { getNumberFormatter, NumberFormats, NumberFormatter } from '@superset-ui/number-format'; +import { getTimeFormatter, TimeFormatter } from '@superset-ui/time-format'; +import { PlainObject } from './types'; + +const DTTM_ALIAS = '__timestamp'; + +export default function processColumns({ + columns, + metrics, + records, + tableTimestampFormat, + datasource, +}: { + columns: string[]; + metrics: string[]; + records: any[]; + tableTimestampFormat: string; + datasource: PlainObject; +}) { + const { columnFormats, verboseMap } = datasource; + + const dataArray: { + [key: string]: any[]; + } = {}; + + metrics.forEach(metric => { + const arr = []; + for (let i = 0; i < records.length; i += 1) { + arr.push(records[i][metric]); + } + + dataArray[metric] = arr; + }); + + const maxes: { + [key: string]: number; + } = {}; + const mins: { + [key: string]: number; + } = {}; + + for (let i = 0; i < metrics.length; i += 1) { + maxes[metrics[i]] = Math.max(...dataArray[metrics[i]]); + mins[metrics[i]] = Math.min(...dataArray[metrics[i]]); + } + + const formatPercent = getNumberFormatter(NumberFormats.PERCENT_3_POINT); + const tsFormatter = getTimeFormatter(tableTimestampFormat); + + const processedColumns = columns.map((key: string) => { + let label = verboseMap[key]; + let formatString = columnFormats && columnFormats[key]; + let formatFunction: NumberFormatter | TimeFormatter | undefined; + let type = 'string'; + + if (key === DTTM_ALIAS) { + formatFunction = tsFormatter; + } + + const extraField: { + [key: string]: any; + } = {}; + + if (metrics.indexOf(key) >= 0) { + formatFunction = getNumberFormatter(formatString); + type = 'metric'; + extraField['maxValue'] = maxes[key]; + extraField['minValue'] = mins[key]; + } + + // Handle verbose names for percents + if (!label) { + if (key[0] === '%') { + const cleanedKey = key.substring(1); + label = `% ${verboseMap[cleanedKey] || cleanedKey}`; + formatFunction = formatPercent; + } else { + label = key; + } + } + + return { + key, + label, + format: formatFunction, + type, + ...extraField, + }; + }); + + return processedColumns; +} diff --git a/packages/superset-ui-plugin-chart-table/src/processData.ts b/packages/superset-ui-plugin-chart-table/src/processData.ts new file mode 100644 index 000000000..ee9dfbca3 --- /dev/null +++ b/packages/superset-ui-plugin-chart-table/src/processData.ts @@ -0,0 +1,45 @@ +import { PlainObject } from './types'; +import { FormDataMetric, Metric } from '@superset-ui/chart'; + +export default function processData({ + timeseriesLimitMetric, + orderDesc, + records, + metrics, +}: { + timeseriesLimitMetric: FormDataMetric; + orderDesc: boolean; + records: PlainObject[]; + metrics: string[]; +}) { + const sortByKey = + timeseriesLimitMetric && + ((timeseriesLimitMetric as Metric).label || (timeseriesLimitMetric as string)); + + let processedData: { + data: PlainObject; + }[] = records.map((row: PlainObject) => ({ + data: row, + })); + + if (sortByKey) { + processedData = processedData.sort((a, b) => { + const delta = a.data[sortByKey] - b.data[sortByKey]; + if (orderDesc) { + return -delta; + } + return delta; + }); + if (metrics.indexOf(sortByKey) < 0) { + processedData = processedData.map(row => { + const data = { ...row.data }; + delete data[sortByKey]; + return { + data, + }; + }); + } + } + + return processedData; +} diff --git a/packages/superset-ui-plugin-chart-table/src/processMetrics.ts b/packages/superset-ui-plugin-chart-table/src/processMetrics.ts new file mode 100644 index 000000000..1f8e742e7 --- /dev/null +++ b/packages/superset-ui-plugin-chart-table/src/processMetrics.ts @@ -0,0 +1,22 @@ +import { FormDataMetric, Metric } from '@superset-ui/chart'; +import { PlainObject } from './types'; + +export default function processMetrics({ + metrics, + percentMetrics, + records, +}: { + metrics: FormDataMetric[]; + percentMetrics: FormDataMetric[]; + records: PlainObject[]; +}) { + const processedMetrics = (metrics || []).map(m => (m as Metric).label || (m as string)); + + const processedPercentMetrics = (percentMetrics || []) + .map(m => (m as Metric).label || (m as string)) + .map(m => `%${m}`); + + return processedMetrics + .concat(processedPercentMetrics) + .filter(m => typeof records[0][m as string] === 'number'); +} diff --git a/packages/superset-ui-plugin-chart-table/src/renderer.tsx b/packages/superset-ui-plugin-chart-table/src/renderer.tsx index f19095dd8..7724f86e4 100644 --- a/packages/superset-ui-plugin-chart-table/src/renderer.tsx +++ b/packages/superset-ui-plugin-chart-table/src/renderer.tsx @@ -2,9 +2,9 @@ import React, { CSSProperties } from 'react'; import { HEIGHT_TO_PX } from '@airbnb/lunar/lib/components/DataTable/constants'; import { RendererProps } from '@airbnb/lunar/lib/components/DataTable/types'; -const NEGATIVE_COLOR = '#ff8787'; +const NEGATIVE_COLOR = '#FFA8A8'; const POSITIVE_COLOR = '#ced4da'; -const SELECTION_COLOR = '#ffec99'; +const SELECTION_COLOR = '#EBEBEB'; export const heightType = 'micro'; @@ -70,19 +70,27 @@ export const getRenderer = ({ alignItems: 'center', margin: '0px 16px', }; + const barStyle: CSSProperties = { background: color, width: `${width}%`, left: `${left}%`, position: 'absolute', - height: HEIGHT_TO_PX[heightType] / 2, + height: HEIGHT_TO_PX[heightType] / 2 + 4, + borderRadius: 3, + }; + + const numberStyle: CSSProperties = { + zIndex: 10, + marginLeft: 'auto', + marginRight: '4px', }; return (
-
{children}
+
{children}
); diff --git a/packages/superset-ui-plugin-chart-table/src/transformProps.ts b/packages/superset-ui-plugin-chart-table/src/transformProps.ts index b522b53e1..8a1012eec 100644 --- a/packages/superset-ui-plugin-chart-table/src/transformProps.ts +++ b/packages/superset-ui-plugin-chart-table/src/transformProps.ts @@ -17,7 +17,10 @@ * under the License. */ -import { ChartProps, Metric, FormDataMetric } from '@superset-ui/chart'; +import { ChartProps, FormDataMetric, Metric } from '@superset-ui/chart'; +import processColumns from './processColumns'; +import processMetrics from './processMetrics'; +import processData from './processData'; const DTTM_ALIAS = '__timestamp'; @@ -26,10 +29,10 @@ type PlainObject = { }; function transformData(data: PlainObject[], formData: PlainObject) { + const { groupby = [], metrics = [], allColumns = [] } = formData; + const columns = new Set( - [...formData.groupby, ...formData.metrics, ...formData.allColumns].map( - column => column.label || column, - ), + [...groupby, ...metrics, ...allColumns].map(column => column.label || column), ); let records = data; @@ -43,6 +46,7 @@ function transformData(data: PlainObject[], formData: PlainObject) { const percentMetrics: string[] = (formData.percentMetrics || []).map( (metric: FormDataMetric) => (metric as Metric).label || (metric as string), ); + if (percentMetrics.length > 0) { const sumPercentMetrics = data.reduce((sumMetrics, item) => { const newSumMetrics = { ...sumMetrics }; @@ -82,7 +86,7 @@ export default function transformProps(chartProps: ChartProps) { alignPn, colorPn, includeSearch, - metrics, + metrics: rawMetrics, orderDesc, pageLength, percentMetrics, @@ -90,37 +94,40 @@ export default function transformProps(chartProps: ChartProps) { tableTimestampFormat, timeseriesLimitMetric, } = formData; - const { columnFormats, verboseMap } = datasource; const { records, columns } = transformData(payload.data, formData); - const processedColumns = columns.map((key: string) => { - let label = verboseMap[key]; - // Handle verbose names for percents - if (!label) { - label = key; - } - return { - key, - label, - format: columnFormats && columnFormats[key], - }; + const metrics = processMetrics({ + metrics: rawMetrics, + percentMetrics, + records, + }); + + const processedData = processData({ + timeseriesLimitMetric, + orderDesc, + records, + metrics, + }); + + const processedColumns = processColumns({ + columns, + metrics, + records, + tableTimestampFormat, + datasource, }); return { height, - data: records, + data: processedData, alignPositiveNegative: alignPn, colorPositiveNegative: colorPn, columns: processedColumns, filters, includeSearch, - metrics, onAddFilter, orderDesc, pageLength: pageLength && parseInt(pageLength, 10), - percentMetrics, tableFilter, - tableTimestampFormat, - timeseriesLimitMetric, }; } diff --git a/packages/superset-ui-plugin-chart-table/src/types.ts b/packages/superset-ui-plugin-chart-table/src/types.ts new file mode 100644 index 000000000..6f8c7f039 --- /dev/null +++ b/packages/superset-ui-plugin-chart-table/src/types.ts @@ -0,0 +1,3 @@ +export type PlainObject = { + [key: string]: any; +}; diff --git a/packages/superset-ui-plugins-demo/storybook/stories/plugin-chart-table/Stories.jsx b/packages/superset-ui-plugins-demo/storybook/stories/plugin-chart-table/Stories.jsx index cf9aa2715..7f4a72bae 100644 --- a/packages/superset-ui-plugins-demo/storybook/stories/plugin-chart-table/Stories.jsx +++ b/packages/superset-ui-plugins-demo/storybook/stories/plugin-chart-table/Stories.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { SuperChart } from '@superset-ui/chart'; import dataLegacy from './dataLegacy'; - +import data from './data'; export default [ { renderStory: () => ( @@ -52,7 +52,7 @@ export default [ }, filters: {}, formData: { - alignPn: false, + alignPn: true, colorPn: true, includeSearch: true, pageLength: 0, @@ -73,4 +73,39 @@ export default [ storyName: 'Legacy-TableFilter', storyPath: 'plugin-chart-table|TableChartPlugin', }, + { + renderStory: () => ( + + ), + storyName: 'TableFilter', + storyPath: 'plugin-chart-table|TableChartPlugin', + }, ];