diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json
index ea758d431..8dbd9a743 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -39161,12 +39161,13 @@
"dev": true
},
"query-string": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
- "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+ "version": "6.13.7",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.7.tgz",
+ "integrity": "sha512-CsGs8ZYb39zu0WLkeOhe0NMePqgYdAuCqxOYKDR5LVCytDZYMGx3Bb+xypvQvPHVPijRXB0HZNFllCzHRe4gEA==",
"requires": {
- "object-assign": "^4.1.0",
- "strict-uri-encode": "^1.0.0"
+ "decode-uri-component": "^0.2.0",
+ "split-on-first": "^1.0.0",
+ "strict-uri-encode": "^2.0.0"
}
},
"querystring": {
@@ -41196,6 +41197,22 @@
"is-retina": "^1.0.3",
"md5": "^2.1.0",
"query-string": "^4.2.2"
+ },
+ "dependencies": {
+ "query-string": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
+ "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
+ "requires": {
+ "object-assign": "^4.1.0",
+ "strict-uri-encode": "^1.0.0"
+ }
+ },
+ "strict-uri-encode": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
+ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
+ }
}
},
"react-helmet-async": {
@@ -43494,24 +43511,9 @@
}
},
"serialize-query-params": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/serialize-query-params/-/serialize-query-params-0.1.4.tgz",
- "integrity": "sha512-d3GHKPAOBULhCMg+jM687vRIMnTXMo8M0lHUOVeFxSGYvfmNlksiOpLyb0orhXPhhFCvZvt+SwC2iPRVIhKS/g==",
- "requires": {
- "query-string": "^5.0.0"
- },
- "dependencies": {
- "query-string": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
- "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
- "requires": {
- "decode-uri-component": "^0.2.0",
- "object-assign": "^4.1.0",
- "strict-uri-encode": "^1.0.0"
- }
- }
- }
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/serialize-query-params/-/serialize-query-params-1.2.4.tgz",
+ "integrity": "sha512-m4hGkOY5y+ksPDSEkw12cNxt3HRUJv5G6oF9/4yq+GCw4LznudxC73qnz++VTHqXa0j1x1/iaBIpoiMBxr6w2w=="
},
"serve-favicon": {
"version": "2.5.0",
@@ -44313,6 +44315,11 @@
"through": "2"
}
},
+ "split-on-first": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
+ "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
+ },
"split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@@ -44582,9 +44589,9 @@
"dev": true
},
"strict-uri-encode": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
- "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+ "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
},
"string-convert": {
"version": "0.2.1",
@@ -46753,11 +46760,11 @@
}
},
"use-query-params": {
- "version": "0.4.5",
- "resolved": "https://registry.npmjs.org/use-query-params/-/use-query-params-0.4.5.tgz",
- "integrity": "sha512-HeSgLvEj26pkNRGeAIq+uTo6Z22iaAqDMosq+Be5lab4v57gwVIUKsS3iZ1BBgsUbLEKKpoqcVvqd9MUg+lkIw==",
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/use-query-params/-/use-query-params-1.1.9.tgz",
+ "integrity": "sha512-WAJ1GrKbFWv1TBn1RQpHqAwC7yyJsLaJjBhIfefrbY/h6mFSngzBQKirJndYwCS1ry77EwhpR/tQi5iovXWvuw==",
"requires": {
- "serialize-query-params": "^0.1.4"
+ "serialize-query-params": "^1.2.3"
}
},
"use-sidecar": {
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index 80981728c..c004b940d 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -127,6 +127,7 @@
"omnibar": "^2.1.1",
"polished": "^3.6.5",
"prop-types": "^15.7.2",
+ "query-string": "^6.13.7",
"re-resizable": "^6.6.1",
"react": "^16.13.1",
"react-ace": "^5.10.0",
@@ -168,7 +169,7 @@
"rison": "^0.1.1",
"shortid": "^2.2.6",
"urijs": "^1.18.10",
- "use-query-params": "^0.4.5"
+ "use-query-params": "^1.1.9"
},
"devDependencies": {
"@babel/cli": "^7.11.5",
diff --git a/superset-frontend/spec/helpers/ProviderWrapper.tsx b/superset-frontend/spec/helpers/ProviderWrapper.tsx
new file mode 100644
index 000000000..f505a8114
--- /dev/null
+++ b/superset-frontend/spec/helpers/ProviderWrapper.tsx
@@ -0,0 +1,40 @@
+/**
+ * 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 { ThemeProvider } from '@superset-ui/core';
+import { BrowserRouter as Router, Route } from 'react-router-dom';
+import { QueryParamProvider } from 'use-query-params';
+
+export function ProviderWrapper(props: any) {
+ const { children, theme } = props;
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/superset-frontend/spec/helpers/theming.ts b/superset-frontend/spec/helpers/theming.ts
index dbb130a11..2ec019b70 100644
--- a/superset-frontend/spec/helpers/theming.ts
+++ b/superset-frontend/spec/helpers/theming.ts
@@ -17,8 +17,9 @@
* under the License.
*/
import { shallow as enzymeShallow, mount as enzymeMount } from 'enzyme';
-import { supersetTheme, ThemeProvider } from '@superset-ui/core';
+import { supersetTheme } from '@superset-ui/core';
import { ReactElement } from 'react';
+import { ProviderWrapper } from './ProviderWrapper';
type optionsType = {
wrappingComponentProps?: any;
@@ -32,7 +33,7 @@ export function styledMount(
) {
return enzymeMount(component, {
...options,
- wrappingComponent: ThemeProvider,
+ wrappingComponent: ProviderWrapper,
wrappingComponentProps: {
theme: supersetTheme,
...options?.wrappingComponentProps,
@@ -46,7 +47,7 @@ export function styledShallow(
) {
return enzymeShallow(component, {
...options,
- wrappingComponent: ThemeProvider,
+ wrappingComponent: ProviderWrapper,
wrappingComponentProps: {
theme: supersetTheme,
...options?.wrappingComponentProps,
diff --git a/superset-frontend/spec/javascripts/components/AlteredSliceTag_spec.jsx b/superset-frontend/spec/javascripts/components/AlteredSliceTag_spec.jsx
index 41b4fe97a..78efb7884 100644
--- a/superset-frontend/spec/javascripts/components/AlteredSliceTag_spec.jsx
+++ b/superset-frontend/spec/javascripts/components/AlteredSliceTag_spec.jsx
@@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
-import { shallow } from 'enzyme';
+import { styledMount as mount } from 'spec/helpers/theming';
import { getChartControlPanelRegistry } from '@superset-ui/core';
import AlteredSliceTag from 'src/components/AlteredSliceTag';
@@ -34,7 +34,7 @@ import {
} from './fixtures/AlteredSliceTag';
const getTableWrapperFromModalBody = modalBody =>
- modalBody.find(ListView).shallow().find(TableCollection).shallow();
+ modalBody.find(ListView).find(TableCollection);
describe('AlteredSliceTag', () => {
let wrapper;
@@ -47,7 +47,7 @@ describe('AlteredSliceTag', () => {
fakePluginControls,
);
props = { ...defaultProps };
- wrapper = shallow();
+ wrapper = mount();
({ controlsMap } = wrapper.instance().state);
});
@@ -63,7 +63,7 @@ describe('AlteredSliceTag', () => {
origFormData: props.origFormData,
currentFormData: props.origFormData,
};
- wrapper = shallow();
+ wrapper = mount();
expect(wrapper.instance().state.rows).toEqual([]);
expect(wrapper.instance().state.hasDiffs).toBe(false);
expect(wrapper.instance().render()).toBeNull();
@@ -78,7 +78,7 @@ describe('AlteredSliceTag', () => {
currentFormData: { ...props.currentFormData },
origFormData: { ...props.origFormData },
};
- wrapper = shallow();
+ wrapper = mount();
const wrapperInstance = wrapper.instance();
wrapperInstance.UNSAFE_componentWillReceiveProps(newProps);
expect(getRowsFromDiffsStub).toHaveBeenCalled();
@@ -98,7 +98,7 @@ describe('AlteredSliceTag', () => {
describe('renderTriggerNode', () => {
it('renders a TooltipWrapper', () => {
- const triggerNode = shallow(
+ const triggerNode = mount(
{wrapper.instance().renderTriggerNode()}
,
);
expect(triggerNode.find(TooltipWrapper)).toHaveLength(1);
@@ -107,14 +107,14 @@ describe('AlteredSliceTag', () => {
describe('renderModalBody', () => {
it('renders a Table', () => {
- const modalBody = shallow(
+ const modalBody = mount(
{wrapper.instance().renderModalBody()}
,
);
expect(modalBody.find(ListView)).toHaveLength(1);
});
it('renders a thead', () => {
- const modalBody = shallow(
+ const modalBody = mount(
{wrapper.instance().renderModalBody()}
,
);
expect(
@@ -123,7 +123,7 @@ describe('AlteredSliceTag', () => {
});
it('renders th', () => {
- const modalBody = shallow(
+ const modalBody = mount(
{wrapper.instance().renderModalBody()}
,
);
const th = getTableWrapperFromModalBody(modalBody).find('th');
@@ -134,7 +134,7 @@ describe('AlteredSliceTag', () => {
});
it('renders the correct number of Tr', () => {
- const modalBody = shallow(
+ const modalBody = mount(
{wrapper.instance().renderModalBody()}
,
);
const tr = getTableWrapperFromModalBody(modalBody).find('tr');
@@ -142,7 +142,7 @@ describe('AlteredSliceTag', () => {
});
it('renders the correct number of td', () => {
- const modalBody = shallow(
+ const modalBody = mount(
{wrapper.instance().renderModalBody()}
,
);
const td = getTableWrapperFromModalBody(modalBody).find('td');
@@ -155,12 +155,12 @@ describe('AlteredSliceTag', () => {
describe('renderRows', () => {
it('returns an array of rows with one tr and three td', () => {
- const modalBody = shallow(
+ const modalBody = mount(
{wrapper.instance().renderModalBody()}
,
);
const rows = getTableWrapperFromModalBody(modalBody).find('tr');
expect(rows).toHaveLength(8);
- const fakeRow = shallow({rows.get(1)}
);
+ const fakeRow = mount({rows.get(1)}
);
expect(fakeRow.find('tr')).toHaveLength(1);
expect(fakeRow.find('td')).toHaveLength(3);
});
diff --git a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
index f8bed6b13..1486eee4d 100644
--- a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
+++ b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
@@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
-import { mount, shallow } from 'enzyme';
+import { styledMount as mount } from 'spec/helpers/theming';
import { act } from 'react-dom/test-utils';
import { QueryParamProvider } from 'use-query-params';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
@@ -338,7 +338,7 @@ describe('ListView', () => {
filters: [...mockedProps.filters, { id: 'some_column' }],
};
expect(() => {
- shallow(, {
+ mount(, {
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: supersetTheme },
});
diff --git a/superset-frontend/src/components/ListView/Filters.tsx b/superset-frontend/src/components/ListView/Filters.tsx
index 3d610f2e1..2725c2130 100644
--- a/superset-frontend/src/components/ListView/Filters.tsx
+++ b/superset-frontend/src/components/ListView/Filters.tsx
@@ -87,8 +87,18 @@ function SelectFilter({
};
const options = [clearFilterSelect, ...selects];
+ let initialOption = clearFilterSelect;
- const [selectedOption, setSelectedOption] = useState(clearFilterSelect);
+ // Set initial value if not async
+ if (!fetchSelects) {
+ const matchingOption = options.find(x => x.value === initialValue);
+
+ if (matchingOption) {
+ initialOption = matchingOption;
+ }
+ }
+
+ const [selectedOption, setSelectedOption] = useState(initialOption);
const onChange = (selected: SelectOption | null) => {
if (selected === null) return;
onSelect(
diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx
index 635a08c2b..5c08395ce 100644
--- a/superset-frontend/src/components/ListView/ListView.tsx
+++ b/superset-frontend/src/components/ListView/ListView.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import { t, styled } from '@superset-ui/core';
-import React, { useEffect, useState } from 'react';
+import React, { useEffect } from 'react';
import { Alert } from 'react-bootstrap';
import { Empty } from 'src/common/components';
import { ReactComponent as EmptyImage } from 'images/empty.svg';
@@ -34,6 +34,7 @@ import {
Filters,
SortColumn,
CardSortSelectOption,
+ ViewModeType,
} from './types';
import { ListViewError, useListViewState } from './utils';
@@ -202,7 +203,6 @@ const ViewModeToggle = ({
);
};
-type ViewModeType = 'card' | 'table';
export interface ListViewProps {
columns: any[];
data: T[];
@@ -263,7 +263,8 @@ function ListView({
applyFilterValue,
selectedFlatRows,
toggleAllRowsSelected,
- state: { pageIndex, pageSize, internalFilters },
+ setViewMode,
+ state: { pageIndex, pageSize, internalFilters, viewMode },
} = useListViewState({
bulkSelectColumnConfig,
bulkSelectMode: bulkSelectEnabled && Boolean(bulkActions.length),
@@ -274,6 +275,8 @@ function ListView({
initialPageSize,
initialSort,
initialFilters: filters,
+ renderCard: Boolean(renderCard),
+ defaultViewMode,
});
const filterable = Boolean(filters.length);
if (filterable) {
@@ -291,9 +294,6 @@ function ListView({
}
const cardViewEnabled = Boolean(renderCard);
- const [viewingMode, setViewingMode] = useState(
- cardViewEnabled ? defaultViewMode : 'table',
- );
useEffect(() => {
// discard selections if bulk select is disabled
@@ -306,7 +306,7 @@ function ListView({
{cardViewEnabled && (
-
+
)}
{filterable && (
({
)}
- {viewingMode === 'card' && cardSortSelectOptions && (
+ {viewMode === 'card' && cardSortSelectOptions && (
({
)}
)}
- {viewingMode === 'card' && (
+ {viewMode === 'card' && (
({
loading={loading}
/>
)}
- {viewingMode === 'table' && (
+ {viewMode === 'table' && (
({
/>
)}
{!loading && rows.length === 0 && (
-
+
}
description={emptyState.message || 'No Data'}
diff --git a/superset-frontend/src/components/ListView/types.ts b/superset-frontend/src/components/ListView/types.ts
index 82dcc519f..7038bd9f7 100644
--- a/superset-frontend/src/components/ListView/types.ts
+++ b/superset-frontend/src/components/ListView/types.ts
@@ -71,6 +71,8 @@ export interface Filter {
export type Filters = Filter[];
+export type ViewModeType = 'card' | 'table';
+
export interface FilterValue {
id: string;
operator?: string;
diff --git a/superset-frontend/src/components/ListView/utils.ts b/superset-frontend/src/components/ListView/utils.ts
index 727343764..a3f6426ed 100644
--- a/superset-frontend/src/components/ListView/utils.ts
+++ b/superset-frontend/src/components/ListView/utils.ts
@@ -26,13 +26,9 @@ import {
useTable,
} from 'react-table';
-import {
- JsonParam,
- NumberParam,
- StringParam,
- useQueryParams,
-} from 'use-query-params';
+import { NumberParam, StringParam, useQueryParams } from 'use-query-params';
+import rison from 'rison';
import { isEqual } from 'lodash';
import { PartialStylesConfig } from 'src/components/Select';
import {
@@ -41,8 +37,17 @@ import {
FilterValue,
InternalFilter,
SortColumn,
+ ViewModeType,
} from './types';
+// Define custom RisonParam for proper encoding/decoding
+const RisonParam = {
+ encode: (data: any | null | undefined) =>
+ data === undefined ? undefined : rison.encode(data),
+ decode: (dataStr: string | undefined) =>
+ dataStr === undefined ? undefined : rison.decode(dataStr),
+};
+
export class ListViewError extends Error {
name = 'ListViewError';
}
@@ -63,11 +68,11 @@ function updateInList(list: any[], index: number, update: any): any[] {
];
}
-function mergeCreateFilterValues(list: Filter[], updateList: FilterValue[]) {
+function mergeCreateFilterValues(list: Filter[], updateObj: any) {
return list.map(({ id, operator }) => {
- const update = updateList.find(obj => obj.id === id);
+ const update = updateObj[id];
- return { id, operator, value: update?.value };
+ return { id, operator, value: update };
});
}
@@ -78,6 +83,37 @@ export function convertFilters(fts: InternalFilter[]): FilterValue[] {
.map(({ value, operator, id }) => ({ value, operator, id }));
}
+// convertFilters but to handle new decoded rison format
+export function convertFiltersRison(
+ filterObj: any,
+ list: Filter[],
+): FilterValue[] {
+ const filters: FilterValue[] = [];
+ const refs = {};
+
+ Object.keys(filterObj).forEach(id => {
+ const filter: FilterValue = {
+ id,
+ value: filterObj[id],
+ // operator: filterObj[id][1], // TODO: can probably get rid of this
+ };
+
+ refs[id] = filter;
+ filters.push(filter);
+ });
+
+ // Add operators from filter list
+ list.forEach(value => {
+ const filter = refs[value.id];
+
+ if (filter) {
+ filter.operator = value.operator;
+ }
+ });
+
+ return filters;
+}
+
export function extractInputValue(inputType: Filter['input'], event: any) {
if (!inputType || inputType === 'text') {
return event.currentTarget.value;
@@ -110,6 +146,8 @@ interface UseListViewConfig {
Header: (conf: any) => React.ReactNode;
Cell: (conf: any) => React.ReactNode;
};
+ renderCard?: boolean;
+ defaultViewMode?: ViewModeType;
}
export function useListViewState({
@@ -122,12 +160,15 @@ export function useListViewState({
initialSort = [],
bulkSelectMode = false,
bulkSelectColumnConfig,
+ renderCard = false,
+ defaultViewMode = 'card',
}: UseListViewConfig) {
const [query, setQuery] = useQueryParams({
- filters: JsonParam,
+ filters: RisonParam,
pageIndex: NumberParam,
sortColumn: StringParam,
sortOrder: StringParam,
+ viewMode: StringParam,
});
const initialSortBy = useMemo(
@@ -139,12 +180,19 @@ export function useListViewState({
);
const initialState = {
- filters: convertFilters(query.filters || []),
+ filters: query.filters
+ ? convertFiltersRison(query.filters, initialFilters)
+ : [],
pageIndex: query.pageIndex || 0,
pageSize: initialPageSize,
sortBy: initialSortBy,
};
+ const [viewMode, setViewMode] = useState(
+ (query.viewMode as ViewModeType) ||
+ (renderCard ? defaultViewMode : 'table'),
+ );
+
const columnsWithSelect = useMemo(() => {
// add exact filter type so filters with falsey values are not filtered out
const columnsWithFilter = columns.map(f => ({ ...f, filter: 'exact' }));
@@ -189,20 +237,37 @@ export function useListViewState({
);
const [internalFilters, setInternalFilters] = useState(
- query.filters || [],
+ query.filters && initialFilters.length
+ ? mergeCreateFilterValues(initialFilters, query.filters)
+ : [],
);
useEffect(() => {
if (initialFilters.length) {
setInternalFilters(
- mergeCreateFilterValues(initialFilters, query.filters || []),
+ mergeCreateFilterValues(
+ initialFilters,
+ query.filters ? query.filters : {},
+ ),
);
}
}, [initialFilters]);
useEffect(() => {
+ // From internalFilters, produce a simplified obj
+ const filterObj = {};
+
+ internalFilters.forEach(filter => {
+ if (
+ filter.value !== undefined &&
+ (typeof filter.value !== 'string' || filter.value.length > 0)
+ ) {
+ filterObj[filter.id] = filter.value;
+ }
+ });
+
const queryParams: any = {
- filters: internalFilters,
+ filters: Object.keys(filterObj).length ? filterObj : undefined,
pageIndex,
};
if (sortBy[0]) {
@@ -210,6 +275,10 @@ export function useListViewState({
queryParams.sortOrder = sortBy[0].desc ? 'desc' : 'asc';
}
+ if (renderCard) {
+ queryParams.viewMode = viewMode;
+ }
+
const method =
typeof query.pageIndex !== 'undefined' &&
queryParams.pageIndex !== query.pageIndex
@@ -218,7 +287,7 @@ export function useListViewState({
setQuery(queryParams, method);
fetchData({ pageIndex, pageSize, sortBy, filters });
- }, [fetchData, pageIndex, pageSize, sortBy, filters]);
+ }, [fetchData, pageIndex, pageSize, sortBy, filters, viewMode]);
useEffect(() => {
if (!isEqual(initialState.pageIndex, pageIndex)) {
@@ -256,9 +325,10 @@ export function useListViewState({
rows,
selectedFlatRows,
setAllFilters,
- state: { pageIndex, pageSize, sortBy, filters, internalFilters },
+ state: { pageIndex, pageSize, sortBy, filters, internalFilters, viewMode },
toggleAllRowsSelected,
applyFilterValue,
+ setViewMode,
};
}
diff --git a/superset-frontend/src/views/App.tsx b/superset-frontend/src/views/App.tsx
index b03496eff..60f9313e6 100644
--- a/superset-frontend/src/views/App.tsx
+++ b/superset-frontend/src/views/App.tsx
@@ -68,7 +68,10 @@ const App = () => (
-
+