refactor: use rison for list view filters stateful urls (#11675)
This commit is contained in:
parent
91bcbc8350
commit
6019113bc6
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Router>
|
||||
<QueryParamProvider
|
||||
ReactRouterRoute={Route}
|
||||
stringifyOptions={{ encode: false }}
|
||||
>
|
||||
{children}
|
||||
</QueryParamProvider>
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(<AlteredSliceTag {...props} />);
|
||||
wrapper = mount(<AlteredSliceTag {...props} />);
|
||||
({ controlsMap } = wrapper.instance().state);
|
||||
});
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ describe('AlteredSliceTag', () => {
|
|||
origFormData: props.origFormData,
|
||||
currentFormData: props.origFormData,
|
||||
};
|
||||
wrapper = shallow(<AlteredSliceTag {...props} />);
|
||||
wrapper = mount(<AlteredSliceTag {...props} />);
|
||||
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(<AlteredSliceTag {...props} />);
|
||||
wrapper = mount(<AlteredSliceTag {...props} />);
|
||||
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(
|
||||
<div>{wrapper.instance().renderTriggerNode()}</div>,
|
||||
);
|
||||
expect(triggerNode.find(TooltipWrapper)).toHaveLength(1);
|
||||
|
|
@ -107,14 +107,14 @@ describe('AlteredSliceTag', () => {
|
|||
|
||||
describe('renderModalBody', () => {
|
||||
it('renders a Table', () => {
|
||||
const modalBody = shallow(
|
||||
const modalBody = mount(
|
||||
<div>{wrapper.instance().renderModalBody()}</div>,
|
||||
);
|
||||
expect(modalBody.find(ListView)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a thead', () => {
|
||||
const modalBody = shallow(
|
||||
const modalBody = mount(
|
||||
<div>{wrapper.instance().renderModalBody()}</div>,
|
||||
);
|
||||
expect(
|
||||
|
|
@ -123,7 +123,7 @@ describe('AlteredSliceTag', () => {
|
|||
});
|
||||
|
||||
it('renders th', () => {
|
||||
const modalBody = shallow(
|
||||
const modalBody = mount(
|
||||
<div>{wrapper.instance().renderModalBody()}</div>,
|
||||
);
|
||||
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(
|
||||
<div>{wrapper.instance().renderModalBody()}</div>,
|
||||
);
|
||||
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(
|
||||
<div>{wrapper.instance().renderModalBody()}</div>,
|
||||
);
|
||||
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(
|
||||
<div>{wrapper.instance().renderModalBody()}</div>,
|
||||
);
|
||||
const rows = getTableWrapperFromModalBody(modalBody).find('tr');
|
||||
expect(rows).toHaveLength(8);
|
||||
const fakeRow = shallow(<div>{rows.get(1)}</div>);
|
||||
const fakeRow = mount(<div>{rows.get(1)}</div>);
|
||||
expect(fakeRow.find('tr')).toHaveLength(1);
|
||||
expect(fakeRow.find('td')).toHaveLength(3);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(<ListView {...props} />, {
|
||||
mount(<ListView {...props} />, {
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<T extends object = any> {
|
||||
columns: any[];
|
||||
data: T[];
|
||||
|
|
@ -263,7 +263,8 @@ function ListView<T extends object = any>({
|
|||
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<T extends object = any>({
|
|||
initialPageSize,
|
||||
initialSort,
|
||||
initialFilters: filters,
|
||||
renderCard: Boolean(renderCard),
|
||||
defaultViewMode,
|
||||
});
|
||||
const filterable = Boolean(filters.length);
|
||||
if (filterable) {
|
||||
|
|
@ -291,9 +294,6 @@ function ListView<T extends object = any>({
|
|||
}
|
||||
|
||||
const cardViewEnabled = Boolean(renderCard);
|
||||
const [viewingMode, setViewingMode] = useState<ViewModeType>(
|
||||
cardViewEnabled ? defaultViewMode : 'table',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// discard selections if bulk select is disabled
|
||||
|
|
@ -306,7 +306,7 @@ function ListView<T extends object = any>({
|
|||
<div className="header">
|
||||
<div className="header-left">
|
||||
{cardViewEnabled && (
|
||||
<ViewModeToggle mode={viewingMode} setMode={setViewingMode} />
|
||||
<ViewModeToggle mode={viewMode} setMode={setViewMode} />
|
||||
)}
|
||||
{filterable && (
|
||||
<FilterControls
|
||||
|
|
@ -317,7 +317,7 @@ function ListView<T extends object = any>({
|
|||
)}
|
||||
</div>
|
||||
<div className="header-right">
|
||||
{viewingMode === 'card' && cardSortSelectOptions && (
|
||||
{viewMode === 'card' && cardSortSelectOptions && (
|
||||
<CardSortSelect
|
||||
initialSort={initialSort}
|
||||
onChange={fetchData}
|
||||
|
|
@ -367,7 +367,7 @@ function ListView<T extends object = any>({
|
|||
)}
|
||||
</BulkSelectWrapper>
|
||||
)}
|
||||
{viewingMode === 'card' && (
|
||||
{viewMode === 'card' && (
|
||||
<CardCollection
|
||||
bulkSelectEnabled={bulkSelectEnabled}
|
||||
prepareRow={prepareRow}
|
||||
|
|
@ -376,7 +376,7 @@ function ListView<T extends object = any>({
|
|||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
{viewingMode === 'table' && (
|
||||
{viewMode === 'table' && (
|
||||
<TableCollection
|
||||
getTableProps={getTableProps}
|
||||
getTableBodyProps={getTableBodyProps}
|
||||
|
|
@ -389,7 +389,7 @@ function ListView<T extends object = any>({
|
|||
/>
|
||||
)}
|
||||
{!loading && rows.length === 0 && (
|
||||
<EmptyWrapper className={viewingMode}>
|
||||
<EmptyWrapper className={viewMode}>
|
||||
<Empty
|
||||
image={<EmptyImage />}
|
||||
description={emptyState.message || 'No Data'}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ export interface Filter {
|
|||
|
||||
export type Filters = Filter[];
|
||||
|
||||
export type ViewModeType = 'card' | 'table';
|
||||
|
||||
export interface FilterValue {
|
||||
id: string;
|
||||
operator?: string;
|
||||
|
|
|
|||
|
|
@ -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<ViewModeType>(
|
||||
(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<InternalFilter[]>(
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,10 @@ const App = () => (
|
|||
<ThemeProvider theme={supersetTheme}>
|
||||
<FlashProvider common={common}>
|
||||
<Router>
|
||||
<QueryParamProvider ReactRouterRoute={Route}>
|
||||
<QueryParamProvider
|
||||
ReactRouterRoute={Route}
|
||||
stringifyOptions={{ encode: false }}
|
||||
>
|
||||
<Menu data={menu} />
|
||||
<Switch>
|
||||
<Route path="/superset/welcome/">
|
||||
|
|
|
|||
Loading…
Reference in New Issue