diff --git a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx index 3ee67e7f3868d..b4a706c70f98c 100644 --- a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx @@ -135,6 +135,7 @@ const SliceHeader: FC = ({ logExploreChart = () => ({}), logEvent, exportCSV = () => ({}), + exportXLSX = () => ({}), editMode = false, annotationQuery = {}, annotationError = {}, @@ -264,6 +265,7 @@ const SliceHeader: FC = ({ logEvent={logEvent} exportCSV={exportCSV} exportFullCSV={exportFullCSV} + exportXLSX={exportXLSX} supersetCanExplore={supersetCanExplore} supersetCanShare={supersetCanShare} supersetCanCSV={supersetCanCSV} diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx index 26f3a6bbbf8e2..d1a33da259b07 100644 --- a/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx @@ -44,6 +44,7 @@ const createProps = (viz_type = 'sunburst') => exploreChart: jest.fn(), exportCSV: jest.fn(), exportFullCSV: jest.fn(), + exportXLSX: jest.fn(), forceRefresh: jest.fn(), handleToggleFullSize: jest.fn(), toggleExpandSlice: jest.fn(), @@ -126,6 +127,8 @@ test('Should render default props', () => { // @ts-ignore delete props.exportCSV; // @ts-ignore + delete props.exportXLSX; + // @ts-ignore delete props.cachedDttm; // @ts-ignore delete props.updatedDttm; @@ -170,6 +173,16 @@ test('Should "export to CSV"', async () => { expect(props.exportCSV).toBeCalledWith(371); }); +test('Should "export to Excel"', async () => { + const props = createProps(); + renderWrapper(props); + expect(props.exportXLSX).toBeCalledTimes(0); + userEvent.hover(screen.getByText('Download')); + userEvent.click(await screen.findByText('Export to Excel')); + expect(props.exportXLSX).toBeCalledTimes(1); + expect(props.exportXLSX).toBeCalledWith(371); +}); + test('Should not show "Download" if slice is filter box', () => { const props = createProps('filter_box'); renderWrapper(props); diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx index bba888710a54c..72b0fb1aa0788 100644 --- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx @@ -64,6 +64,7 @@ const MENU_KEYS = { EXPLORE_CHART: 'explore_chart', EXPORT_CSV: 'export_csv', EXPORT_FULL_CSV: 'export_full_csv', + EXPORT_XLSX: 'export_xlsx', FORCE_REFRESH: 'force_refresh', FULLSCREEN: 'fullscreen', TOGGLE_CHART_DESCRIPTION: 'toggle_chart_description', @@ -144,6 +145,7 @@ export interface SliceHeaderControlsProps { toggleExpandSlice?: (sliceId: number) => void; exportCSV?: (sliceId: number) => void; exportFullCSV?: (sliceId: number) => void; + exportXLSX?: (sliceId: number) => void; handleToggleFullSize: () => void; addDangerToast: (message: string) => void; @@ -294,6 +296,10 @@ const SliceHeaderControls = (props: SliceHeaderControlsPropsWithRouter) => { // eslint-disable-next-line no-unused-expressions props.exportFullCSV?.(props.slice.slice_id); break; + case MENU_KEYS.EXPORT_XLSX: + // eslint-disable-next-line no-unused-expressions + props.exportXLSX?.(props.slice.slice_id); + break; case MENU_KEYS.DOWNLOAD_AS_IMAGE: { // menu closes with a delay, we need to hide it manually, // so that we don't capture it on the screenshot @@ -493,6 +499,12 @@ const SliceHeaderControls = (props: SliceHeaderControlsPropsWithRouter) => { )} + } + > + {t('Export to Excel')} + } diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx index 0f151ff4f8d6a..b16bd63f188e1 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx @@ -29,6 +29,7 @@ import { LOG_ACTIONS_CHANGE_DASHBOARD_FILTER, LOG_ACTIONS_EXPLORE_DASHBOARD_CHART, LOG_ACTIONS_EXPORT_CSV_DASHBOARD_CHART, + LOG_ACTIONS_EXPORT_XLSX_DASHBOARD_CHART, LOG_ACTIONS_FORCE_REFRESH_CHART, } from 'src/logger/LogUtils'; import { areObjectsEqual } from 'src/reduxUtils'; @@ -139,6 +140,7 @@ class Chart extends React.Component { this.handleFilterMenuClose = this.handleFilterMenuClose.bind(this); this.exportCSV = this.exportCSV.bind(this); this.exportFullCSV = this.exportFullCSV.bind(this); + this.exportXLSX = this.exportXLSX.bind(this); this.forceRefresh = this.forceRefresh.bind(this); this.resize = this.resize.bind(this); this.setDescriptionRef = this.setDescriptionRef.bind(this); @@ -324,8 +326,24 @@ class Chart extends React.Component { } }; + exportFullCSV() { + this.exportCSV(true); + } + exportCSV(isFullCSV = false) { - this.props.logEvent(LOG_ACTIONS_EXPORT_CSV_DASHBOARD_CHART, { + this.exportTable('csv', isFullCSV); + } + + exportXLSX() { + this.exportTable('xlsx', false); + } + + exportTable(format, isFullCSV) { + const logAction = + format === 'csv' + ? LOG_ACTIONS_EXPORT_CSV_DASHBOARD_CHART + : LOG_ACTIONS_EXPORT_XLSX_DASHBOARD_CHART; + this.props.logEvent(logAction, { slice_id: this.props.slice.slice_id, is_cached: this.props.isCached, }); @@ -334,16 +352,12 @@ class Chart extends React.Component { ? { ...this.props.formData, row_limit: this.props.maxRows } : this.props.formData, resultType: 'full', - resultFormat: 'csv', + resultFormat: format, force: true, ownState: this.props.ownState, }); } - exportFullCSV() { - this.exportCSV(true); - } - forceRefresh() { this.props.logEvent(LOG_ACTIONS_FORCE_REFRESH_CHART, { slice_id: this.props.slice.slice_id, @@ -437,6 +451,7 @@ class Chart extends React.Component { logEvent={logEvent} onExploreChart={this.onExploreChart} exportCSV={this.exportCSV} + exportXLSX={this.exportXLSX} exportFullCSV={this.exportFullCSV} updateSliceName={updateSliceName} sliceName={sliceName} diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx index a3851a73b3e94..c892f7fff58de 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.test.jsx @@ -62,6 +62,7 @@ describe('Chart', () => { addDangerToast() {}, exportCSV() {}, exportFullCSV() {}, + exportXLSX() {}, componentId: 'test', dashboardId: 111, editMode: false, @@ -145,4 +146,20 @@ describe('Chart', () => { expect(stubbedExportCSV.lastCall.args[0].formData.row_limit).toEqual(666); exploreUtils.exportChart.restore(); }); + it('should call exportChart when exportXLSX is clicked', () => { + const stubbedExportXLSX = sinon + .stub(exploreUtils, 'exportChart') + .returns(() => {}); + const wrapper = setup(); + wrapper.instance().exportXLSX(props.slice.sliceId); + expect(stubbedExportXLSX.calledOnce).toBe(true); + expect(stubbedExportXLSX.lastCall.args[0]).toEqual( + expect.objectContaining({ + formData: expect.anything(), + resultType: 'full', + resultFormat: 'xlsx', + }), + ); + exploreUtils.exportChart.restore(); + }); }); diff --git a/superset-frontend/src/logger/LogUtils.ts b/superset-frontend/src/logger/LogUtils.ts index cf5580c7bdaa3..258b5dbb5eea9 100644 --- a/superset-frontend/src/logger/LogUtils.ts +++ b/superset-frontend/src/logger/LogUtils.ts @@ -34,6 +34,8 @@ export const LOG_ACTIONS_PERIODIC_RENDER_DASHBOARD = export const LOG_ACTIONS_EXPLORE_DASHBOARD_CHART = 'explore_dashboard_chart'; export const LOG_ACTIONS_EXPORT_CSV_DASHBOARD_CHART = 'export_csv_dashboard_chart'; +export const LOG_ACTIONS_EXPORT_XLSX_DASHBOARD_CHART = + 'export_csv_dashboard_chart'; export const LOG_ACTIONS_CHANGE_DASHBOARD_FILTER = 'change_dashboard_filter'; export const LOG_ACTIONS_DATASET_CREATION_EMPTY_CANCELLATION = 'dataset_creation_empty_cancellation';