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;

Reply via email to