amaannawab923 commented on code in PR #43756:
URL: https://github.com/apache/superset/pull/43756#discussion_r3906771094


##########
superset-frontend/plugins/plugin-chart-table/src/DataTable/components/GlobalFilter.tsx:
##########
@@ -87,10 +94,14 @@ export default (memo as <T>(fn: T) => T)(function 
GlobalFilter<
 }: GlobalFilterProps<D>) {
   const count = serverPagination ? rowCount : preGlobalFilteredRows.length;
   const inputRef = useRef<InputRef>(null);
+  const isComposingRef = useRef(false);
 
   const [value, setValue] = useAsyncState(
     filterValue,
     (newValue: string) => {
+      if (isComposingRef.current) {

Review Comment:
   Worth confirming the intended scope here. Android soft keyboards, Gboard in 
particular, fire `compositionstart` and `compositionend` around ordinary Latin 
word entry with suggestions enabled, not only for CJK input methods.
   
   With this guard in place, search on those keyboards defers to word 
boundaries rather than the 200ms debounce. That may well be acceptable or even 
preferable, but it is a behaviour change for a much larger group than the issue 
describes, so it seems worth being deliberate about rather than incidental.



##########
superset-frontend/plugins/plugin-chart-table/src/DataTable/components/GlobalFilter.tsx:
##########
@@ -118,6 +129,17 @@ export default (memo as <T>(fn: T) => T)(function 
GlobalFilter<
     isSearchFocused.set(id, false);
   };
 
+  const handleCompositionStart = () => {
+    isComposingRef.current = true;

Review Comment:
   `isComposingRef` is set here and cleared only in `handleCompositionEnd`. If 
a composition is interrupted without a `compositionend`, the flag stays set for 
the lifetime of the component and every later debounced `setGlobalFilter` 
returns early at the guard above, so search stops working with nothing surfaced 
to the user.
   
   Blur while a candidate window is open is the easiest way to reach that 
state, and it is not consistent across browsers and IMEs.
   
   Clearing it in `handleBlur` alongside `isSearchFocused.set(id, false)` would 
bound the failure to a single interaction rather than the component instance.



##########
superset-frontend/plugins/plugin-chart-table/src/DataTable/components/GlobalFilter.tsx:
##########
@@ -118,6 +129,17 @@ export default (memo as <T>(fn: T) => T)(function 
GlobalFilter<
     isSearchFocused.set(id, false);
   };
 
+  const handleCompositionStart = () => {
+    isComposingRef.current = true;
+  };
+
+  const handleCompositionEnd = (
+    e: React.CompositionEvent<HTMLInputElement>,

Review Comment:
   Nit: this file imports its React types by name, including 
`CompositionEventHandler` added just above, and does not import the `React` 
namespace. Referring to `React.CompositionEvent` here relies on the global UMD 
namespace instead.
   
   `CompositionEvent<HTMLInputElement>` from the existing named import would 
keep it consistent with the rest of the file.



##########
superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx:
##########
@@ -2658,6 +2659,74 @@ describe('plugin-chart-table', () => {
     expect(screen.queryByText('Search by')).toBeInTheDocument();
   });
 
+  test('defers server-side search until IME composition ends', async () => {
+    jest.useFakeTimers();
+    try {
+      const setDataMask = jest.fn();
+      const props = transformProps({
+        ...testData.raw,
+        rawFormData: {
+          ...testData.raw.rawFormData,
+          server_pagination: true,
+          include_search: true,
+        },
+        hooks: { setDataMask },
+        queriesData: [
+          {
+            ...testData.raw.queriesData[0],
+            colnames: ['name'],
+            coltypes: [GenericDataType.String],
+            data: [{ name: 'Michael' }, { name: 'John' }],
+          },
+        ],
+      });
+      render(
+        ProviderWrapper({
+          children: (
+            <TableChart {...props} setDataMask={setDataMask} sticky={false} />
+          ),
+        }),
+      );
+
+      const searchInput = screen.getByRole('textbox');
+      const searchCalls = () =>
+        setDataMask.mock.calls.filter(([mask]) =>
+          Object.prototype.hasOwnProperty.call(
+            mask?.ownState ?? {},
+            'searchText',
+          ),
+        );
+
+      fireEvent.compositionStart(searchInput);
+      fireEvent.change(searchInput, { target: { value: 'nihao' } });
+
+      await act(async () => {
+        jest.advanceTimersByTime(300);
+      });
+      await act(async () => {
+        jest.advanceTimersByTime(900);
+      });
+      expect(searchInput).toHaveValue('nihao');
+      expect(searchCalls()).toHaveLength(0);
+
+      fireEvent.change(searchInput, { target: { value: '你好' } });
+      fireEvent.compositionEnd(searchInput);

Review Comment:
   The test fires the final change before `compositionEnd`. Browsers differ on 
this ordering, and Chrome commonly delivers `compositionend` before the final 
input event.
   
   The implementation already handles that case by reading 
`e.currentTarget.value` directly, which is the part worth protecting. A second 
case that fires `compositionEnd` first, with the committed text set on the 
element, would lock that in. As written the suite would still pass if the 
handler were changed to depend on the preceding change event.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to