Skip to content

Commit

Permalink
feat: drill by display chart (#23524)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lily Kuang authored Apr 4, 2023
1 parent 95db6c0 commit 4452a65
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 57 deletions.
1 change: 1 addition & 0 deletions superset-frontend/spec/fixtures/mockChartQueries.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default {
triggerQuery: false,
lastRendered: 0,
form_data: {
adhoc_filters: [],
datasource: datasourceId,
viz_type: 'pie',
slice_id: sliceId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* 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 { render, screen, waitFor } from 'spec/helpers/testing-library';
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<string, any> = {}) => {
const props = {
column: { column_name: 'state' },
formData: { ...chart.form_data, viz_type: 'pie' },
groupbyFieldName: 'groupby',
...overrides,
};
return render(
<DrillByChart
filters={[
{
col: 'gender',
op: '==',
val: 'boy',
formattedVal: 'boy',
},
]}
{...props}
/>,
{
useRedux: true,
},
);
};

const waitForRender = (overrides: Record<string, any> = {}) =>
waitFor(() => setup(overrides));

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();
expect(await screen.findByText('No Results')).toBeInTheDocument();
});
94 changes: 94 additions & 0 deletions superset-frontend/src/components/Chart/DrillBy/DrillByChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* 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, { useEffect, useState } from 'react';
import {
Behavior,
BinaryQueryObjectFilterClause,
Column,
css,
SuperChart,
} from '@superset-ui/core';
import { simpleFilterToAdhoc } from 'src/utils/simpleFilterToAdhoc';
import { getChartDataRequest } from 'src/components/Chart/chartAction';
import Loading from 'src/components/Loading';

interface DrillByChartProps {
column?: Column;
filters?: BinaryQueryObjectFilterClause[];
formData: { [key: string]: any; viz_type: string };
groupbyFieldName?: string;
}

export default function DrillByChart({
column,
filters,
formData,
groupbyFieldName = 'groupby',
}: DrillByChartProps) {
let updatedFormData = formData;
let groupbyField: any = [];
const [chartDataResult, setChartDataResult] = useState();

if (groupbyFieldName && column) {
groupbyField = Array.isArray(formData[groupbyFieldName])
? [column.column_name]
: column.column_name;
}

if (filters) {
const adhocFilters = filters.map(filter => simpleFilterToAdhoc(filter));
updatedFormData = {
...formData,
adhoc_filters: [...formData.adhoc_filters, ...adhocFilters],
[groupbyFieldName]: groupbyField,
};
}

useEffect(() => {
getChartDataRequest({
formData: updatedFormData,
}).then(({ json }) => {
setChartDataResult(json.result);
});
}, []);

return (
<div
css={css`
width: 100%;
height: 100%;
`}
>
{chartDataResult ? (
<SuperChart
disableErrorBoundary
behaviors={[Behavior.INTERACTIVE_CHART]}
chartType={formData.viz_type}
enableNoResults
formData={updatedFormData}
height="100%"
queriesData={chartDataResult}
width="100%"
/>
) : (
<Loading />
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,18 @@ export interface DrillByMenuItemsProps {
contextMenuY?: number;
submenuIndex?: number;
groupbyFieldName?: string;
onSelection?: () => void;
onClick?: (event: MouseEvent) => void;
}

export const DrillByMenuItems = ({
filters,
groupbyFieldName,
formData,
contextMenuY = 0,
submenuIndex = 0,
onSelection = () => {},
onClick = () => {},
...rest
}: DrillByMenuItemsProps) => {
const theme = useTheme();
Expand All @@ -73,10 +78,15 @@ export const DrillByMenuItems = ({
const [showModal, setShowModal] = useState(false);
const [currentColumn, setCurrentColumn] = useState();

const openModal = useCallback(column => {
setCurrentColumn(column);
setShowModal(true);
}, []);
const openModal = useCallback(
(event, column) => {
onClick(event);
onSelection();
setCurrentColumn(column);
setShowModal(true);
},
[onClick, onSelection],
);
const closeModal = useCallback(() => {
setShowModal(false);
}, []);
Expand Down Expand Up @@ -218,7 +228,7 @@ export const DrillByMenuItems = ({
key={`drill-by-item-${column.column_name}`}
tooltipText={column.verbose_name || column.column_name}
{...rest}
onClick={() => openModal(column)}
onClick={e => openModal(e, column)}
>
{column.verbose_name || column.column_name}
</MenuItemWithTruncation>
Expand All @@ -235,6 +245,7 @@ export const DrillByMenuItems = ({
column={currentColumn}
filters={filters}
formData={formData}
groupbyFieldName={groupbyFieldName}
onHideModal={closeModal}
showModal={showModal}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ 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 fetchMock from 'fetch-mock';
import DrillByModal from './DrillByModal';

const CHART_DATA_ENDPOINT =
'glob:*api/v1/chart/data?form_data=%7B%22slice_id%22%3A18%7D';

fetchMock.post(CHART_DATA_ENDPOINT, { body: {} }, {});

const { form_data: formData } = chartQueries[sliceId];
const { slice_name: chartName } = formData;
const drillByModalState = {
Expand All @@ -41,21 +47,20 @@ const drillByModalState = {
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,
Expand All @@ -66,6 +71,7 @@ const renderModal = async (state?: object) => {
userEvent.click(screen.getByRole('button', { name: 'Show modal' }));
await screen.findByRole('dialog', { name: `Drill by: ${chartName}` });
};
afterEach(fetchMock.restore);

test('should render the title', async () => {
await renderModal(drillByModalState);
Expand Down
14 changes: 11 additions & 3 deletions superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Modal from 'src/components/Modal';
import Button from 'src/components/Button';
import { useSelector } from 'react-redux';
import { DashboardLayout, RootState } from 'src/dashboard/types';
import DrillByChart from './DrillByChart';

interface ModalFooterProps {
exploreChart: () => void;
Expand All @@ -44,7 +45,7 @@ const ModalFooter = ({ exploreChart, closeModal }: ModalFooterProps) => (
buttonStyle="primary"
buttonSize="small"
onClick={closeModal}
data-test="close-drillby-modal"
data-test="close-drill-by-modal"
>
{t('Close')}
</Button>
Expand All @@ -55,14 +56,16 @@ interface DrillByModalProps {
column?: Column;
filters?: BinaryQueryObjectFilterClause[];
formData: { [key: string]: any; viz_type: string };
groupbyFieldName?: string;
onHideModal: () => void;
showModal: boolean;
}

export default function DrillByModal({
column,
formData,
filters,
formData,
groupbyFieldName,
onHideModal,
showModal,
}: DrillByModalProps) {
Expand Down Expand Up @@ -102,7 +105,12 @@ export default function DrillByModal({
destroyOnClose
maskClosable={false}
>
{}
<DrillByChart
column={column}
filters={filters}
formData={formData}
groupbyFieldName={groupbyFieldName}
/>
</Modal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import React, { ReactNode } from 'react';
import { css, truncationCSS, useCSSTextTruncation } from '@superset-ui/core';
import { Menu } from 'src/components/Menu';
import { Tooltip } from 'src/components/Tooltip';
import type { MenuProps } from 'antd/lib/menu';

export type MenuItemWithTruncationProps = {
tooltipText: ReactNode;
children: ReactNode;
onClick?: () => void;
onClick?: MenuProps['onClick'];
};

export const MenuItemWithTruncation = ({
Expand Down
Loading

0 comments on commit 4452a65

Please sign in to comment.