You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by en...@apache.org on 2025/03/03 21:13:46 UTC
(superset) branch enxdev/refactor/antd5/create-wrappers created (now 309ae32ab1)
This is an automated email from the ASF dual-hosted git repository.
enzomartellucci pushed a change to branch enxdev/refactor/antd5/create-wrappers
in repository https://gitbox.apache.org/repos/asf/superset.git
at 309ae32ab1 refactor(AutoComplete): add wrapper, tests, and story for Ant Design 5 AutoComplete component
This branch includes the following new commits:
new 309ae32ab1 refactor(AutoComplete): add wrapper, tests, and story for Ant Design 5 AutoComplete component
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(superset) 01/01: refactor(AutoComplete): add wrapper, tests, and story for Ant Design 5 AutoComplete component
Posted by en...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
enzomartellucci pushed a commit to branch enxdev/refactor/antd5/create-wrappers
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 309ae32ab11d94646d04b1164e5d60aeaf986d93
Author: Enzo Martellucci <en...@gmail.com>
AuthorDate: Mon Mar 3 22:13:24 2025 +0100
refactor(AutoComplete): add wrapper, tests, and story for Ant Design 5 AutoComplete component
---
.../src/SqlLab/components/SaveQuery/index.tsx | 4 +-
.../components/ScheduleQueryButton/index.tsx | 4 +-
.../AutoComplete/AutoComplete.stories.tsx | 95 ++++++++++++++++++++++
.../components/AutoComplete/AutoComplete.test.tsx | 87 ++++++++++++++++++++
.../components/{Input => AutoComplete}/index.tsx | 11 ++-
.../src/components/Input/Input.stories.tsx | 4 +-
.../src/components/Input/Input.test.tsx | 4 +-
superset-frontend/src/components/Input/index.tsx | 11 ++-
superset-frontend/src/components/index.ts | 1 -
.../components/menu/BackgroundStyleDropdown.tsx | 6 +-
.../FiltersConfigModal/DividerConfigForm.tsx | 4 +-
.../FiltersConfigForm/FiltersConfigForm.tsx | 4 +-
.../src/explore/components/EmbedCodeContent.jsx | 4 +-
.../explore/components/PropertiesModal/index.tsx | 4 +-
.../databases/DatabaseModal/SSHTunnelForm.tsx | 4 +-
.../src/features/rls/RowLevelSecurityModal.tsx | 4 +-
16 files changed, 219 insertions(+), 32 deletions(-)
diff --git a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
index dbebf6d9b4..b5793f11d3 100644
--- a/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
+++ b/superset-frontend/src/SqlLab/components/SaveQuery/index.tsx
@@ -20,7 +20,7 @@ import { useState, useEffect, useMemo, ChangeEvent } from 'react';
import type { DatabaseObject } from 'src/features/databases/types';
import { Row, Col } from 'src/components';
-import { Input, TextArea } from 'src/components/Input';
+import { Input } from 'src/components/Input';
import { t, styled } from '@superset-ui/core';
import Button from 'src/components/Button';
import { Menu } from 'src/components/Menu';
@@ -172,7 +172,7 @@ const SaveQuery = ({
<Row>
<Col xs={24}>
<FormItem label={t('Description')}>
- <TextArea
+ <Input.TextArea
rows={4}
value={description}
onChange={onDescriptionChange}
diff --git a/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx b/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx
index b7faf5d714..0a68ac3ce6 100644
--- a/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx
+++ b/superset-frontend/src/SqlLab/components/ScheduleQueryButton/index.tsx
@@ -22,7 +22,7 @@ import SchemaForm, { FormProps } from '@rjsf/core';
import { FormValidation } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
import { Row, Col } from 'src/components';
-import { Input, TextArea } from 'src/components/Input';
+import { Input } from 'src/components/Input';
import { t, styled } from '@superset-ui/core';
import { parseDate } from 'chrono-node';
import ModalTrigger, { ModalTriggerRef } from 'src/components/ModalTrigger';
@@ -185,7 +185,7 @@ const ScheduleQueryButton: FunctionComponent<ScheduleQueryButtonProps> = ({
<StyledRow>
<Col xs={24}>
<FormItem label={t('Description')}>
- <TextArea
+ <Input.TextArea
rows={4}
placeholder={t('Write a description for your query')}
value={description}
diff --git a/superset-frontend/src/components/AutoComplete/AutoComplete.stories.tsx b/superset-frontend/src/components/AutoComplete/AutoComplete.stories.tsx
new file mode 100644
index 0000000000..2bf9727fff
--- /dev/null
+++ b/superset-frontend/src/components/AutoComplete/AutoComplete.stories.tsx
@@ -0,0 +1,95 @@
+import { useState } from 'react';
+import { Meta, StoryFn } from '@storybook/react';
+import AutoComplete, { AntAutoCompleteProps } from './index';
+import { Input } from '../Input';
+
+export default {
+ title: 'Components/AutoComplete',
+ component: AutoComplete,
+} as Meta;
+
+const getRandomInt = (max: number, min = 0) =>
+ Math.floor(Math.random() * (max - min + 1)) + min;
+
+const searchResult = (query: string) =>
+ Array.from({ length: getRandomInt(5) }).map((_, idx) => {
+ const category = `${query}${idx}`;
+ return {
+ value: category,
+ label: (
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <span>
+ Found {query} on{' '}
+ <a
+ href={`https://github.com/apache/superset?q=${query}`}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {category}
+ </a>
+ </span>
+ <span>{getRandomInt(200, 100)} results</span>
+ </div>
+ ),
+ };
+ });
+
+const Template: StoryFn<AntAutoCompleteProps> = args => {
+ const [options, setOptions] = useState<AntAutoCompleteProps['options']>([]);
+
+ const handleSearch = (value: string) => {
+ setOptions(value ? searchResult(value) : []);
+ };
+
+ return <AutoComplete {...args} options={options} onSearch={handleSearch} />;
+};
+
+export const Default = Template.bind({});
+Default.args = {
+ style: { width: 300 },
+ placeholder: 'Type to search...',
+};
+
+export const WithInputSearch = () => {
+ const [options, setOptions] = useState<AntAutoCompleteProps['options']>([]);
+
+ const handleSearch = (value: string) => {
+ setOptions(value ? searchResult(value) : []);
+ };
+
+ return (
+ <AutoComplete
+ popupMatchSelectWidth={252}
+ style={{ width: 300 }}
+ options={options}
+ onSearch={handleSearch}
+ >
+ <Input.Search size="large" placeholder="input here" enterButton />
+ </AutoComplete>
+ );
+};
+
+export const Disabled = Template.bind({});
+Disabled.args = {
+ style: { width: 300 },
+ placeholder: 'Disabled AutoComplete',
+ disabled: true,
+};
+
+export const CustomRender = () => {
+ const [options, setOptions] = useState<AntAutoCompleteProps['options']>([]);
+
+ const handleSearch = (value: string) => {
+ setOptions(value ? searchResult(value) : []);
+ };
+
+ return (
+ <AutoComplete
+ style={{ width: 300 }}
+ options={options}
+ onSearch={handleSearch}
+ >
+ <Input placeholder="Custom Render" />
+ </AutoComplete>
+ );
+};
diff --git a/superset-frontend/src/components/AutoComplete/AutoComplete.test.tsx b/superset-frontend/src/components/AutoComplete/AutoComplete.test.tsx
new file mode 100644
index 0000000000..700d39870d
--- /dev/null
+++ b/superset-frontend/src/components/AutoComplete/AutoComplete.test.tsx
@@ -0,0 +1,87 @@
+/**
+ * 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 { useState } from 'react';
+import { render, screen, userEvent, waitFor } from 'spec/helpers/testing-library';
+import { AutoComplete, Input } from 'antd';
+
+const searchResult = (query: string): Array<{ value: string; label: string }> =>
+ Array.from({ length: 3 }).map((_, idx) => ({
+ value: `${query}${idx}`,
+ label: `${query} result ${idx}`,
+ }));
+
+const AutoCompleteTest = () => {
+ const [options, setOptions] = useState<{ value: string; label: string }[]>([]);
+
+ const handleSearch = (value: string) => {
+ setOptions(value ? searchResult(value) : []);
+ };
+
+ return (
+ <AutoComplete options={options} onSearch={handleSearch}>
+ <Input placeholder="Type to search..." />
+ </AutoComplete>
+ );
+};
+
+describe('AutoComplete Component', () => {
+ test('renders input field', () => {
+ render(<AutoCompleteTest />);
+ expect(screen.getByPlaceholderText('Type to search...')).toBeInTheDocument();
+ });
+
+ test('shows options when user types', async () => {
+ render(<AutoCompleteTest />);
+ const input = screen.getByPlaceholderText('Type to search...');
+ userEvent.type(input, 'test');
+
+ await waitFor(() => {
+ expect(screen.getByText('test result 0')).toBeInTheDocument();
+ expect(screen.getByText('test result 1')).toBeInTheDocument();
+ expect(screen.getByText('test result 2')).toBeInTheDocument();
+ });
+ });
+
+ test('clears options when input is cleared', async () => {
+ render(<AutoCompleteTest />);
+ const input = screen.getByPlaceholderText('Type to search...');
+ userEvent.type(input, 'test');
+
+ await waitFor(() => expect(screen.getByText('test result 0')).toBeInTheDocument());
+
+ userEvent.clear(input);
+
+ await waitFor(() => {
+ expect(screen.queryByText('test result 0')).not.toBeInTheDocument();
+ });
+ });
+
+ test('selecting an option updates input value', async () => {
+ render(<AutoCompleteTest />);
+ const input = screen.getByPlaceholderText('Type to search...');
+ userEvent.type(input, 'test');
+
+ await waitFor(() => {
+ expect(screen.getByText('test result 0')).toBeInTheDocument();
+ });
+
+ userEvent.click(screen.getByText('test result 0'));
+ expect(input).toHaveValue('test0');
+ });
+});
diff --git a/superset-frontend/src/components/Input/index.tsx b/superset-frontend/src/components/AutoComplete/index.tsx
similarity index 80%
copy from superset-frontend/src/components/Input/index.tsx
copy to superset-frontend/src/components/AutoComplete/index.tsx
index 1fcb4a5369..8db93ca105 100644
--- a/superset-frontend/src/components/Input/index.tsx
+++ b/superset-frontend/src/components/AutoComplete/index.tsx
@@ -16,11 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
+import AutoComplete, {
+ AutoCompleteProps as AntAutoCompleteProps,
+} from 'antd-v5/lib/auto-complete';
-import { Input as AntdInput, InputNumber as AntdInputNumber } from 'antd-v5';
+export type { AntAutoCompleteProps };
-export const Input = AntdInput;
-
-export const InputNumber = AntdInputNumber;
-
-export const { TextArea } = AntdInput;
+export default AutoComplete;
diff --git a/superset-frontend/src/components/Input/Input.stories.tsx b/superset-frontend/src/components/Input/Input.stories.tsx
index 4023c53d97..095491a076 100644
--- a/superset-frontend/src/components/Input/Input.stories.tsx
+++ b/superset-frontend/src/components/Input/Input.stories.tsx
@@ -20,7 +20,7 @@
import { InputProps, TextAreaProps } from 'antd-v5/lib/input';
import { InputNumberProps } from 'antd-v5/lib/input-number';
import { AntdThemeProvider } from 'src/components/AntdThemeProvider';
-import { Input, TextArea, InputNumber } from '.';
+import { Input, InputNumber } from '.';
export default {
title: 'Input',
@@ -41,7 +41,7 @@ export const InteractiveInputNumber = (args: InputNumberProps) => (
export const InteractiveTextArea = (args: TextAreaProps) => (
<AntdThemeProvider>
- <TextArea {...args} />
+ <Input.TextArea {...args} />
</AntdThemeProvider>
);
diff --git a/superset-frontend/src/components/Input/Input.test.tsx b/superset-frontend/src/components/Input/Input.test.tsx
index 2fdfcc9eca..cca266e476 100644
--- a/superset-frontend/src/components/Input/Input.test.tsx
+++ b/superset-frontend/src/components/Input/Input.test.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
import { render } from 'spec/helpers/testing-library';
-import { Input, InputNumber, TextArea } from '.';
+import { Input, InputNumber } from '.';
test('should render Input', () => {
const { container } = render(<Input />);
@@ -30,6 +30,6 @@ test('should render InputNumber', () => {
});
test('should render TextArea', () => {
- const { container } = render(<TextArea />);
+ const { container } = render(<Input.TextArea />);
expect(container).toBeInTheDocument();
});
diff --git a/superset-frontend/src/components/Input/index.tsx b/superset-frontend/src/components/Input/index.tsx
index 1fcb4a5369..fede520f42 100644
--- a/superset-frontend/src/components/Input/index.tsx
+++ b/superset-frontend/src/components/Input/index.tsx
@@ -17,10 +17,13 @@
* under the License.
*/
-import { Input as AntdInput, InputNumber as AntdInputNumber } from 'antd-v5';
-
-export const Input = AntdInput;
+import { Input as Antd5Input, InputNumber as AntdInputNumber } from 'antd-v5';
export const InputNumber = AntdInputNumber;
-export const { TextArea } = AntdInput;
+export const Input = Object.assign(Antd5Input, {
+ TextArea: Antd5Input.TextArea,
+ Search: Antd5Input.Search,
+ Password: Antd5Input.Password,
+ OTP: Antd5Input.OTP,
+});
diff --git a/superset-frontend/src/components/index.ts b/superset-frontend/src/components/index.ts
index 7b7d158013..c7b42ecea8 100644
--- a/superset-frontend/src/components/index.ts
+++ b/superset-frontend/src/components/index.ts
@@ -29,7 +29,6 @@ export { default as Card } from './Card';
// Vanilla Ant Design components from v5
export {
- AutoComplete,
Col,
Grid,
Layout,
diff --git a/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx b/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx
index eb44ee81c0..ccf11bc854 100644
--- a/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx
+++ b/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx
@@ -62,7 +62,11 @@ const BackgroundStyleOption = styled.div`
${theme.colors.text.label} 25%,
transparent 25%
),
- linear-gradient(-45deg, ${theme.colors.text.label} 25%, transparent 25%),
+ linear-gradient(
+ -45deg,
+ ${theme.colors.text.label} 25%,
+ transparent 25%
+ ),
linear-gradient(45deg, transparent 75%, ${theme.colors.text.label} 75%),
linear-gradient(-45deg, transparent 75%, ${theme.colors.text.label} 75%);
background-size: ${theme.gridUnit * 2}px ${theme.gridUnit * 2}px;
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/DividerConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/DividerConfigForm.tsx
index e1f2cf4edf..f4db222360 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/DividerConfigForm.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/DividerConfigForm.tsx
@@ -18,7 +18,7 @@
*/
import { FC } from 'react';
import { FormItem } from 'src/components/Form';
-import { Input, TextArea } from 'src/components/Input';
+import { Input } from 'src/components/Input';
import { NativeFilterType, styled, t } from '@superset-ui/core';
interface Props {
@@ -51,7 +51,7 @@ const DividerConfigForm: FC<Props> = ({ componentId, divider }) => (
label={t('Description')}
name={['filters', componentId, 'description']}
>
- <TextArea rows={4} />
+ <Input.TextArea rows={4} />
</FormItem>
<FormItem
hidden
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
index ff247daa29..4b3baff9c7 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
@@ -56,7 +56,7 @@ import rison from 'rison';
import { PluginFilterSelectCustomizeProps } from 'src/filters/components/Select/types';
import { useSelector } from 'react-redux';
import { getChartDataRequest } from 'src/components/Chart/chartAction';
-import { Input, TextArea } from 'src/components/Input';
+import { Input } from 'src/components/Input';
import { Select, FormInstance } from 'src/components';
import Collapse from 'src/components/Collapse';
import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
@@ -1216,7 +1216,7 @@ const FiltersConfigForm = (
initialValue={filterToEdit?.description}
label={<StyledLabel>{t('Description')}</StyledLabel>}
>
- <TextArea onChange={debouncedFormChanged} />
+ <Input.TextArea onChange={debouncedFormChanged} />
</StyledFormItem>
<CleanFormItem
name={['filters', filterId, 'defaultValueQueriesData']}
diff --git a/superset-frontend/src/explore/components/EmbedCodeContent.jsx b/superset-frontend/src/explore/components/EmbedCodeContent.jsx
index 19ccfeb54c..0f8ca97fc0 100644
--- a/superset-frontend/src/explore/components/EmbedCodeContent.jsx
+++ b/superset-frontend/src/explore/components/EmbedCodeContent.jsx
@@ -18,7 +18,7 @@
*/
import { useCallback, useEffect, useMemo, useState } from 'react';
import { css, styled, t } from '@superset-ui/core';
-import { Input, TextArea } from 'src/components/Input';
+import { Input } from 'src/components/Input';
import CopyToClipboard from 'src/components/CopyToClipboard';
import { URL_PARAMS } from 'src/constants';
import { getChartPermalink } from 'src/utils/urlUtils';
@@ -97,7 +97,7 @@ const EmbedCodeContent = ({ formData, addDangerToast }) => {
</CopyButtonEmbedCode>
}
/>
- <TextArea
+ <Input.TextArea
data-test="embed-code-textarea"
name="embedCode"
disabled={!html}
diff --git a/superset-frontend/src/explore/components/PropertiesModal/index.tsx b/superset-frontend/src/explore/components/PropertiesModal/index.tsx
index 9a6831fc59..b5c8cd8d64 100644
--- a/superset-frontend/src/explore/components/PropertiesModal/index.tsx
+++ b/superset-frontend/src/explore/components/PropertiesModal/index.tsx
@@ -19,7 +19,7 @@
import { ChangeEvent, useMemo, useState, useCallback, useEffect } from 'react';
import Modal from 'src/components/Modal';
-import { Input, TextArea } from 'src/components/Input';
+import { Input } from 'src/components/Input';
import Button from 'src/components/Button';
import { AsyncSelect, Row, Col, AntdForm } from 'src/components';
import { SelectValue } from 'antd/lib/select';
@@ -311,7 +311,7 @@ function PropertiesModal({
</FormItem>
<FormItem>
<StyledFormItem label={t('Description')} name="description">
- <TextArea rows={3} style={{ maxWidth: '100%' }} />
+ <Input.TextArea rows={3} style={{ maxWidth: '100%' }} />
</StyledFormItem>
<StyledHelpBlock className="help-block">
{t(
diff --git a/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx b/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
index 907fe3c9a0..fd496b4026 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
@@ -21,7 +21,7 @@ import { t, styled } from '@superset-ui/core';
import { AntdForm, Col, Row } from 'src/components';
import { Form, FormLabel } from 'src/components/Form';
import { Radio } from 'src/components/Radio';
-import { Input, TextArea } from 'src/components/Input';
+import { Input } from 'src/components/Input';
import { Input as AntdInput, Tooltip } from 'antd';
import Icons from 'src/components/Icons';
import { DatabaseObject, FieldPropTypes } from '../types';
@@ -177,7 +177,7 @@ const SSHTunnelForm = ({
<FormLabel htmlFor="private_key" required>
{t('Private Key')}
</FormLabel>
- <TextArea
+ <Input.TextArea
name="private_key"
placeholder={t('Paste Private Key here')}
value={db?.ssh_tunnel?.private_key || ''}
diff --git a/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx b/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx
index a64d99dd51..3cb6c2bbb8 100644
--- a/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx
+++ b/superset-frontend/src/features/rls/RowLevelSecurityModal.tsx
@@ -28,7 +28,7 @@ import Modal from 'src/components/Modal';
import { useCallback, useEffect, useMemo, useState } from 'react';
import Icons from 'src/components/Icons';
import Select from 'src/components/Select/Select';
-import { TextArea } from 'src/components/Input';
+import { Input } from 'src/components/Input';
import AsyncSelect from 'src/components/Select/AsyncSelect';
import rison from 'rison';
import { LabeledErrorBoundInput } from 'src/components/Form';
@@ -105,7 +105,7 @@ const StyledInputContainer = styled.div`
}
`;
-const StyledTextArea = styled(TextArea)`
+const StyledTextArea = styled(Input.TextArea)`
resize: none;
margin-top: ${({ theme }) => theme.gridUnit}px;
`;