This is an automated email from the ASF dual-hosted git repository. arivero pushed a commit to branch table-time-comparison-offset in repository https://gitbox.apache.org/repos/asf/superset.git
commit 5a0fcda84b461364ed26b9b38b476a48638d87c0 Author: Lily Kuang <[email protected]> AuthorDate: Tue Mar 26 11:40:12 2024 -0700 feat(time-comparison-table): show and hide time comparison columns (#27446) - cherry picked from 29f45113f7b93462592a8adfe5a4af646882294e --- superset-frontend/package-lock.json | 1 + .../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 | 20 ++- .../plugins/plugin-chart-table/src/TableChart.tsx | 172 ++++++++++++++++++++- 7 files changed, 191 insertions(+), 47 deletions(-) diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index dd5bcba5af..c2ae23cbb9 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -71010,6 +71010,7 @@ "xss": "^1.0.15" }, "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/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 ab0aa44e6e..2e63062c1e 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.15" }, "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 5ac717b2f7..08f4956a64 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 @@ -358,7 +361,9 @@ 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={renderTimeComparisonDropdown ? 'col-sm-5' : 'col-sm-6'} + > {hasPagination ? ( <SelectPageSize total={resultsSize} @@ -374,7 +379,11 @@ export default typedMemo(function DataTable<D extends object>({ ) : null} </div> {searchInput ? ( - <div className="col-sm-6"> + <div + className={ + renderTimeComparisonDropdown ? 'col-sm-5' : 'col-sm-6' + } + > <GlobalFilter<D> searchInput={ typeof searchInput === 'boolean' ? undefined : searchInput @@ -385,6 +394,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 50cd9e3c59..5c0e99b203 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx @@ -50,6 +50,13 @@ import { tn, useTheme, } from '@superset-ui/core'; +import { Dropdown, Menu } from '@superset-ui/chart-controls'; +import { + CheckOutlined, + DownOutlined, + MinusCircleOutlined, + PlusCircleOutlined, +} from '@ant-design/icons'; import { isEmpty } from 'lodash'; import { DataColumnMeta, TableChartTransformedProps } from './types'; @@ -244,6 +251,12 @@ export default function TableChart<D extends DataRecord = DataRecord>( emitCrossFilters, isUsingTimeComparison, } = props; + const comparisonColumns = [ + { key: 'all', label: t('Display all') }, + { key: '#', label: '#' }, + { key: '△', label: '△' }, + { key: '%', label: '%' }, + ]; const timestampFormatter = useCallback( value => getTimeFormatterForGranularity(timeGrain)(value), [timeGrain], @@ -254,6 +267,11 @@ 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 [showComparisonDropdown, setShowComparisonDropdown] = useState(false); + const [selectedComparisonColumns, setSelectedComparisonColumns] = useState([ + comparisonColumns[0].key, + ]); + const [hideComparisonKeys, setHideComparisonKeys] = useState<string[]>([]); const theme = useTheme(); // only take relevant page size options @@ -372,6 +390,38 @@ export default function TableChart<D extends DataRecord = DataRecord>( }; }; + const comparisonLabels = [t('Main'), '#', '△', '%']; + const filteredColumnsMeta = useMemo(() => { + if (!isUsingTimeComparison) { + return columnsMeta; + } + const allColumns = comparisonColumns[0].key; + const main = comparisonLabels[0]; + const showAllColumns = selectedComparisonColumns.includes(allColumns); + + return columnsMeta.filter(({ label, key }) => { + // Extract the key portion after the space, assuming the format is always "label key" + const keyPortion = key.substring(label.length); + const isKeyHidded = hideComparisonKeys.includes(keyPortion); + const isLableMain = label === main; + + return ( + isLableMain || + (!isKeyHidded && + (!comparisonLabels.includes(label) || + showAllColumns || + selectedComparisonColumns.includes(label))) + ); + }); + }, [ + columnsMeta, + comparisonColumns, + comparisonLabels, + isUsingTimeComparison, + hideComparisonKeys, + selectedComparisonColumns, + ]); + const handleContextMenu = onContextMenu && !isRawRecords ? ( @@ -385,7 +435,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({ @@ -417,8 +467,6 @@ export default function TableChart<D extends DataRecord = DataRecord>( } : undefined; - const comparisonLabels = [t('Main'), '#', '△', '%']; - const getHeaderColumns = ( columnsMeta: DataColumnMeta[], enableTimeComparison?: boolean, @@ -448,6 +496,89 @@ 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) { + setSelectedComparisonColumns([allKey]); + } else if (selectedComparisonColumns.includes(allKey)) { + setSelectedComparisonColumns([key]); + } else { + // Toggle selection for other keys + setSelectedComparisonColumns( + selectedComparisonColumns.includes(key) + ? selectedComparisonColumns.filter(k => k !== key) // Deselect if already selected + : [...selectedComparisonColumns, key], + ); // Select if not already selected + } + }; + + const handleOnBlur = () => { + if (selectedComparisonColumns.length === 3) { + setSelectedComparisonColumns([comparisonColumns[0].key]); + } + }; + + return ( + <Dropdown + placement="bottomRight" + visible={showComparisonDropdown} + onVisibleChange={(flag: boolean) => { + setShowComparisonDropdown(flag); + }} + overlay={ + <Menu + multiple + onClick={handleOnClick} + onBlur={handleOnBlur} + selectedKeys={selectedComparisonColumns} + > + <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; + `} + > + {selectedComparisonColumns.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 = []; @@ -473,6 +604,30 @@ export default function TableChart<D extends DataRecord = DataRecord>( headers.push( <th key={`header-${key}`} colSpan={colSpan} style={{ borderBottom: 0 }}> {key} + <span + css={css` + float: right; + & svg { + color: ${theme.colors.grayscale.base} !important; + } + `} + > + {hideComparisonKeys.includes(key) ? ( + <PlusCircleOutlined + onClick={() => + setHideComparisonKeys( + hideComparisonKeys.filter(k => k !== key), + ) + } + /> + ) : ( + <MinusCircleOutlined + onClick={() => + setHideComparisonKeys([...hideComparisonKeys, key]) + } + /> + )} + </span> </th>, ); @@ -500,8 +655,8 @@ export default function TableChart<D extends DataRecord = DataRecord>( }; const groupHeaderColumns = useMemo( - () => getHeaderColumns(columnsMeta, true), - [columnsMeta, true], + () => getHeaderColumns(filteredColumnsMeta, isUsingTimeComparison), + [filteredColumnsMeta, isUsingTimeComparison], ); const getColumnConfigs = useCallback( @@ -773,8 +928,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( @@ -844,6 +999,9 @@ export default function TableChart<D extends DataRecord = DataRecord>( renderGroupingHeaders={ !isEmpty(groupHeaderColumns) ? renderGroupingHeaders : undefined } + renderTimeComparisonDropdown={ + isUsingTimeComparison ? renderTimeComparisonDropdown : undefined + } /> </Styles> );
