mirror of
https://github.com/apache/superset.git
synced 2024-09-06 13:57:40 -04:00
refactor: introduce react-query on api resource hook (#21240)
This commit is contained in:
parent
1aeb8fd6b7
commit
65a11b6f45
96
superset-frontend/package-lock.json
generated
96
superset-frontend/package-lock.json
generated
@ -111,6 +111,7 @@
|
|||||||
"react-lines-ellipsis": "^0.15.0",
|
"react-lines-ellipsis": "^0.15.0",
|
||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
"react-markdown": "^4.3.1",
|
"react-markdown": "^4.3.1",
|
||||||
|
"react-query": "^3.39.2",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.0",
|
||||||
"react-resize-detector": "^6.7.6",
|
"react-resize-detector": "^6.7.6",
|
||||||
"react-reverse-portal": "^2.0.1",
|
"react-reverse-portal": "^2.0.1",
|
||||||
@ -37290,6 +37291,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/js-sha3": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
|
||||||
|
},
|
||||||
"node_modules/js-string-escape": {
|
"node_modules/js-string-escape": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
|
||||||
@ -45584,6 +45590,55 @@
|
|||||||
"react-dom": "^16.6.0 || ^17.0.0"
|
"react-dom": "^16.6.0 || ^17.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-query": {
|
||||||
|
"version": "3.39.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.2.tgz",
|
||||||
|
"integrity": "sha512-F6hYDKyNgDQfQOuR1Rsp3VRzJnWHx6aRnnIZHMNGGgbL3SBgpZTDg8MQwmxOgpCAoqZJA+JSNCydF1xGJqKOCA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"broadcast-channel": "^3.4.1",
|
||||||
|
"match-sorter": "^6.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-query/node_modules/broadcast-channel": {
|
||||||
|
"version": "3.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
|
||||||
|
"integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.7.2",
|
||||||
|
"detect-node": "^2.1.0",
|
||||||
|
"js-sha3": "0.8.0",
|
||||||
|
"microseconds": "0.2.0",
|
||||||
|
"nano-time": "1.0.0",
|
||||||
|
"oblivious-set": "1.0.0",
|
||||||
|
"rimraf": "3.0.2",
|
||||||
|
"unload": "2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-query/node_modules/unload": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.6.2",
|
||||||
|
"detect-node": "^2.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-redux": {
|
"node_modules/react-redux": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz",
|
||||||
@ -86719,6 +86774,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
|
||||||
"integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="
|
"integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="
|
||||||
},
|
},
|
||||||
|
"js-sha3": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
|
||||||
|
},
|
||||||
"js-string-escape": {
|
"js-string-escape": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz",
|
||||||
@ -93219,6 +93279,42 @@
|
|||||||
"react-popper": "^2.2.4"
|
"react-popper": "^2.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-query": {
|
||||||
|
"version": "3.39.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.2.tgz",
|
||||||
|
"integrity": "sha512-F6hYDKyNgDQfQOuR1Rsp3VRzJnWHx6aRnnIZHMNGGgbL3SBgpZTDg8MQwmxOgpCAoqZJA+JSNCydF1xGJqKOCA==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"broadcast-channel": "^3.4.1",
|
||||||
|
"match-sorter": "^6.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"broadcast-channel": {
|
||||||
|
"version": "3.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
|
||||||
|
"integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.7.2",
|
||||||
|
"detect-node": "^2.1.0",
|
||||||
|
"js-sha3": "0.8.0",
|
||||||
|
"microseconds": "0.2.0",
|
||||||
|
"nano-time": "1.0.0",
|
||||||
|
"oblivious-set": "1.0.0",
|
||||||
|
"rimraf": "3.0.2",
|
||||||
|
"unload": "2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unload": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.6.2",
|
||||||
|
"detect-node": "^2.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-redux": {
|
"react-redux": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz",
|
||||||
|
@ -175,6 +175,7 @@
|
|||||||
"react-lines-ellipsis": "^0.15.0",
|
"react-lines-ellipsis": "^0.15.0",
|
||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
"react-markdown": "^4.3.1",
|
"react-markdown": "^4.3.1",
|
||||||
|
"react-query": "^3.39.2",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.0",
|
||||||
"react-resize-detector": "^6.7.6",
|
"react-resize-detector": "^6.7.6",
|
||||||
"react-reverse-portal": "^2.0.1",
|
"react-reverse-portal": "^2.0.1",
|
||||||
|
@ -34,12 +34,14 @@ import { DndProvider } from 'react-dnd';
|
|||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import reducerIndex from 'spec/helpers/reducerIndex';
|
import reducerIndex from 'spec/helpers/reducerIndex';
|
||||||
import { QueryParamProvider } from 'use-query-params';
|
import { QueryParamProvider } from 'use-query-params';
|
||||||
|
import QueryProvider from 'src/views/QueryProvider';
|
||||||
|
|
||||||
type Options = Omit<RenderOptions, 'queries'> & {
|
type Options = Omit<RenderOptions, 'queries'> & {
|
||||||
useRedux?: boolean;
|
useRedux?: boolean;
|
||||||
useDnd?: boolean;
|
useDnd?: boolean;
|
||||||
useQueryParams?: boolean;
|
useQueryParams?: boolean;
|
||||||
useRouter?: boolean;
|
useRouter?: boolean;
|
||||||
|
useQuery?: boolean;
|
||||||
initialState?: {};
|
initialState?: {};
|
||||||
reducers?: {};
|
reducers?: {};
|
||||||
store?: Store;
|
store?: Store;
|
||||||
@ -50,6 +52,7 @@ function createWrapper(options?: Options) {
|
|||||||
useDnd,
|
useDnd,
|
||||||
useRedux,
|
useRedux,
|
||||||
useQueryParams,
|
useQueryParams,
|
||||||
|
useQuery = true,
|
||||||
useRouter,
|
useRouter,
|
||||||
initialState,
|
initialState,
|
||||||
reducers,
|
reducers,
|
||||||
@ -85,6 +88,10 @@ function createWrapper(options?: Options) {
|
|||||||
result = <BrowserRouter>{result}</BrowserRouter>;
|
result = <BrowserRouter>{result}</BrowserRouter>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (useQuery) {
|
||||||
|
result = <QueryProvider>{result}</QueryProvider>;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import thunkMiddleware from 'redux-thunk';
|
|||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import { ThemeProvider } from '@superset-ui/core';
|
import { ThemeProvider } from '@superset-ui/core';
|
||||||
import { GlobalStyles } from 'src/GlobalStyles';
|
import { GlobalStyles } from 'src/GlobalStyles';
|
||||||
|
import QueryProvider from 'src/views/QueryProvider';
|
||||||
import {
|
import {
|
||||||
initFeatureFlags,
|
initFeatureFlags,
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
@ -134,12 +135,14 @@ if (sqlLabMenu) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Application = () => (
|
const Application = () => (
|
||||||
|
<QueryProvider>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<GlobalStyles />
|
<GlobalStyles />
|
||||||
<App />
|
<App />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
</QueryProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default hot(Application);
|
export default hot(Application);
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
import AceEditorWrapper from 'src/SqlLab/components/AceEditorWrapper';
|
import AceEditorWrapper from 'src/SqlLab/components/AceEditorWrapper';
|
||||||
import ConnectedSouthPane from 'src/SqlLab/components/SouthPane/state';
|
import ConnectedSouthPane from 'src/SqlLab/components/SouthPane/state';
|
||||||
import SqlEditor from 'src/SqlLab/components/SqlEditor';
|
import SqlEditor from 'src/SqlLab/components/SqlEditor';
|
||||||
|
import QueryProvider from 'src/views/QueryProvider';
|
||||||
import SqlEditorLeftBar from 'src/SqlLab/components/SqlEditorLeftBar';
|
import SqlEditorLeftBar from 'src/SqlLab/components/SqlEditorLeftBar';
|
||||||
import { AntdDropdown } from 'src/components';
|
import { AntdDropdown } from 'src/components';
|
||||||
import {
|
import {
|
||||||
@ -101,9 +102,11 @@ describe('SqlEditor', () => {
|
|||||||
|
|
||||||
const buildWrapper = (props = {}) =>
|
const buildWrapper = (props = {}) =>
|
||||||
mount(
|
mount(
|
||||||
|
<QueryProvider>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<SqlEditor {...mockedProps} {...props} />
|
<SqlEditor {...mockedProps} {...props} />
|
||||||
</Provider>,
|
</Provider>
|
||||||
|
</QueryProvider>,
|
||||||
{
|
{
|
||||||
wrappingComponent: ThemeProvider,
|
wrappingComponent: ThemeProvider,
|
||||||
wrappingComponentProps: { theme: supersetTheme },
|
wrappingComponentProps: { theme: supersetTheme },
|
||||||
|
@ -31,6 +31,7 @@ import TabbedSqlEditors from 'src/SqlLab/components/TabbedSqlEditors';
|
|||||||
import SqlEditor from 'src/SqlLab/components/SqlEditor';
|
import SqlEditor from 'src/SqlLab/components/SqlEditor';
|
||||||
import { table, initialState } from 'src/SqlLab/fixtures';
|
import { table, initialState } from 'src/SqlLab/fixtures';
|
||||||
import { newQueryTabName } from 'src/SqlLab/utils/newQueryTabName';
|
import { newQueryTabName } from 'src/SqlLab/utils/newQueryTabName';
|
||||||
|
import QueryProvider from 'src/views/QueryProvider';
|
||||||
|
|
||||||
fetchMock.get('glob:*/api/v1/database/*', {});
|
fetchMock.get('glob:*/api/v1/database/*', {});
|
||||||
fetchMock.get('glob:*/savedqueryviewapi/api/get/*', {});
|
fetchMock.get('glob:*/savedqueryviewapi/api/get/*', {});
|
||||||
@ -89,9 +90,11 @@ describe('TabbedSqlEditors', () => {
|
|||||||
const mountWithAct = async () =>
|
const mountWithAct = async () =>
|
||||||
act(async () => {
|
act(async () => {
|
||||||
mount(
|
mount(
|
||||||
|
<QueryProvider>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<TabbedSqlEditors {...mockedProps} />
|
<TabbedSqlEditors {...mockedProps} />
|
||||||
</Provider>,
|
</Provider>
|
||||||
|
</QueryProvider>,
|
||||||
{
|
{
|
||||||
wrappingComponent: ThemeProvider,
|
wrappingComponent: ThemeProvider,
|
||||||
wrappingComponentProps: { theme: supersetTheme },
|
wrappingComponentProps: { theme: supersetTheme },
|
||||||
|
@ -32,6 +32,7 @@ import { DatasourceModal } from 'src/components/Datasource';
|
|||||||
import DatasourceEditor from 'src/components/Datasource/DatasourceEditor';
|
import DatasourceEditor from 'src/components/Datasource/DatasourceEditor';
|
||||||
import * as featureFlags from 'src/featureFlags';
|
import * as featureFlags from 'src/featureFlags';
|
||||||
import mockDatasource from 'spec/fixtures/mockDatasource';
|
import mockDatasource from 'spec/fixtures/mockDatasource';
|
||||||
|
import QueryProvider from 'src/views/QueryProvider';
|
||||||
|
|
||||||
const mockStore = configureStore([thunk]);
|
const mockStore = configureStore([thunk]);
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
@ -53,9 +54,11 @@ const mockedProps = {
|
|||||||
|
|
||||||
async function mountAndWait(props = mockedProps) {
|
async function mountAndWait(props = mockedProps) {
|
||||||
const mounted = mount(
|
const mounted = mount(
|
||||||
|
<QueryProvider>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<DatasourceModal {...props} />
|
<DatasourceModal {...props} />
|
||||||
</Provider>,
|
</Provider>
|
||||||
|
</QueryProvider>,
|
||||||
{
|
{
|
||||||
wrappingComponent: ThemeProvider,
|
wrappingComponent: ThemeProvider,
|
||||||
wrappingComponentProps: { theme: supersetTheme },
|
wrappingComponentProps: { theme: supersetTheme },
|
||||||
|
@ -25,7 +25,7 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { SelectValue } from 'antd/lib/select';
|
import { SelectValue } from 'antd/lib/select';
|
||||||
|
|
||||||
import { styled, SupersetClient, t } from '@superset-ui/core';
|
import { styled, t } from '@superset-ui/core';
|
||||||
import { Select } from 'src/components';
|
import { Select } from 'src/components';
|
||||||
import { FormLabel } from 'src/components/Form';
|
import { FormLabel } from 'src/components/Form';
|
||||||
import Icons from 'src/components/Icons';
|
import Icons from 'src/components/Icons';
|
||||||
@ -37,6 +37,7 @@ import CertifiedBadge from 'src/components/CertifiedBadge';
|
|||||||
import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip';
|
import WarningIconWithTooltip from 'src/components/WarningIconWithTooltip';
|
||||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||||
import { SchemaOption } from 'src/SqlLab/types';
|
import { SchemaOption } from 'src/SqlLab/types';
|
||||||
|
import { useTables, Table } from 'src/hooks/apiResources';
|
||||||
|
|
||||||
const TableSelectorWrapper = styled.div`
|
const TableSelectorWrapper = styled.div`
|
||||||
${({ theme }) => `
|
${({ theme }) => `
|
||||||
@ -101,19 +102,6 @@ interface TableSelectorProps {
|
|||||||
tableSelectMode?: 'single' | 'multiple';
|
tableSelectMode?: 'single' | 'multiple';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Table {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
type: string;
|
|
||||||
extra?: {
|
|
||||||
certification?: {
|
|
||||||
certified_by: string;
|
|
||||||
details: string;
|
|
||||||
};
|
|
||||||
warning_markdown?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TableOption {
|
interface TableOption {
|
||||||
label: JSX.Element;
|
label: JSX.Element;
|
||||||
text: string;
|
text: string;
|
||||||
@ -147,6 +135,15 @@ const TableOption = ({ table }: { table: Table }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function renderSelectRow(select: ReactNode, refreshBtn: ReactNode) {
|
||||||
|
return (
|
||||||
|
<div className="section">
|
||||||
|
<span className="select">{select}</span>
|
||||||
|
<span className="refresh">{refreshBtn}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
||||||
database,
|
database,
|
||||||
emptyState,
|
emptyState,
|
||||||
@ -166,34 +163,50 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
|||||||
tableValue = undefined,
|
tableValue = undefined,
|
||||||
onTableSelectChange,
|
onTableSelectChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [currentDatabase, setCurrentDatabase] = useState<
|
const { addSuccessToast } = useToasts();
|
||||||
DatabaseObject | null | undefined
|
|
||||||
>(database);
|
|
||||||
const [currentSchema, setCurrentSchema] = useState<string | undefined>(
|
const [currentSchema, setCurrentSchema] = useState<string | undefined>(
|
||||||
schema,
|
schema,
|
||||||
);
|
);
|
||||||
const [tableOptions, setTableOptions] = useState<TableOption[]>([]);
|
|
||||||
const [tableSelectValue, setTableSelectValue] = useState<
|
const [tableSelectValue, setTableSelectValue] = useState<
|
||||||
SelectValue | undefined
|
SelectValue | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
const [refresh, setRefresh] = useState(0);
|
const {
|
||||||
const [previousRefresh, setPreviousRefresh] = useState(0);
|
data,
|
||||||
const [loadingTables, setLoadingTables] = useState(false);
|
isFetching: loadingTables,
|
||||||
const { addSuccessToast } = useToasts();
|
isFetched,
|
||||||
|
refetch,
|
||||||
|
} = useTables({
|
||||||
|
dbId: database?.id,
|
||||||
|
schema: currentSchema,
|
||||||
|
onSuccess: (data: { options: Table[] }) => {
|
||||||
|
onTablesLoad?.(data.options);
|
||||||
|
if (isFetched) {
|
||||||
|
addSuccessToast('List updated');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: () => handleError(t('There was an error loading the tables')),
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableOptions = useMemo<TableOption[]>(
|
||||||
|
() =>
|
||||||
|
data
|
||||||
|
? data.options.map(table => ({
|
||||||
|
value: table.value,
|
||||||
|
label: <TableOption table={table} />,
|
||||||
|
text: table.label,
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// reset selections
|
// reset selections
|
||||||
if (database === undefined) {
|
if (database === undefined) {
|
||||||
setCurrentDatabase(undefined);
|
|
||||||
setCurrentSchema(undefined);
|
setCurrentSchema(undefined);
|
||||||
setTableSelectValue(undefined);
|
setTableSelectValue(undefined);
|
||||||
}
|
}
|
||||||
}, [database, tableSelectMode]);
|
}, [database, tableSelectMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentDatabase(database);
|
|
||||||
}, [database]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tableSelectMode === 'single') {
|
if (tableSelectMode === 'single') {
|
||||||
setTableSelectValue(
|
setTableSelectValue(
|
||||||
@ -208,56 +221,6 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
|||||||
}
|
}
|
||||||
}, [tableOptions, tableValue, tableSelectMode]);
|
}, [tableOptions, tableValue, tableSelectMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentDatabase && currentSchema) {
|
|
||||||
setLoadingTables(true);
|
|
||||||
const encodedSchema = encodeURIComponent(currentSchema);
|
|
||||||
const forceRefresh = refresh !== previousRefresh;
|
|
||||||
// TODO: Would be nice to add pagination in a follow-up. Needs endpoint changes.
|
|
||||||
const endpoint = encodeURI(
|
|
||||||
`/superset/tables/${currentDatabase.id}/${encodedSchema}/undefined/${forceRefresh}/`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (previousRefresh !== refresh) {
|
|
||||||
setPreviousRefresh(refresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
SupersetClient.get({ endpoint })
|
|
||||||
.then(({ json }) => {
|
|
||||||
const options: TableOption[] = json.options.map((table: Table) => {
|
|
||||||
const option: TableOption = {
|
|
||||||
value: table.value,
|
|
||||||
label: <TableOption table={table} />,
|
|
||||||
text: table.label,
|
|
||||||
};
|
|
||||||
|
|
||||||
return option;
|
|
||||||
});
|
|
||||||
|
|
||||||
onTablesLoad?.(json.options);
|
|
||||||
setTableOptions(options);
|
|
||||||
setLoadingTables(false);
|
|
||||||
if (forceRefresh) addSuccessToast('List updated');
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setLoadingTables(false);
|
|
||||||
handleError(t('There was an error loading the tables'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// We are using the refresh state to re-trigger the query
|
|
||||||
// previousRefresh should be out of dependencies array
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [currentDatabase, currentSchema, onTablesLoad, setTableOptions, refresh]);
|
|
||||||
|
|
||||||
function renderSelectRow(select: ReactNode, refreshBtn: ReactNode) {
|
|
||||||
return (
|
|
||||||
<div className="section">
|
|
||||||
<span className="select">{select}</span>
|
|
||||||
<span className="refresh">{refreshBtn}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const internalTableChange = (
|
const internalTableChange = (
|
||||||
selectedOptions: TableOption | TableOption[] | undefined,
|
selectedOptions: TableOption | TableOption[] | undefined,
|
||||||
) => {
|
) => {
|
||||||
@ -274,7 +237,6 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const internalDbChange = (db: DatabaseObject) => {
|
const internalDbChange = (db: DatabaseObject) => {
|
||||||
setCurrentDatabase(db);
|
|
||||||
if (onDbChange) {
|
if (onDbChange) {
|
||||||
onDbChange(db);
|
onDbChange(db);
|
||||||
}
|
}
|
||||||
@ -286,14 +248,15 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
|||||||
onSchemaChange(schema);
|
onSchemaChange(schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
internalTableChange(undefined);
|
const value = tableSelectMode === 'single' ? undefined : [];
|
||||||
|
internalTableChange(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderDatabaseSelector() {
|
function renderDatabaseSelector() {
|
||||||
return (
|
return (
|
||||||
<DatabaseSelector
|
<DatabaseSelector
|
||||||
key={currentDatabase?.id}
|
key={database?.id}
|
||||||
db={currentDatabase}
|
db={database}
|
||||||
emptyState={emptyState}
|
emptyState={emptyState}
|
||||||
formMode={formMode}
|
formMode={formMode}
|
||||||
getDbList={getDbList}
|
getDbList={getDbList}
|
||||||
@ -353,7 +316,7 @@ const TableSelector: FunctionComponent<TableSelectorProps> = ({
|
|||||||
|
|
||||||
const refreshLabel = !formMode && !readOnly && (
|
const refreshLabel = !formMode && !readOnly && (
|
||||||
<RefreshLabel
|
<RefreshLabel
|
||||||
onClick={() => setRefresh(refresh + 1)}
|
onClick={() => refetch()}
|
||||||
tooltipContent={t('Force refresh table list')}
|
tooltipContent={t('Force refresh table list')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -28,3 +28,4 @@ export {
|
|||||||
// different files for different resource types.
|
// different files for different resource types.
|
||||||
export * from './charts';
|
export * from './charts';
|
||||||
export * from './dashboards';
|
export * from './dashboards';
|
||||||
|
export * from './tables';
|
||||||
|
221
superset-frontend/src/hooks/apiResources/tables.test.ts
Normal file
221
superset-frontend/src/hooks/apiResources/tables.test.ts
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
/**
|
||||||
|
* 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 { act, renderHook } from '@testing-library/react-hooks';
|
||||||
|
import { SupersetClient } from '@superset-ui/core';
|
||||||
|
import QueryProvider, { queryClient } from 'src/views/QueryProvider';
|
||||||
|
import { useTables } from './tables';
|
||||||
|
|
||||||
|
const fakeApiResult = {
|
||||||
|
json: {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'fake api result1',
|
||||||
|
label: 'fake api label1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'fake api result2',
|
||||||
|
label: 'fake api label2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tableLength: 2,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fakeHasMoreApiResult = {
|
||||||
|
json: {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'fake api result1',
|
||||||
|
label: 'fake api label1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'fake api result2',
|
||||||
|
label: 'fake api label2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tableLength: 4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedData = {
|
||||||
|
...fakeApiResult.json,
|
||||||
|
hasMore: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedHasMoreData = {
|
||||||
|
...fakeHasMoreApiResult.json,
|
||||||
|
hasMore: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('@superset-ui/core', () => ({
|
||||||
|
SupersetClient: {
|
||||||
|
get: jest.fn().mockResolvedValue(fakeApiResult),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('useTables hook', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(SupersetClient.get as jest.Mock).mockClear();
|
||||||
|
queryClient.clear();
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns api response mapping json options', async () => {
|
||||||
|
const expectDbId = 'db1';
|
||||||
|
const expectedSchema = 'schemaA';
|
||||||
|
const forceRefresh = false;
|
||||||
|
const { result } = renderHook(
|
||||||
|
() =>
|
||||||
|
useTables({
|
||||||
|
dbId: expectDbId,
|
||||||
|
schema: expectedSchema,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
wrapper: QueryProvider,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledTimes(1);
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledWith({
|
||||||
|
endpoint: `/superset/tables/${expectDbId}/${expectedSchema}/undefined/${forceRefresh}/`,
|
||||||
|
});
|
||||||
|
expect(result.current.data).toEqual(expectedData);
|
||||||
|
await act(async () => {
|
||||||
|
result.current.refetch();
|
||||||
|
});
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledTimes(2);
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledWith({
|
||||||
|
endpoint: `/superset/tables/${expectDbId}/${expectedSchema}/undefined/true/`,
|
||||||
|
});
|
||||||
|
expect(result.current.data).toEqual(expectedData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns api response for search keyword', async () => {
|
||||||
|
const expectDbId = 'db1';
|
||||||
|
const expectedSchema = 'schemaA';
|
||||||
|
const expectedKeyword = 'my work';
|
||||||
|
const forceRefresh = false;
|
||||||
|
renderHook(
|
||||||
|
() =>
|
||||||
|
useTables({
|
||||||
|
dbId: expectDbId,
|
||||||
|
schema: expectedSchema,
|
||||||
|
keyword: expectedKeyword,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
wrapper: QueryProvider,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledTimes(1);
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledWith({
|
||||||
|
endpoint: `/superset/tables/${expectDbId}/${expectedSchema}/${encodeURIComponent(
|
||||||
|
expectedKeyword,
|
||||||
|
)}/${forceRefresh}/`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns hasMore when total is larger than result size', async () => {
|
||||||
|
(SupersetClient.get as jest.Mock).mockResolvedValueOnce(
|
||||||
|
fakeHasMoreApiResult,
|
||||||
|
);
|
||||||
|
const expectDbId = 'db1';
|
||||||
|
const expectedSchema = 'schemaA';
|
||||||
|
const { result } = renderHook(
|
||||||
|
() =>
|
||||||
|
useTables({
|
||||||
|
dbId: expectDbId,
|
||||||
|
schema: expectedSchema,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
wrapper: QueryProvider,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledTimes(1);
|
||||||
|
expect(result.current.data).toEqual(expectedHasMoreData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns cached data without api request', async () => {
|
||||||
|
const expectDbId = 'db1';
|
||||||
|
const expectedSchema = 'schemaA';
|
||||||
|
const { result, rerender } = renderHook(
|
||||||
|
() =>
|
||||||
|
useTables({
|
||||||
|
dbId: expectDbId,
|
||||||
|
schema: expectedSchema,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
wrapper: QueryProvider,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledTimes(1);
|
||||||
|
rerender();
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledTimes(1);
|
||||||
|
expect(result.current.data).toEqual(expectedData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns refreshed data after expires', async () => {
|
||||||
|
const expectDbId = 'db1';
|
||||||
|
const expectedSchema = 'schemaA';
|
||||||
|
const { result, rerender } = renderHook(
|
||||||
|
() =>
|
||||||
|
useTables({
|
||||||
|
dbId: expectDbId,
|
||||||
|
schema: expectedSchema,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
wrapper: QueryProvider,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledTimes(1);
|
||||||
|
rerender();
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledTimes(1);
|
||||||
|
queryClient.clear();
|
||||||
|
rerender();
|
||||||
|
await act(async () => {
|
||||||
|
jest.runAllTimers();
|
||||||
|
});
|
||||||
|
expect(SupersetClient.get).toHaveBeenCalledTimes(2);
|
||||||
|
expect(result.current.data).toEqual(expectedData);
|
||||||
|
});
|
||||||
|
});
|
97
superset-frontend/src/hooks/apiResources/tables.ts
Normal file
97
superset-frontend/src/hooks/apiResources/tables.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* 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 { useRef } from 'react';
|
||||||
|
import { useQuery, UseQueryOptions } from 'react-query';
|
||||||
|
import { SupersetClient } from '@superset-ui/core';
|
||||||
|
|
||||||
|
export type FetchTablesQueryParams = {
|
||||||
|
dbId?: string | number;
|
||||||
|
schema?: string;
|
||||||
|
forceRefresh?: boolean;
|
||||||
|
keyword?: string;
|
||||||
|
};
|
||||||
|
export interface Table {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
type: string;
|
||||||
|
extra?: {
|
||||||
|
certification?: {
|
||||||
|
certified_by: string;
|
||||||
|
details: string;
|
||||||
|
};
|
||||||
|
warning_markdown?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryData = {
|
||||||
|
json: { options: Table[]; tableLength: number };
|
||||||
|
response: Response;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Data = QueryData['json'] & {
|
||||||
|
hasMore: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function fetchTables({
|
||||||
|
dbId,
|
||||||
|
schema,
|
||||||
|
forceRefresh,
|
||||||
|
keyword,
|
||||||
|
}: FetchTablesQueryParams) {
|
||||||
|
const encodedSchema = schema ? encodeURIComponent(schema) : '';
|
||||||
|
const encodedKeyword = keyword ? encodeURIComponent(keyword) : 'undefined';
|
||||||
|
// TODO: Would be nice to add pagination in a follow-up. Needs endpoint changes.
|
||||||
|
const endpoint = `/superset/tables/${
|
||||||
|
dbId ?? 'undefined'
|
||||||
|
}/${encodedSchema}/${encodedKeyword}/${forceRefresh}/`;
|
||||||
|
return SupersetClient.get({ endpoint }) as Promise<QueryData>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Params = FetchTablesQueryParams &
|
||||||
|
Pick<UseQueryOptions, 'onSuccess' | 'onError'>;
|
||||||
|
|
||||||
|
export function useTables(options: Params) {
|
||||||
|
const { dbId, schema, keyword, onSuccess, onError } = options || {};
|
||||||
|
const forceRefreshRef = useRef(false);
|
||||||
|
const params = { dbId, schema, keyword };
|
||||||
|
const result = useQuery<QueryData, Error, Data>(
|
||||||
|
['tables', { dbId, schema, keyword }],
|
||||||
|
() => fetchTables({ ...params, forceRefresh: forceRefreshRef.current }),
|
||||||
|
{
|
||||||
|
select: ({ json }) => ({
|
||||||
|
...json,
|
||||||
|
hasMore: json.tableLength > json.options.length,
|
||||||
|
}),
|
||||||
|
enabled: Boolean(dbId && schema),
|
||||||
|
onSuccess,
|
||||||
|
onError,
|
||||||
|
onSettled: () => {
|
||||||
|
forceRefreshRef.current = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
refetch: () => {
|
||||||
|
forceRefreshRef.current = true;
|
||||||
|
return result.refetch();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -36,6 +36,7 @@ import { routes, isFrontendRoute } from 'src/views/routes';
|
|||||||
import { Logger } from 'src/logger/LogUtils';
|
import { Logger } from 'src/logger/LogUtils';
|
||||||
import { RootContextProviders } from './RootContextProviders';
|
import { RootContextProviders } from './RootContextProviders';
|
||||||
import { ScrollToTop } from './ScrollToTop';
|
import { ScrollToTop } from './ScrollToTop';
|
||||||
|
import QueryProvider from './QueryProvider';
|
||||||
|
|
||||||
setupApp();
|
setupApp();
|
||||||
setupPlugins();
|
setupPlugins();
|
||||||
@ -60,6 +61,7 @@ const LocationPathnameLogger = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
|
<QueryProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
<LocationPathnameLogger />
|
<LocationPathnameLogger />
|
||||||
@ -80,6 +82,7 @@ const App = () => (
|
|||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
</RootContextProviders>
|
</RootContextProviders>
|
||||||
</Router>
|
</Router>
|
||||||
|
</QueryProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default hot(App);
|
export default hot(App);
|
||||||
|
43
superset-frontend/src/views/QueryProvider.tsx
Normal file
43
superset-frontend/src/views/QueryProvider.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* 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 { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
|
|
||||||
|
export const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
staleTime: Infinity,
|
||||||
|
retry: false,
|
||||||
|
retryOnMount: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Queryprovider: React.FC<Props> = ({ children }) => (
|
||||||
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Queryprovider;
|
Loading…
Reference in New Issue
Block a user