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 2023/03/29 20:14:03 UTC
[superset] branch master updated: feat: drill by modal (#23458)
This is an automated email from the ASF dual-hosted git repository.
lilykuang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 97b5cdd588 feat: drill by modal (#23458)
97b5cdd588 is described below
commit 97b5cdd588ceb2702098ca0f569750f7f16f2bbb
Author: Lily Kuang <li...@preset.io>
AuthorDate: Wed Mar 29 13:13:52 2023 -0700
feat: drill by modal (#23458)
Co-authored-by: Kamil Gabryjelski <ka...@gmail.com>
---
.../components/Chart/DrillBy/DrillByMenuItems.tsx | 134 ++++++++++++---------
.../components/Chart/DrillBy/DrillByModal.test.tsx | 88 ++++++++++++++
.../src/components/Chart/DrillBy/DrillByModal.tsx | 108 +++++++++++++++++
3 files changed, 274 insertions(+), 56 deletions(-)
diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx
index 1da50a412f..07b00c4be3 100644
--- a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx
+++ b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx
@@ -44,6 +44,7 @@ import {
supersetGetCache,
} from 'src/utils/cachedSupersetGet';
import { MenuItemTooltip } from '../DisabledMenuItemTooltip';
+import DrillByModal from './DrillByModal';
import { getSubmenuYOffset } from '../utils';
import { MenuItemWithTruncation } from '../MenuItemWithTruncation';
@@ -69,6 +70,17 @@ export const DrillByMenuItems = ({
const theme = useTheme();
const [searchInput, setSearchInput] = useState('');
const [columns, setColumns] = useState<Column[]>([]);
+ const [showModal, setShowModal] = useState(false);
+ const [currentColumn, setCurrentColumn] = useState();
+
+ const openModal = useCallback(column => {
+ setCurrentColumn(column);
+ setShowModal(true);
+ }, []);
+ const closeModal = useCallback(() => {
+ setShowModal(false);
+ }, []);
+
useEffect(() => {
// Input is displayed only when columns.length > SHOW_COLUMNS_SEARCH_THRESHOLD
// Reset search input in case Input gets removed
@@ -161,61 +173,71 @@ export const DrillByMenuItems = ({
}
return (
- <Menu.SubMenu
- title={t('Drill by')}
- key="drill-by-submenu"
- popupClassName="chart-context-submenu"
- popupOffset={[0, submenuYOffset]}
- {...rest}
- >
- <div data-test="drill-by-submenu">
- {columns.length > SHOW_COLUMNS_SEARCH_THRESHOLD && (
- <Input
- prefix={
- <Icons.Search
- iconSize="l"
- iconColor={theme.colors.grayscale.light1}
- />
- }
- onChange={handleInput}
- placeholder={t('Search columns')}
- value={searchInput}
- onClick={e => {
- // prevent closing menu when clicking on input
- e.nativeEvent.stopImmediatePropagation();
- }}
- allowClear
- css={css`
- width: auto;
- max-width: 100%;
- margin: ${theme.gridUnit * 2}px ${theme.gridUnit * 3}px;
- box-shadow: none;
- `}
- />
- )}
- {filteredColumns.length ? (
- <div
- css={css`
- max-height: ${MAX_SUBMENU_HEIGHT}px;
- overflow: auto;
- `}
- >
- {filteredColumns.map(column => (
- <MenuItemWithTruncation
- key={`drill-by-item-${column.column_name}`}
- tooltipText={column.verbose_name || column.column_name}
- {...rest}
- >
- {column.verbose_name || column.column_name}
- </MenuItemWithTruncation>
- ))}
- </div>
- ) : (
- <Menu.Item disabled key="no-drill-by-columns-found" {...rest}>
- {t('No columns found')}
- </Menu.Item>
- )}
- </div>
- </Menu.SubMenu>
+ <>
+ <Menu.SubMenu
+ title={t('Drill by')}
+ key="drill-by-submenu"
+ popupClassName="chart-context-submenu"
+ popupOffset={[0, submenuYOffset]}
+ {...rest}
+ >
+ <div data-test="drill-by-submenu">
+ {columns.length > SHOW_COLUMNS_SEARCH_THRESHOLD && (
+ <Input
+ prefix={
+ <Icons.Search
+ iconSize="l"
+ iconColor={theme.colors.grayscale.light1}
+ />
+ }
+ onChange={handleInput}
+ placeholder={t('Search columns')}
+ value={searchInput}
+ onClick={e => {
+ // prevent closing menu when clicking on input
+ e.nativeEvent.stopImmediatePropagation();
+ }}
+ allowClear
+ css={css`
+ width: auto;
+ max-width: 100%;
+ margin: ${theme.gridUnit * 2}px ${theme.gridUnit * 3}px;
+ box-shadow: none;
+ `}
+ />
+ )}
+ {filteredColumns.length ? (
+ <div
+ css={css`
+ max-height: ${MAX_SUBMENU_HEIGHT}px;
+ overflow: auto;
+ `}
+ >
+ {filteredColumns.map(column => (
+ <MenuItemWithTruncation
+ key={`drill-by-item-${column.column_name}`}
+ tooltipText={column.verbose_name || column.column_name}
+ {...rest}
+ onClick={() => openModal(column)}
+ >
+ {column.verbose_name || column.column_name}
+ </MenuItemWithTruncation>
+ ))}
+ </div>
+ ) : (
+ <Menu.Item disabled key="no-drill-by-columns-found" {...rest}>
+ {t('No columns found')}
+ </Menu.Item>
+ )}
+ </div>
+ </Menu.SubMenu>
+ <DrillByModal
+ column={currentColumn}
+ filters={filters}
+ formData={formData}
+ onHideModal={closeModal}
+ showModal={showModal}
+ />
+ </>
);
};
diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx
new file mode 100644
index 0000000000..10d9e1af83
--- /dev/null
+++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState } from 'react';
+import userEvent from '@testing-library/user-event';
+import { render, screen } from 'spec/helpers/testing-library';
+import chartQueries, { sliceId } from 'spec/fixtures/mockChartQueries';
+import mockState from 'spec/fixtures/mockState';
+import DrillByModal from './DrillByModal';
+
+const { form_data: formData } = chartQueries[sliceId];
+const { slice_name: chartName } = formData;
+const drillByModalState = {
+ ...mockState,
+ dashboardLayout: {
+ CHART_ID: {
+ id: 'CHART_ID',
+ meta: {
+ chartId: formData.slice_id,
+ sliceName: chartName,
+ },
+ },
+ },
+};
+const renderModal = async (state?: object) => {
+ const DrillByModalWrapper = () => {
+ const [showModal, setShowModal] = useState(false);
+ return (
+ <>
+ <button type="button" onClick={() => setShowModal(true)}>
+ Show modal
+ </button>
+ <DrillByModal
+ formData={formData}
+ filters={[]}
+ showModal={showModal}
+ onHideModal={() => setShowModal(false)}
+ />
+ </>
+ );
+ };
+
+ render(<DrillByModalWrapper />, {
+ useDnd: true,
+ useRedux: true,
+ useRouter: true,
+ initialState: state,
+ });
+
+ userEvent.click(screen.getByRole('button', { name: 'Show modal' }));
+ await screen.findByRole('dialog', { name: `Drill by: ${chartName}` });
+};
+
+test('should render the title', async () => {
+ await renderModal(drillByModalState);
+ expect(screen.getByText(`Drill by: ${chartName}`)).toBeInTheDocument();
+});
+
+test('should render the button', async () => {
+ await renderModal();
+ expect(
+ screen.getByRole('button', { name: 'Edit chart' }),
+ ).toBeInTheDocument();
+ expect(screen.getAllByRole('button', { name: 'Close' })).toHaveLength(2);
+});
+
+test('should close the modal', async () => {
+ await renderModal();
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
+ userEvent.click(screen.getAllByRole('button', { name: 'Close' })[1]);
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
+});
diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx
new file mode 100644
index 0000000000..527284ebe5
--- /dev/null
+++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx
@@ -0,0 +1,108 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import {
+ BinaryQueryObjectFilterClause,
+ Column,
+ css,
+ t,
+ useTheme,
+} from '@superset-ui/core';
+import Modal from 'src/components/Modal';
+import Button from 'src/components/Button';
+import { useSelector } from 'react-redux';
+import { DashboardLayout, RootState } from 'src/dashboard/types';
+
+interface ModalFooterProps {
+ exploreChart: () => void;
+ closeModal?: () => void;
+}
+
+const ModalFooter = ({ exploreChart, closeModal }: ModalFooterProps) => (
+ <>
+ <Button buttonStyle="secondary" buttonSize="small" onClick={exploreChart}>
+ {t('Edit chart')}
+ </Button>
+ <Button
+ buttonStyle="primary"
+ buttonSize="small"
+ onClick={closeModal}
+ data-test="close-drillby-modal"
+ >
+ {t('Close')}
+ </Button>
+ </>
+);
+
+interface DrillByModalProps {
+ column?: Column;
+ filters?: BinaryQueryObjectFilterClause[];
+ formData: { [key: string]: any; viz_type: string };
+ onHideModal: () => void;
+ showModal: boolean;
+}
+
+export default function DrillByModal({
+ column,
+ formData,
+ filters,
+ onHideModal,
+ showModal,
+}: DrillByModalProps) {
+ const theme = useTheme();
+ const dashboardLayout = useSelector<RootState, DashboardLayout>(
+ state => state.dashboardLayout.present,
+ );
+ const chartLayoutItem = Object.values(dashboardLayout).find(
+ layoutItem => layoutItem.meta?.chartId === formData.slice_id,
+ );
+ const chartName =
+ chartLayoutItem?.meta.sliceNameOverride || chartLayoutItem?.meta.sliceName;
+ const exploreChart = () => {};
+
+ return (
+ <Modal
+ css={css`
+ .ant-modal-footer {
+ border-top: none;
+ }
+ `}
+ show={showModal}
+ onHide={onHideModal ?? (() => null)}
+ title={t('Drill by: %s', chartName)}
+ footer={<ModalFooter exploreChart={exploreChart} />}
+ responsive
+ resizable
+ resizableConfig={{
+ minHeight: theme.gridUnit * 128,
+ minWidth: theme.gridUnit * 128,
+ defaultSize: {
+ width: 'auto',
+ height: '75vh',
+ },
+ }}
+ draggable
+ destroyOnClose
+ maskClosable={false}
+ >
+ {}
+ </Modal>
+ );
+}