Skip to content

Commit 117360c

Browse files
authored
feat: Drill by open in Explore (#23575)
1 parent 9d2f43d commit 117360c

File tree

6 files changed

+171
-117
lines changed

6 files changed

+171
-117
lines changed

superset-frontend/packages/superset-ui-core/src/query/types/Filter.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,17 @@ import {
2727
} from './Operator';
2828
import { TimeGranularity } from '../../time-format';
2929

30-
interface BaseSimpleAdhocFilter {
31-
expressionType: 'SIMPLE';
30+
interface BaseAdhocFilter {
3231
clause: 'WHERE' | 'HAVING';
33-
subject: string;
3432
timeGrain?: TimeGranularity;
3533
isExtra?: boolean;
3634
}
3735

36+
interface BaseSimpleAdhocFilter extends BaseAdhocFilter {
37+
expressionType: 'SIMPLE';
38+
subject: string;
39+
}
40+
3841
export type UnaryAdhocFilter = BaseSimpleAdhocFilter & {
3942
operator: UnaryOperator;
4043
};
@@ -54,9 +57,8 @@ export type SimpleAdhocFilter =
5457
| BinaryAdhocFilter
5558
| SetAdhocFilter;
5659

57-
export interface FreeFormAdhocFilter {
60+
export interface FreeFormAdhocFilter extends BaseAdhocFilter {
5861
expressionType: 'SQL';
59-
clause: 'WHERE' | 'HAVING';
6062
sqlExpression: string;
6163
}
6264

superset-frontend/src/components/Chart/DrillBy/DrillByChart.test.tsx

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,10 @@ const fetchWithNoData = () => {
4040
});
4141
};
4242

43-
const setup = (overrides: Record<string, any> = {}) => {
44-
const props = {
45-
column: { column_name: 'state' },
46-
formData: { ...chart.form_data, viz_type: 'pie' },
47-
groupbyFieldName: 'groupby',
48-
...overrides,
49-
};
50-
return render(
51-
<DrillByChart
52-
filters={[
53-
{
54-
col: 'gender',
55-
op: '==',
56-
val: 'boy',
57-
formattedVal: 'boy',
58-
},
59-
]}
60-
{...props}
61-
/>,
62-
{
63-
useRedux: true,
64-
},
65-
);
66-
};
43+
const setup = (overrides: Record<string, any> = {}) =>
44+
render(<DrillByChart formData={{ ...chart.form_data, ...overrides }} />, {
45+
useRedux: true,
46+
});
6747

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

superset-frontend/src/components/Chart/DrillBy/DrillByChart.tsx

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,52 +17,20 @@
1717
* under the License.
1818
*/
1919
import React, { useEffect, useState } from 'react';
20-
import {
21-
Behavior,
22-
BinaryQueryObjectFilterClause,
23-
Column,
24-
css,
25-
SuperChart,
26-
} from '@superset-ui/core';
27-
import { simpleFilterToAdhoc } from 'src/utils/simpleFilterToAdhoc';
20+
import { BaseFormData, Behavior, css, SuperChart } from '@superset-ui/core';
2821
import { getChartDataRequest } from 'src/components/Chart/chartAction';
2922
import Loading from 'src/components/Loading';
3023

3124
interface DrillByChartProps {
32-
column?: Column;
33-
filters?: BinaryQueryObjectFilterClause[];
34-
formData: { [key: string]: any; viz_type: string };
35-
groupbyFieldName?: string;
25+
formData: BaseFormData & { [key: string]: any };
3626
}
3727

38-
export default function DrillByChart({
39-
column,
40-
filters,
41-
formData,
42-
groupbyFieldName = 'groupby',
43-
}: DrillByChartProps) {
44-
let updatedFormData = formData;
45-
let groupbyField: any = [];
28+
export default function DrillByChart({ formData }: DrillByChartProps) {
4629
const [chartDataResult, setChartDataResult] = useState();
4730

48-
if (column) {
49-
groupbyField = Array.isArray(formData[groupbyFieldName])
50-
? [column.column_name]
51-
: column.column_name;
52-
}
53-
54-
if (filters) {
55-
const adhocFilters = filters.map(filter => simpleFilterToAdhoc(filter));
56-
updatedFormData = {
57-
...formData,
58-
adhoc_filters: [...formData.adhoc_filters, ...adhocFilters],
59-
[groupbyFieldName]: groupbyField,
60-
};
61-
}
62-
6331
useEffect(() => {
6432
getChartDataRequest({
65-
formData: updatedFormData,
33+
formData,
6634
}).then(({ json }) => {
6735
setChartDataResult(json.result);
6836
});
@@ -81,7 +49,7 @@ export default function DrillByChart({
8149
behaviors={[Behavior.INTERACTIVE_CHART]}
8250
chartType={formData.viz_type}
8351
enableNoResults
84-
formData={updatedFormData}
52+
formData={formData}
8553
queriesData={chartDataResult}
8654
height="100%"
8755
width="100%"

superset-frontend/src/components/Chart/DrillBy/DrillByModal.test.tsx

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,35 @@
1818
*/
1919

2020
import React, { useState } from 'react';
21+
import fetchMock from 'fetch-mock';
22+
import { omit, isUndefined, omitBy } from 'lodash';
2123
import userEvent from '@testing-library/user-event';
24+
import { waitFor } from '@testing-library/react';
2225
import { render, screen } from 'spec/helpers/testing-library';
2326
import chartQueries, { sliceId } from 'spec/fixtures/mockChartQueries';
2427
import mockState from 'spec/fixtures/mockState';
25-
import fetchMock from 'fetch-mock';
28+
import { DashboardPageIdContext } from 'src/dashboard/containers/DashboardPage';
2629
import DrillByModal from './DrillByModal';
2730

28-
const CHART_DATA_ENDPOINT =
29-
'glob:*api/v1/chart/data?form_data=%7B%22slice_id%22%3A18%7D';
30-
31-
fetchMock.post(CHART_DATA_ENDPOINT, { body: {} }, {});
31+
const CHART_DATA_ENDPOINT = 'glob:*/api/v1/chart/data*';
32+
const FORM_DATA_KEY_ENDPOINT = 'glob:*/api/v1/explore/form_data';
3233

3334
const { form_data: formData } = chartQueries[sliceId];
3435
const { slice_name: chartName } = formData;
3536
const drillByModalState = {
3637
...mockState,
3738
dashboardLayout: {
38-
CHART_ID: {
39-
id: 'CHART_ID',
40-
meta: {
41-
chartId: formData.slice_id,
42-
sliceName: chartName,
39+
past: [],
40+
present: {
41+
CHART_ID: {
42+
id: 'CHART_ID',
43+
meta: {
44+
chartId: formData.slice_id,
45+
sliceName: chartName,
46+
},
4347
},
4448
},
49+
future: [],
4550
},
4651
};
4752
const dataset = {
@@ -56,12 +61,13 @@ const dataset = {
5661
},
5762
],
5863
};
59-
const renderModal = async (state?: object) => {
64+
65+
const renderModal = async () => {
6066
const DrillByModalWrapper = () => {
6167
const [showModal, setShowModal] = useState(false);
6268

6369
return (
64-
<>
70+
<DashboardPageIdContext.Provider value="1">
6571
<button type="button" onClick={() => setShowModal(true)}>
6672
Show modal
6773
</button>
@@ -71,23 +77,29 @@ const renderModal = async (state?: object) => {
7177
onHideModal={() => setShowModal(false)}
7278
dataset={dataset}
7379
/>
74-
</>
80+
</DashboardPageIdContext.Provider>
7581
);
7682
};
7783
render(<DrillByModalWrapper />, {
7884
useDnd: true,
7985
useRedux: true,
8086
useRouter: true,
81-
initialState: state,
87+
initialState: drillByModalState,
8288
});
8389

8490
userEvent.click(screen.getByRole('button', { name: 'Show modal' }));
8591
await screen.findByRole('dialog', { name: `Drill by: ${chartName}` });
8692
};
93+
94+
beforeEach(() => {
95+
fetchMock
96+
.post(CHART_DATA_ENDPOINT, { body: {} }, {})
97+
.post(FORM_DATA_KEY_ENDPOINT, { key: '123' });
98+
});
8799
afterEach(fetchMock.restore);
88100

89101
test('should render the title', async () => {
90-
await renderModal(drillByModalState);
102+
await renderModal();
91103
expect(screen.getByText(`Drill by: ${chartName}`)).toBeInTheDocument();
92104
});
93105

@@ -105,3 +117,30 @@ test('should close the modal', async () => {
105117
userEvent.click(screen.getAllByRole('button', { name: 'Close' })[1]);
106118
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
107119
});
120+
121+
test('should generate Explore url', async () => {
122+
await renderModal();
123+
await waitFor(() => fetchMock.called(FORM_DATA_KEY_ENDPOINT));
124+
const expectedRequestPayload = {
125+
form_data: {
126+
...omitBy(
127+
omit(formData, ['slice_id', 'slice_name', 'dashboards']),
128+
isUndefined,
129+
),
130+
slice_id: 0,
131+
},
132+
datasource_id: Number(formData.datasource.split('__')[0]),
133+
datasource_type: formData.datasource.split('__')[1],
134+
};
135+
136+
const parsedRequestPayload = JSON.parse(
137+
fetchMock.lastCall()?.[1]?.body as string,
138+
);
139+
parsedRequestPayload.form_data = JSON.parse(parsedRequestPayload.form_data);
140+
141+
expect(parsedRequestPayload).toEqual(expectedRequestPayload);
142+
143+
expect(
144+
await screen.findByRole('link', { name: 'Edit chart' }),
145+
).toHaveAttribute('href', '/explore/?form_data_key=123&dashboard_page_id=1');
146+
});

0 commit comments

Comments
 (0)