This is an automated email from the ASF dual-hosted git repository. alexandrusoare pushed a commit to branch alexandrusoare/fix/samples-tab-filter in repository https://gitbox.apache.org/repos/asf/superset.git
commit e8fd0acfe77d78d7ba457faceeea6c8dfcfd86ad Author: alexandrusoare <[email protected]> AuthorDate: Thu Jul 23 13:48:11 2026 +0300 fix(samples): add control to filter input --- .../DataTableControl/FilterInput.test.tsx | 39 +++++++++++++++++++--- .../explore/components/DataTableControl/index.tsx | 23 ++++++++++--- .../components/DataTableControls.tsx | 7 +++- .../DataTablesPane/components/SamplesPane.tsx | 2 ++ .../components/SingleQueryResultPane.tsx | 1 + .../src/explore/components/DataTablesPane/types.ts | 1 + 6 files changed, 64 insertions(+), 9 deletions(-) diff --git a/superset-frontend/src/explore/components/DataTableControl/FilterInput.test.tsx b/superset-frontend/src/explore/components/DataTableControl/FilterInput.test.tsx index 8d4d6188dd8..dcb2f86c042 100755 --- a/superset-frontend/src/explore/components/DataTableControl/FilterInput.test.tsx +++ b/superset-frontend/src/explore/components/DataTableControl/FilterInput.test.tsx @@ -19,10 +19,20 @@ import { render, screen, userEvent } from 'spec/helpers/testing-library'; import { FilterInput } from '.'; -jest.mock('lodash', () => ({ - ...jest.requireActual('lodash'), - debounce: (fuc: Function) => fuc, -})); +jest.mock('lodash', () => { + const debounce = <T extends (...args: never[]) => unknown>(func: T) => { + const debounced = (...args: Parameters<T>) => func(...args); + debounced.cancel = jest.fn(); + debounced.flush = jest.fn(); + return debounced; + }; + + return { + __esModule: true, + ...jest.requireActual('lodash'), + debounce, + }; +}); test('Render a FilterInput', async () => { const onChangeHandler = jest.fn(); @@ -53,6 +63,27 @@ test('FilterInput auto-focuses when a non-editable element (e.g. a tab) has focu } }); +test('FilterInput syncs displayed value from external value prop', () => { + const onChangeHandler = jest.fn(); + const { rerender } = render( + <FilterInput onChangeHandler={onChangeHandler} value="hello" />, + ); + const input = screen.getByRole('textbox') as HTMLInputElement; + expect(input.value).toBe('hello'); + + rerender(<FilterInput onChangeHandler={onChangeHandler} value="" />); + expect(input.value).toBe(''); +}); + +test('FilterInput updates immediately on typing despite debounce', async () => { + const onChangeHandler = jest.fn(); + render(<FilterInput onChangeHandler={onChangeHandler} value="" />); + const input = screen.getByRole('textbox') as HTMLInputElement; + + await userEvent.type(input, 'abc'); + expect(input.value).toBe('abc'); +}); + test('FilterInput does not steal focus when another input already has focus', () => { const onChangeHandler = jest.fn(); const otherInput = document.createElement('input'); diff --git a/superset-frontend/src/explore/components/DataTableControl/index.tsx b/superset-frontend/src/explore/components/DataTableControl/index.tsx index ffaa6eb4d78..5670c6eca50 100755 --- a/superset-frontend/src/explore/components/DataTableControl/index.tsx +++ b/superset-frontend/src/explore/components/DataTableControl/index.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { useMemo, useEffect, useRef, RefObject } from 'react'; +import { useMemo, useState, useEffect, useRef, RefObject } from 'react'; import { t } from '@apache-superset/core/translation'; import { css, styled, useTheme } from '@apache-superset/core/theme'; @@ -91,11 +91,18 @@ export const CopyToClipboardButton = ({ export const FilterInput = ({ onChangeHandler, shouldFocus = false, + value: externalValue = '', }: { onChangeHandler(filterText: string): void; shouldFocus?: boolean; + value?: string; }) => { const inputRef: RefObject<any> = useRef(null); + const [internalValue, setInternalValue] = useState(externalValue); + + useEffect(() => { + setInternalValue(externalValue); + }, [externalValue]); useEffect(() => { if (inputRef.current && shouldFocus) { @@ -116,16 +123,24 @@ export const FilterInput = ({ }, []); const theme = useTheme(); - const debouncedChangeHandler = debounce( - onChangeHandler, - Constants.SLOW_DEBOUNCE, + const debouncedChangeHandler = useMemo( + () => debounce(onChangeHandler, Constants.SLOW_DEBOUNCE), + [onChangeHandler], ); + + useEffect( + () => () => debouncedChangeHandler.cancel(), + [debouncedChangeHandler], + ); + return ( <Input prefix={<Icons.SearchOutlined iconSize="l" />} placeholder={t('Search')} + value={internalValue} onChange={(event: any) => { const filterText = event.target.value; + setInternalValue(filterText); debouncedChangeHandler(filterText); }} css={css` diff --git a/superset-frontend/src/explore/components/DataTablesPane/components/DataTableControls.tsx b/superset-frontend/src/explore/components/DataTablesPane/components/DataTableControls.tsx index abae9473796..bf6ae5c173c 100644 --- a/superset-frontend/src/explore/components/DataTablesPane/components/DataTableControls.tsx +++ b/superset-frontend/src/explore/components/DataTablesPane/components/DataTableControls.tsx @@ -60,6 +60,7 @@ export const TableControls = ({ data, datasourceId, onInputChange, + filterText, columnNames, columnTypes, rowcount, @@ -93,7 +94,11 @@ export const TableControls = ({ const { canCopyClipboard: copyEnabled } = usePermissions(); return ( <TableControlsWrapper> - <FilterInput onChangeHandler={onInputChange} shouldFocus /> + <FilterInput + onChangeHandler={onInputChange} + shouldFocus + value={filterText} + /> <div css={css` display: flex; diff --git a/superset-frontend/src/explore/components/DataTablesPane/components/SamplesPane.tsx b/superset-frontend/src/explore/components/DataTablesPane/components/SamplesPane.tsx index e743ad6844a..9e2fa986164 100644 --- a/superset-frontend/src/explore/components/DataTablesPane/components/SamplesPane.tsx +++ b/superset-frontend/src/explore/components/DataTablesPane/components/SamplesPane.tsx @@ -148,6 +148,7 @@ export const SamplesPane = ({ rowcount={rowcount} datasourceId={datasourceId} onInputChange={handleInputChange} + filterText={filterText} isLoading={isLoading} canDownload={canDownload} rowLimit={rowLimit} @@ -176,6 +177,7 @@ export const SamplesPane = ({ rowcount={rowcount} datasourceId={datasourceId} onInputChange={handleInputChange} + filterText={filterText} isLoading={isLoading} canDownload={canDownload} rowLimit={rowLimit} diff --git a/superset-frontend/src/explore/components/DataTablesPane/components/SingleQueryResultPane.tsx b/superset-frontend/src/explore/components/DataTablesPane/components/SingleQueryResultPane.tsx index 8cd279f4164..0c2cb0a4e5d 100644 --- a/superset-frontend/src/explore/components/DataTablesPane/components/SingleQueryResultPane.tsx +++ b/superset-frontend/src/explore/components/DataTablesPane/components/SingleQueryResultPane.tsx @@ -81,6 +81,7 @@ export const SingleQueryResultPane = ({ rowcount={rowcount} datasourceId={datasourceId} onInputChange={handleInputChange} + filterText={filterText} isLoading={false} canDownload={canDownload} rowLimit={rowLimit} diff --git a/superset-frontend/src/explore/components/DataTablesPane/types.ts b/superset-frontend/src/explore/components/DataTablesPane/types.ts index 411e0ef3bff..e7db8500638 100644 --- a/superset-frontend/src/explore/components/DataTablesPane/types.ts +++ b/superset-frontend/src/explore/components/DataTablesPane/types.ts @@ -76,6 +76,7 @@ export interface TableControlsProps extends DrillControlsProps { // {datasource.id}__{datasource.type}, eg: 1__table datasourceId?: string; onInputChange: (input: string) => void; + filterText?: string; columnNames: string[]; columnTypes: GenericDataType[]; isLoading: boolean;
