feat(dashboard): Let users download full CSV of a table (#15046)

* - Convert SliceHeader to TSX in progress
- Add menu option to download full CSV. Probably will change it

* Add Download Full CSV feature, and tests

* Added more tests, more TS fixes

* Added feature flag

* Update @superset-ui package versions

* Update @superset-ui packages versions

* use backend config instead of hardcoding number of rows

* Update tests

* front end test fix

* Lint fixes and test fixes
This commit is contained in:
Ajay M
2021-06-14 17:26:18 -04:00
committed by GitHub
parent 6ed0a3a9e0
commit 045fa1bc7a
17 changed files with 578 additions and 433 deletions

View File

@@ -36,6 +36,7 @@ import { sliceId } from 'spec/fixtures/mockChartQueries';
import dashboardInfo from 'spec/fixtures/mockDashboardInfo';
import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout';
import { sliceEntitiesForChart } from 'spec/fixtures/mockSliceEntities';
import { initialState } from 'spec/javascripts/sqllab/fixtures';
import { nativeFiltersInfo } from '../../fixtures/mockNativeFilters';
describe('ChartHolder', () => {
@@ -61,6 +62,7 @@ describe('ChartHolder', () => {
function setup(overrideProps) {
const mockStore = getMockStore({
...initialState,
sliceEntities: sliceEntitiesForChart,
});

View File

@@ -23,7 +23,7 @@ import sinon from 'sinon';
import Chart from 'src/dashboard/components/gridComponents/Chart';
import SliceHeader from 'src/dashboard/components/SliceHeader';
import ChartContainer from 'src/chart/ChartContainer';
import * as exploreUtils from 'src/explore/exploreUtils';
import { sliceEntitiesForChart as sliceEntities } from 'spec/fixtures/mockSliceEntities';
import mockDatasource from 'spec/fixtures/mockDatasource';
import chartQueries, {
@@ -38,6 +38,7 @@ describe('Chart', () => {
updateSliceName() {},
// from redux
maxRows: 666,
chart: chartQueries[queryId],
formData: chartQueries[queryId].formData,
datasource: mockDatasource[sliceEntities.slices[queryId].datasource],
@@ -59,6 +60,8 @@ describe('Chart', () => {
unsetFocusedFilterField() {},
addSuccessToast() {},
addDangerToast() {},
exportCSV() {},
exportFullCSV() {},
componentId: 'test',
dashboardId: 111,
editMode: false,
@@ -86,7 +89,6 @@ describe('Chart', () => {
it('should render a description if it has one and isExpanded=true', () => {
const wrapper = setup();
expect(wrapper.find('.slice_description')).not.toExist();
wrapper.setProps({ ...props, isExpanded: true });
expect(wrapper.find('.slice_description')).toExist();
});
@@ -104,4 +106,30 @@ describe('Chart', () => {
wrapper.instance().changeFilter();
expect(changeFilter.callCount).toBe(1);
});
it('should call exportChart when exportCSV is clicked', () => {
const stubbedExportCSV = sinon
.stub(exploreUtils, 'exportChart')
.returns(() => {});
const wrapper = setup();
wrapper.instance().exportCSV(props.slice.sliceId);
expect(stubbedExportCSV.calledOnce).toBe(true);
expect(stubbedExportCSV.lastCall.args[0]).toEqual(
expect.objectContaining({
formData: expect.anything(),
resultType: 'results',
resultFormat: 'csv',
}),
);
exploreUtils.exportChart.restore();
});
it('should call exportChart with row_limit props.maxRows when exportFullCSV is clicked', () => {
const stubbedExportCSV = sinon
.stub(exploreUtils, 'exportChart')
.returns(() => {});
const wrapper = setup();
wrapper.instance().exportFullCSV(props.slice.sliceId);
expect(stubbedExportCSV.calledOnce).toBe(true);
expect(stubbedExportCSV.lastCall.args[0].formData.row_limit).toEqual(666);
exploreUtils.exportChart.restore();
});
});

View File

@@ -34,8 +34,9 @@ import IconButton from 'src/dashboard/components/IconButton';
import ResizableContainer from 'src/dashboard/components/resizable/ResizableContainer';
import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
import { mockStore } from 'spec/fixtures/mockStore';
import { getMockStore } from 'spec/fixtures/mockStore';
import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout';
import { initialState } from 'spec/javascripts/sqllab/fixtures';
describe('Column', () => {
const columnWithoutChildren = {
@@ -65,6 +66,9 @@ describe('Column', () => {
function setup(overrideProps) {
// We have to wrap provide DragDropContext for the underlying DragDroppable
// otherwise we cannot assert on DragDroppable children
const mockStore = getMockStore({
...initialState,
});
const wrapper = mount(
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>

View File

@@ -34,8 +34,9 @@ import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
import { DASHBOARD_GRID_ID } from 'src/dashboard/util/constants';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import { mockStore } from 'spec/fixtures/mockStore';
import { getMockStore } from 'spec/fixtures/mockStore';
import { dashboardLayout as mockLayout } from 'spec/fixtures/mockDashboardLayout';
import { initialState } from 'spec/javascripts/sqllab/fixtures';
describe('Row', () => {
const rowWithoutChildren = { ...mockLayout.present.ROW_ID, children: [] };
@@ -61,6 +62,9 @@ describe('Row', () => {
function setup(overrideProps) {
// We have to wrap provide DragDropContext for the underlying DragDroppable
// otherwise we cannot assert on DragDroppable children
const mockStore = getMockStore({
...initialState,
});
const wrapper = mount(
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>

View File

@@ -31,7 +31,8 @@ import Tab, {
RENDER_TAB_CONTENT,
} from 'src/dashboard/components/gridComponents/Tab';
import { dashboardLayoutWithTabs } from 'spec/fixtures/mockDashboardLayout';
import { mockStoreWithTabs } from 'spec/fixtures/mockStore';
import { getMockStore } from 'spec/fixtures/mockStore';
import { initialState } from 'spec/javascripts/sqllab/fixtures';
describe('Tabs', () => {
const props = {
@@ -62,8 +63,13 @@ describe('Tabs', () => {
function setup(overrideProps) {
// We have to wrap provide DragDropContext for the underlying DragDroppable
// otherwise we cannot assert on DragDroppable children
const mockStore = getMockStore({
...initialState,
dashboardLayout: dashboardLayoutWithTabs,
dashboardFilters: {},
});
const wrapper = mount(
<Provider store={mockStoreWithTabs}>
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>
<Tab {...props} {...overrideProps} />
</DndProvider>

View File

@@ -35,8 +35,9 @@ import Tabs from 'src/dashboard/components/gridComponents/Tabs';
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
import emptyDashboardLayout from 'src/dashboard/fixtures/emptyDashboardLayout';
import { dashboardLayoutWithTabs } from 'spec/fixtures/mockDashboardLayout';
import { mockStoreWithTabs } from 'spec/fixtures/mockStore';
import { getMockStore } from 'spec/fixtures/mockStore';
import { nativeFilters } from 'spec/fixtures/mockNativeFilters';
import { initialState } from 'spec/javascripts/sqllab/fixtures';
describe('Tabs', () => {
fetchMock.post('glob:*/r/shortner/', {});
@@ -68,8 +69,13 @@ describe('Tabs', () => {
function setup(overrideProps) {
// We have to wrap provide DragDropContext for the underlying DragDroppable
// otherwise we cannot assert on DragDroppable children
const mockStore = getMockStore({
...initialState,
dashboardLayout: dashboardLayoutWithTabs,
dashboardFilters: {},
});
const wrapper = mount(
<Provider store={mockStoreWithTabs}>
<Provider store={mockStore}>
<DndProvider backend={HTML5Backend}>
<Tabs {...props} {...overrideProps} />
</DndProvider>