From 02275587d1c1ed7d93c8d4de1fa132e157a991d2 Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Tue, 11 Apr 2023 07:59:03 -0700 Subject: [PATCH] feat: implement drill by table (#23603) --- .../Chart/DrillBy/DrillByChart.test.tsx | 41 +++------ .../components/Chart/DrillBy/DrillByChart.tsx | 49 +++++----- .../Chart/DrillBy/DrillByMenuItems.tsx | 19 ++-- .../Chart/DrillBy/DrillByModal.test.tsx | 44 +++++++-- .../components/Chart/DrillBy/DrillByModal.tsx | 90 +++++++++++++++++-- .../src/components/Chart/types.ts | 5 ++ 6 files changed, 162 insertions(+), 86 deletions(-) diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByChart.test.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByChart.test.tsx index 55dbd3dee37a8..02d1754939628 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByChart.test.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByChart.test.tsx @@ -22,28 +22,18 @@ import chartQueries, { sliceId } from 'spec/fixtures/mockChartQueries'; import fetchMock from 'fetch-mock'; import DrillByChart from './DrillByChart'; -const CHART_DATA_ENDPOINT = - 'glob:*api/v1/chart/data?form_data=%7B%22slice_id%22%3A18%7D'; - const chart = chartQueries[sliceId]; -const fetchWithNoData = () => { - fetchMock.post(CHART_DATA_ENDPOINT, { - result: [ - { - total_count: 0, - data: [], - colnames: [], - coltypes: [], - }, - ], - }); -}; - -const setup = (overrides: Record = {}) => - render(, { - useRedux: true, - }); +const setup = (overrides: Record = {}, result?: any) => + render( + , + { + useRedux: true, + }, + ); const waitForRender = (overrides: Record = {}) => waitFor(() => setup(overrides)); @@ -51,20 +41,11 @@ const waitForRender = (overrides: Record = {}) => afterEach(fetchMock.restore); test('should render', async () => { - fetchWithNoData(); const { container } = await waitForRender(); expect(container).toBeInTheDocument(); }); -test('should render loading indicator', async () => { - setup(); - await waitFor(() => - expect(screen.getByLabelText('Loading')).toBeInTheDocument(), - ); -}); - test('should render the "No results" components', async () => { - fetchWithNoData(); - setup(); + setup({}, []); expect(await screen.findByText('No Results')).toBeInTheDocument(); }); diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByChart.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByChart.tsx index 5d1a3dec236b3..d19dbe9137669 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByChart.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByChart.tsx @@ -16,26 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect, useState } from 'react'; -import { BaseFormData, Behavior, css, SuperChart } from '@superset-ui/core'; -import { getChartDataRequest } from 'src/components/Chart/chartAction'; -import Loading from 'src/components/Loading'; +import React from 'react'; +import { + BaseFormData, + Behavior, + QueryData, + SuperChart, + css, +} from '@superset-ui/core'; interface DrillByChartProps { formData: BaseFormData & { [key: string]: any }; + result: QueryData[]; } -export default function DrillByChart({ formData }: DrillByChartProps) { - const [chartDataResult, setChartDataResult] = useState(); - - useEffect(() => { - getChartDataRequest({ - formData, - }).then(({ json }) => { - setChartDataResult(json.result); - }); - }, []); - +export default function DrillByChart({ formData, result }: DrillByChartProps) { return (
- {chartDataResult ? ( - - ) : ( - - )} +
); } diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx index d1a27d5ea9db6..4064006fac861 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByMenuItems.tsx @@ -244,15 +244,16 @@ export const DrillByMenuItems = ({ )} - + {showModal && ( + + )} ); }; diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx index 54eb65302822a..f08a9701a85a2 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx @@ -71,12 +71,13 @@ const renderModal = async () => { - setShowModal(false)} - dataset={dataset} - /> + {showModal && ( + setShowModal(false)} + dataset={dataset} + /> + )} ); }; @@ -118,9 +119,16 @@ test('should close the modal', async () => { expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); +test('should render loading indicator', async () => { + await renderModal(); + await waitFor(() => + expect(screen.getByLabelText('Loading')).toBeInTheDocument(), + ); +}); + test('should generate Explore url', async () => { await renderModal(); - await waitFor(() => fetchMock.called(FORM_DATA_KEY_ENDPOINT)); + await waitFor(() => fetchMock.called(CHART_DATA_ENDPOINT)); const expectedRequestPayload = { form_data: { ...omitBy( @@ -128,6 +136,9 @@ test('should generate Explore url', async () => { isUndefined, ), slice_id: 0, + result_format: 'json', + result_type: 'full', + force: false, }, datasource_id: Number(formData.datasource.split('__')[0]), datasource_type: formData.datasource.split('__')[1], @@ -136,11 +147,26 @@ test('should generate Explore url', async () => { const parsedRequestPayload = JSON.parse( fetchMock.lastCall()?.[1]?.body as string, ); - parsedRequestPayload.form_data = JSON.parse(parsedRequestPayload.form_data); - expect(parsedRequestPayload).toEqual(expectedRequestPayload); + expect(parsedRequestPayload.form_data).toEqual( + expectedRequestPayload.form_data, + ); expect( await screen.findByRole('link', { name: 'Edit chart' }), ).toHaveAttribute('href', '/explore/?form_data_key=123&dashboard_page_id=1'); }); + +test('should render radio buttons', async () => { + await renderModal(); + const chartRadio = screen.getByRole('radio', { name: /chart/i }); + const tableRadio = screen.getByRole('radio', { name: /table/i }); + + expect(chartRadio).toBeInTheDocument(); + expect(tableRadio).toBeInTheDocument(); + expect(chartRadio).toBeChecked(); + expect(tableRadio).not.toBeChecked(); + userEvent.click(tableRadio); + expect(chartRadio).not.toBeChecked(); + expect(tableRadio).toBeChecked(); +}); diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx index 2c87ee0cab850..776346705c269 100644 --- a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx +++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx @@ -22,6 +22,7 @@ import { BaseFormData, BinaryQueryObjectFilterClause, Column, + QueryData, css, ensureIsArray, t, @@ -30,19 +31,24 @@ import { import { useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; import Modal from 'src/components/Modal'; +import Loading from 'src/components/Loading'; import Button from 'src/components/Button'; +import { Radio } from 'src/components/Radio'; import { DashboardLayout, RootState } from 'src/dashboard/types'; import { DashboardPageIdContext } from 'src/dashboard/containers/DashboardPage'; import { postFormData } from 'src/explore/exploreUtils/formData'; import { noOp } from 'src/utils/common'; import { simpleFilterToAdhoc } from 'src/utils/simpleFilterToAdhoc'; import { useDatasetMetadataBar } from 'src/features/datasets/metadataBar/useDatasetMetadataBar'; -import { Dataset } from '../types'; +import { SingleQueryResultPane } from 'src/explore/components/DataTablesPane/components/SingleQueryResultPane'; +import { Dataset, DrillByType } from '../types'; import DrillByChart from './DrillByChart'; +import { getChartDataRequest } from '../chartAction'; +const DATA_SIZE = 15; interface ModalFooterProps { - formData: BaseFormData; closeModal?: () => void; + formData: BaseFormData; } const ModalFooter = ({ formData, closeModal }: ModalFooterProps) => { @@ -74,6 +80,7 @@ const ModalFooter = ({ formData, closeModal }: ModalFooterProps) => { {t('Edit chart')} +