You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by li...@apache.org on 2024/03/26 18:40:19 UTC

(superset) branch table-time-comparison updated: feat(time-comparison-table): show and hide time comparison columns (#27446)

This is an automated email from the ASF dual-hosted git repository.

lilykuang pushed a commit to branch table-time-comparison
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/table-time-comparison by this push:
     new 9f062edb73 feat(time-comparison-table): show and hide time comparison columns (#27446)
9f062edb73 is described below

commit 9f062edb73eb15bc6aee43a9a90aa1662e39949f
Author: Lily Kuang <li...@preset.io>
AuthorDate: Tue Mar 26 11:40:12 2024 -0700

    feat(time-comparison-table): show and hide time comparison columns (#27446)
---
 .../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 ++++++++++++++++++++-
 6 files changed, 190 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..242029e163 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,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}
@@ -375,7 +380,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
@@ -386,6 +395,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..c274296ccd 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';
@@ -242,6 +249,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 +265,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
@@ -371,6 +389,38 @@ 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 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,
+    enableTimeComparison,
+    hideComparisonKeys,
+    selectedComparisonColumns,
+  ]);
+
   const handleContextMenu =
     onContextMenu && !isRawRecords
       ? (
@@ -384,7 +434,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 +466,6 @@ export default function TableChart<D extends DataRecord = DataRecord>(
         }
       : undefined;
 
-  const comparisonLabels = [t('Main'), '#', '△', '%'];
-
   const getHeaderColumns = (
     columnsMeta: DataColumnMeta[],
     enableTimeComparison?: boolean,
@@ -447,6 +495,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 = [];
@@ -472,6 +603,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>,
       );
 
@@ -499,8 +654,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 +924,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 +995,9 @@ export default function TableChart<D extends DataRecord = DataRecord>(
         renderGroupingHeaders={
           !isEmpty(groupHeaderColumns) ? renderGroupingHeaders : undefined
         }
+        renderTimeComparisonDropdown={
+          enableTimeComparison ? renderTimeComparisonDropdown : undefined
+        }
       />
     </Styles>
   );