This is an automated email from the ASF dual-hosted git repository.
diegopucci pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new a782a62 chore: Select component refactoring - SelectAsyncControl -
Iteration 5 (#16609)
a782a62 is described below
commit a782a62097a1960ff6d8a198b89ce71ed5fe82ad
Author: Geido <[email protected]>
AuthorDate: Thu Oct 7 14:20:32 2021 +0300
chore: Select component refactoring - SelectAsyncControl - Iteration 5
(#16609)
* Refactor Select
* Implement onError
* Separate async request
* Lint
* Allow clear
* Improve type
* Reconcile with Select latest changes
* Fix handleOnChange
* Clean up
* Add placeholder
* Accept null values
* Update
superset-frontend/src/explore/components/controls/SelectAsyncControl/index.tsx
Co-authored-by: David Aaron Suddjian
<[email protected]>
* Clean up
* Implement type guard
* Fix lint
* Catch error within loadOptions
Co-authored-by: David Aaron Suddjian
<[email protected]>
---
superset-frontend/src/components/Select/Select.tsx | 5 +
.../SelectAsyncControl/SelectAsyncControl.test.tsx | 37 ++++---
.../controls/SelectAsyncControl/index.jsx | 92 ------------------
.../controls/SelectAsyncControl/index.tsx | 106 +++++++++++++++++++++
4 files changed, 127 insertions(+), 113 deletions(-)
diff --git a/superset-frontend/src/components/Select/Select.tsx
b/superset-frontend/src/components/Select/Select.tsx
index e705274..c5c53a6 100644
--- a/superset-frontend/src/components/Select/Select.tsx
+++ b/superset-frontend/src/components/Select/Select.tsx
@@ -93,6 +93,7 @@ export interface SelectProps extends PickedSelectProps {
pageSize?: number;
invertSelection?: boolean;
fetchOnlyOnSearch?: boolean;
+ onError?: (error: string) => void;
}
const StyledContainer = styled.div`
@@ -331,6 +332,10 @@ const Select = ({
getClientErrorObject(response).then(e => {
const { error } = e;
setError(error);
+
+ if (props.onError) {
+ props.onError(error);
+ }
});
const handleData = (data: OptionsType) => {
diff --git
a/superset-frontend/src/explore/components/controls/SelectAsyncControl/SelectAsyncControl.test.tsx
b/superset-frontend/src/explore/components/controls/SelectAsyncControl/SelectAsyncControl.test.tsx
index bc1ec82..0b0b303 100644
---
a/superset-frontend/src/explore/components/controls/SelectAsyncControl/SelectAsyncControl.test.tsx
+++
b/superset-frontend/src/explore/components/controls/SelectAsyncControl/SelectAsyncControl.test.tsx
@@ -17,19 +17,21 @@
* under the License.
*/
import userEvent from '@testing-library/user-event';
+import fetchMock from 'fetch-mock';
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import SelectAsyncControl from '.';
-jest.mock('src/components/AsyncSelect', () => ({
+const datasetsOwnersEndpoint = 'glob:*/api/v1/dataset/related/owners*';
+
+jest.mock('src/components/Select/Select', () => ({
__esModule: true,
default: (props: any) => (
<div
data-test="select-test"
- data-data-endpoint={props.dataEndpoint}
data-value={JSON.stringify(props.value)}
data-placeholder={props.placeholder}
- data-multi={props.multi ? 'true' : 'false'}
+ data-multi={props.mode}
>
<button
type="button"
@@ -40,19 +42,19 @@ jest.mock('src/components/AsyncSelect', () => ({
<button type="button" onClick={() => props.mutator()}>
mutator
</button>
-
- <div data-test="valueRenderer">
- {props.valueRenderer({ label: 'valueRenderer' })}
- </div>
</div>
),
}));
+fetchMock.get(datasetsOwnersEndpoint, {
+ result: [],
+});
+
const createProps = () => ({
+ ariaLabel: 'SelectAsyncControl',
value: [],
- dataEndpoint: 'api/v1/dataset/related/owners',
+ dataEndpoint: datasetsOwnersEndpoint,
multi: true,
- onAsyncErrorMessage: 'Error while fetching data',
placeholder: 'Select ...',
onChange: jest.fn(),
mutator: jest.fn(),
@@ -65,18 +67,14 @@ beforeEach(() => {
test('Should render', () => {
const props = createProps();
render(<SelectAsyncControl {...props} />, { useRedux: true });
- expect(screen.getByTestId('SelectAsyncControl')).toBeInTheDocument();
+ expect(screen.getByTestId('select-test')).toBeInTheDocument();
});
-test('Should send correct props to AsyncSelect component - value props', () =>
{
+test('Should send correct props to Select component - value props', () => {
const props = createProps();
render(<SelectAsyncControl {...props} />, { useRedux: true });
expect(screen.getByTestId('select-test')).toHaveAttribute(
- 'data-data-endpoint',
- props.dataEndpoint,
- );
- expect(screen.getByTestId('select-test')).toHaveAttribute(
'data-value',
JSON.stringify(props.value),
);
@@ -86,14 +84,11 @@ test('Should send correct props to AsyncSelect component -
value props', () => {
);
expect(screen.getByTestId('select-test')).toHaveAttribute(
'data-multi',
- 'true',
- );
- expect(screen.getByTestId('valueRenderer')).toHaveTextContent(
- 'valueRenderer',
+ 'multiple',
);
});
-test('Should send correct props to AsyncSelect component - function onChange
multi:true', () => {
+test('Should send correct props to Select component - function onChange
multi:true', () => {
const props = createProps();
render(<SelectAsyncControl {...props} />, { useRedux: true });
expect(props.onChange).toBeCalledTimes(0);
@@ -101,7 +96,7 @@ test('Should send correct props to AsyncSelect component -
function onChange mul
expect(props.onChange).toBeCalledTimes(1);
});
-test('Should send correct props to AsyncSelect component - function onChange
multi:false', () => {
+test('Should send correct props to Select component - function onChange
multi:false', () => {
const props = createProps();
render(<SelectAsyncControl {...{ ...props, multi: false }} />, {
useRedux: true,
diff --git
a/superset-frontend/src/explore/components/controls/SelectAsyncControl/index.jsx
b/superset-frontend/src/explore/components/controls/SelectAsyncControl/index.jsx
deleted file mode 100644
index db70755..0000000
---
a/superset-frontend/src/explore/components/controls/SelectAsyncControl/index.jsx
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * 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 PropTypes from 'prop-types';
-import { t } from '@superset-ui/core';
-
-import Select from 'src/components/AsyncSelect';
-import ControlHeader from 'src/explore/components/ControlHeader';
-import withToasts from 'src/components/MessageToasts/withToasts';
-
-const propTypes = {
- dataEndpoint: PropTypes.string.isRequired,
- multi: PropTypes.bool,
- mutator: PropTypes.func,
- onAsyncErrorMessage: PropTypes.string,
- onChange: PropTypes.func,
- placeholder: PropTypes.string,
- value: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number,
- PropTypes.arrayOf(PropTypes.string),
- PropTypes.arrayOf(PropTypes.number),
- ]),
- addDangerToast: PropTypes.func.isRequired,
-};
-
-const defaultProps = {
- multi: true,
- onAsyncErrorMessage: t('Error while fetching data'),
- onChange: () => {},
- placeholder: t('Select ...'),
-};
-
-const SelectAsyncControl = props => {
- const {
- value,
- onChange,
- dataEndpoint,
- multi,
- mutator,
- placeholder,
- onAsyncErrorMessage,
- } = props;
- const onSelectionChange = options => {
- let val;
- if (multi) {
- val = options?.map(option => option.value) ?? null;
- } else {
- val = options?.value ?? null;
- }
- onChange(val);
- };
-
- return (
- <div data-test="SelectAsyncControl">
- <ControlHeader {...props} />
- <Select
- dataEndpoint={dataEndpoint}
- onChange={onSelectionChange}
- onAsyncError={errorMsg =>
- props.addDangerToast(`${onAsyncErrorMessage}: ${errorMsg}`)
- }
- mutator={mutator}
- multi={multi}
- value={value}
- placeholder={placeholder}
- valueRenderer={v => <div>{v.label}</div>}
- />
- </div>
- );
-};
-
-SelectAsyncControl.propTypes = propTypes;
-SelectAsyncControl.defaultProps = defaultProps;
-
-export default withToasts(SelectAsyncControl);
diff --git
a/superset-frontend/src/explore/components/controls/SelectAsyncControl/index.tsx
b/superset-frontend/src/explore/components/controls/SelectAsyncControl/index.tsx
new file mode 100644
index 0000000..85230f7
--- /dev/null
+++
b/superset-frontend/src/explore/components/controls/SelectAsyncControl/index.tsx
@@ -0,0 +1,106 @@
+/**
+ * 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, { useEffect, useState } from 'react';
+import { t, SupersetClient } from '@superset-ui/core';
+import ControlHeader from 'src/explore/components/ControlHeader';
+import { Select } from 'src/components';
+import { SelectProps, OptionsType } from 'src/components/Select/Select';
+import { SelectValue, LabeledValue } from 'antd/lib/select';
+import withToasts from 'src/components/MessageToasts/withToasts';
+import { getClientErrorObject } from 'src/utils/getClientErrorObject';
+
+type SelectAsyncProps = Omit<SelectProps, 'options' | 'ariaLabel' |
'onChange'>;
+
+interface SelectAsyncControlProps extends SelectAsyncProps {
+ addDangerToast: (error: string) => void;
+ ariaLabel?: string;
+ dataEndpoint: string;
+ default?: SelectValue;
+ mutator?: (response: Record<string, any>) => OptionsType;
+ multi?: boolean;
+ onChange: (val: SelectValue) => void;
+ // ControlHeader related props
+ description?: string;
+ hovered?: boolean;
+ label?: string;
+}
+
+function isLabeledValue(arg: any): arg is LabeledValue {
+ return arg.value !== undefined;
+}
+
+const SelectAsyncControl = ({
+ addDangerToast,
+ allowClear = true,
+ ariaLabel,
+ dataEndpoint,
+ multi = true,
+ mutator,
+ onChange,
+ placeholder,
+ value,
+ ...props
+}: SelectAsyncControlProps) => {
+ const [options, setOptions] = useState<OptionsType>([]);
+
+ const handleOnChange = (val: SelectValue) => {
+ let onChangeVal = val;
+ if (Array.isArray(val)) {
+ const values = val.map(v => (isLabeledValue(v) ? v.value : v));
+ onChangeVal = values;
+ }
+ if (isLabeledValue(val)) {
+ onChangeVal = val.value;
+ }
+ onChange(onChangeVal);
+ };
+
+ useEffect(() => {
+ const onError = (response: Response) =>
+ getClientErrorObject(response).then(e => {
+ const { error } = e;
+ addDangerToast(`${t('Error while fetching data')}: ${error}`);
+ });
+ const loadOptions = () =>
+ SupersetClient.get({
+ endpoint: dataEndpoint,
+ })
+ .then(response => {
+ const data = mutator ? mutator(response.json) : response.json.result;
+ setOptions(data);
+ })
+ .catch(onError);
+ loadOptions();
+ }, [addDangerToast, dataEndpoint, mutator]);
+
+ return (
+ <Select
+ allowClear={allowClear}
+ ariaLabel={ariaLabel || t('Select ...')}
+ value={value || (props.default !== undefined ? props.default :
undefined)}
+ header={<ControlHeader {...props} />}
+ mode={multi ? 'multiple' : 'single'}
+ onChange={handleOnChange}
+ options={options}
+ placeholder={placeholder}
+ />
+ );
+};
+
+export default withToasts(SelectAsyncControl);