This is an automated email from the ASF dual-hosted git repository. lilykuang pushed a commit to branch table-time-compare-hide-show in repository https://gitbox.apache.org/repos/asf/superset.git
commit cc5bdc1ad5351eccde8294996a54be35a8bbe614 Author: lilykuang <[email protected]> AuthorDate: Fri Mar 8 09:04:55 2024 -0800 feat(time-comparison-table): implement comparison columns dropdown --- .../src/{index.ts => components/Dropdown.tsx} | 20 +-- .../src/{index.ts => components/Menu.tsx} | 20 +-- .../superset-ui-chart-controls/src/index.ts | 4 +- .../plugins/plugin-chart-table/package.json | 1 + .../plugin-chart-table/src/DataTable/DataTable.tsx | 14 ++- .../plugins/plugin-chart-table/src/TableChart.tsx | 134 +++++++++++++++++++-- 6 files changed, 146 insertions(+), 47 deletions(-) diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts b/superset-frontend/packages/superset-ui-chart-controls/src/components/Dropdown.tsx similarity index 56% copy from superset-frontend/packages/superset-ui-chart-controls/src/index.ts copy to superset-frontend/packages/superset-ui-chart-controls/src/components/Dropdown.tsx index 4e00929119..032365ebd5 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/components/Dropdown.tsx @@ -16,22 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -import * as sectionsModule from './sections'; -export * from './utils'; -export * from './constants'; -export * from './operators'; - -// can't do `export * as sections from './sections'`, babel-transformer will fail -export const sections = sectionsModule; - -export * from './components/InfoTooltipWithTrigger'; -export * from './components/ColumnOption'; -export * from './components/ColumnTypeLabel/ColumnTypeLabel'; -export * from './components/MetricOption'; -export * from './components/ControlSubSectionHeader'; -export * from './components/Tooltip'; - -export * from './shared-controls'; -export * from './types'; -export * from './fixtures'; +export { Dropdown } from 'antd'; +export type { DropDownProps } from 'antd/lib/dropdown'; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts b/superset-frontend/packages/superset-ui-chart-controls/src/components/Menu.tsx similarity index 56% copy from superset-frontend/packages/superset-ui-chart-controls/src/index.ts copy to superset-frontend/packages/superset-ui-chart-controls/src/components/Menu.tsx index 4e00929119..89a7405cde 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/components/Menu.tsx @@ -16,22 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -import * as sectionsModule from './sections'; -export * from './utils'; -export * from './constants'; -export * from './operators'; - -// can't do `export * as sections from './sections'`, babel-transformer will fail -export const sections = sectionsModule; - -export * from './components/InfoTooltipWithTrigger'; -export * from './components/ColumnOption'; -export * from './components/ColumnTypeLabel/ColumnTypeLabel'; -export * from './components/MetricOption'; -export * from './components/ControlSubSectionHeader'; -export * from './components/Tooltip'; - -export * from './shared-controls'; -export * from './types'; -export * from './fixtures'; +export { Menu } from 'antd'; +export type { MenuProps } from 'antd/lib/menu'; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts b/superset-frontend/packages/superset-ui-chart-controls/src/index.ts index 4e00929119..fed32cae3a 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/index.ts @@ -28,8 +28,10 @@ export const sections = sectionsModule; export * from './components/InfoTooltipWithTrigger'; export * from './components/ColumnOption'; export * from './components/ColumnTypeLabel/ColumnTypeLabel'; -export * from './components/MetricOption'; export * from './components/ControlSubSectionHeader'; +export * from './components/Dropdown'; +export * from './components/Menu'; +export * from './components/MetricOption'; export * from './components/Tooltip'; export * from './shared-controls'; diff --git a/superset-frontend/plugins/plugin-chart-table/package.json b/superset-frontend/plugins/plugin-chart-table/package.json index 6df1ceb33e..d43e33808e 100644 --- a/superset-frontend/plugins/plugin-chart-table/package.json +++ b/superset-frontend/plugins/plugin-chart-table/package.json @@ -37,6 +37,7 @@ "xss": "^1.0.14" }, "peerDependencies": { + "@ant-design/icons": "^5.0.1", "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", "@testing-library/dom": "^7.29.4", diff --git a/superset-frontend/plugins/plugin-chart-table/src/DataTable/DataTable.tsx b/superset-frontend/plugins/plugin-chart-table/src/DataTable/DataTable.tsx index 79ab44981e..18c7ac6214 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/DataTable/DataTable.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/DataTable/DataTable.tsx @@ -68,6 +68,7 @@ export interface DataTableProps<D extends object> extends TableOptions<D> { wrapperRef?: MutableRefObject<HTMLDivElement>; onColumnOrderChange: () => void; renderGroupingHeaders?: () => JSX.Element; + renderTimeComparisonDropdown?: () => JSX.Element; } export interface RenderHTMLCellProps extends HTMLProps<HTMLTableCellElement> { @@ -101,6 +102,7 @@ export default typedMemo(function DataTable<D extends object>({ wrapperRef: userWrapperRef, onColumnOrderChange, renderGroupingHeaders, + renderTimeComparisonDropdown, ...moreUseTableOptions }: DataTableProps<D>): JSX.Element { const tableHooks: PluginHook<D>[] = [ @@ -117,7 +119,8 @@ export default typedMemo(function DataTable<D extends object>({ const sortByRef = useRef([]); // cache initial `sortby` so sorting doesn't trigger page reset const pageSizeRef = useRef([initialPageSize, resultsSize]); const hasPagination = initialPageSize > 0 && resultsSize > 0; // pageSize == 0 means no pagination - const hasGlobalControl = hasPagination || !!searchInput; + const hasGlobalControl = + hasPagination || !!searchInput || renderTimeComparisonDropdown; const initialState = { ...initialState_, // zero length means all pages, the `usePagination` plugin does not @@ -359,7 +362,7 @@ export default typedMemo(function DataTable<D extends object>({ {hasGlobalControl ? ( <div ref={globalControlRef} className="form-inline dt-controls"> <div className="row"> - <div className="col-sm-6"> + <div className="col-sm-5"> {hasPagination ? ( <SelectPageSize total={resultsSize} @@ -375,7 +378,7 @@ export default typedMemo(function DataTable<D extends object>({ ) : null} </div> {searchInput ? ( - <div className="col-sm-6"> + <div className="col-sm-5"> <GlobalFilter<D> searchInput={ typeof searchInput === 'boolean' ? undefined : searchInput @@ -386,6 +389,11 @@ export default typedMemo(function DataTable<D extends object>({ /> </div> ) : null} + {renderTimeComparisonDropdown ? ( + <div className="col-sm-2" style={{ float: 'right' }}> + {renderTimeComparisonDropdown()} + </div> + ) : null} </div> </div> ) : null} diff --git a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx index ab75914c97..7034262efc 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx @@ -50,6 +50,8 @@ import { tn, useTheme, } from '@superset-ui/core'; +import { Dropdown, Menu } from '@superset-ui/chart-controls'; +import { CheckOutlined, DownOutlined } from '@ant-design/icons'; import { isEmpty } from 'lodash'; import { DataColumnMeta, TableChartTransformedProps } from './types'; @@ -242,6 +244,12 @@ export default function TableChart<D extends DataRecord = DataRecord>( emitCrossFilters, enableTimeComparison, } = props; + const comparisonColumns = [ + { key: 'all', label: t('Display all') }, + { key: '#', label: '#' }, + { key: '△', label: '△' }, + { key: '%', label: '%' }, + ]; const timestampFormatter = useCallback( value => getTimeFormatterForGranularity(timeGrain)(value), [timeGrain], @@ -252,6 +260,9 @@ export default function TableChart<D extends DataRecord = DataRecord>( }); // keep track of whether column order changed, so that column widths can too const [columnOrderToggle, setColumnOrderToggle] = useState(false); + const [showTimeComparisonDropdown, setShowTimeComparisonDropdown] = + useState(false); + const [selectedKeys, setSelectedKeys] = useState([comparisonColumns[0].key]); const theme = useTheme(); // only take relevant page size options @@ -371,6 +382,33 @@ export default function TableChart<D extends DataRecord = DataRecord>( }; }; + const comparisonLabels = [t('Main'), '#', '△', '%']; + const filteredColumnsMeta = useMemo(() => { + if (!enableTimeComparison) { + return columnsMeta; + } + const allColumns = comparisonColumns[0].key; + const isAllColumnsKeySelected = selectedKeys.includes(allColumns); + const mainLabel = comparisonLabels[0]; + + return columnsMeta.filter(col => { + const { label } = col; + + return ( + label === mainLabel || + !comparisonLabels.includes(label) || + isAllColumnsKeySelected || + selectedKeys.includes(label) + ); + }); + }, [ + columnsMeta, + comparisonColumns, + comparisonLabels, + selectedKeys, + enableTimeComparison, + ]); + const handleContextMenu = onContextMenu && !isRawRecords ? ( @@ -384,7 +422,7 @@ export default function TableChart<D extends DataRecord = DataRecord>( clientY: number, ) => { const drillToDetailFilters: BinaryQueryObjectFilterClause[] = []; - columnsMeta.forEach(col => { + filteredColumnsMeta.forEach(col => { if (!col.isMetric) { const dataRecordValue = value[col.key]; drillToDetailFilters.push({ @@ -416,8 +454,6 @@ export default function TableChart<D extends DataRecord = DataRecord>( } : undefined; - const comparisonLabels = [t('Main'), '#', '△', '%']; - const getHeaderColumns = ( columnsMeta: DataColumnMeta[], enableTimeComparison?: boolean, @@ -447,6 +483,87 @@ export default function TableChart<D extends DataRecord = DataRecord>( return resultMap; }; + const renderTimeComparisonDropdown = (): JSX.Element => { + const allKey = comparisonColumns[0].key; + const handleOnClick = (data: any) => { + const { key } = data; + // Toggle 'All' key selection + if (key === allKey) { + setSelectedKeys([allKey]); + } else if (selectedKeys.includes(allKey)) { + setSelectedKeys([key]); + } else { + // Toggle selection for other keys + setSelectedKeys( + selectedKeys.includes(key) + ? selectedKeys.filter(k => k !== key) // Deselect if already selected + : [...selectedKeys, key], + ); // Select if not already selected + } + }; + + const handleOnBlur = () => { + if (selectedKeys.length === 3) { + setSelectedKeys([comparisonColumns[0].key]); + } + }; + + return ( + <Dropdown + placement="bottomRight" + visible={showTimeComparisonDropdown} + onVisibleChange={(flag: boolean) => { + setShowTimeComparisonDropdown(flag); + }} + overlay={ + <Menu + multiple + onClick={handleOnClick} + onBlur={handleOnBlur} + selectedKeys={selectedKeys} + > + <div + css={css` + max-width: 242px; + padding: 0 ${theme.gridUnit * 2}px; + color: ${theme.colors.grayscale.base}; + font-size: ${theme.typography.sizes.s}px; + `} + > + {t( + 'Select columns that will be displayed in the table. You can multiselect columns.', + )} + </div> + {comparisonColumns.map(column => ( + <Menu.Item key={column.key}> + <span + css={css` + color: ${theme.colors.grayscale.dark2}; + `} + > + {column.label} + </span> + <span + css={css` + float: right; + font-size: ${theme.typography.sizes.s}px; + `} + > + {selectedKeys.includes(column.key) && <CheckOutlined />} + </span> + </Menu.Item> + ))} + </Menu> + } + trigger={['click']} + > + <span> + {t('Display columns')} <DownOutlined /> + </span> + </Dropdown> + ); + }; + const renderGroupingHeaders = (): JSX.Element => { // TODO: Make use of ColumnGroup to render the aditional headers const headers: any = []; @@ -499,8 +616,8 @@ export default function TableChart<D extends DataRecord = DataRecord>( }; const groupHeaderColumns = useMemo( - () => getHeaderColumns(columnsMeta, enableTimeComparison), - [columnsMeta, enableTimeComparison], + () => getHeaderColumns(filteredColumnsMeta, enableTimeComparison), + [filteredColumnsMeta, enableTimeComparison], ); const getColumnConfigs = useCallback( @@ -769,8 +886,8 @@ export default function TableChart<D extends DataRecord = DataRecord>( ); const columns = useMemo( - () => columnsMeta.map(getColumnConfigs), - [columnsMeta, getColumnConfigs], + () => filteredColumnsMeta.map(getColumnConfigs), + [filteredColumnsMeta, getColumnConfigs], ); const handleServerPaginationChange = useCallback( @@ -840,6 +957,9 @@ export default function TableChart<D extends DataRecord = DataRecord>( renderGroupingHeaders={ !isEmpty(groupHeaderColumns) ? renderGroupingHeaders : undefined } + renderTimeComparisonDropdown={ + enableTimeComparison ? renderTimeComparisonDropdown : undefined + } /> </Styles> );
