This is an automated email from the ASF dual-hosted git repository.

johnbodley 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 16c449748a fix: Custom SQL filter control (#29260)
16c449748a is described below

commit 16c449748a4b1a0811285ef5c8765cc8b447907b
Author: Michael S. Molina <[email protected]>
AuthorDate: Fri Jun 14 14:26:58 2024 -0300

    fix: Custom SQL filter control (#29260)
---
 superset-frontend/spec/helpers/testing-library.tsx | 24 ++++++-
 .../ScopingModal/ScopingModal.test.tsx             | 34 +++-------
 .../controls/FilterControl/AdhocFilter/index.js    |  1 +
 .../FilterControl/AdhocFilterEditPopover/index.jsx |  5 +-
 .../AdhocFilterEditPopoverSqlTabContent.test.jsx   | 73 ---------------------
 .../AdhocFilterEditPopoverSqlTabContent.test.tsx   | 75 ++++++++++++++++++++++
 .../AdhocFilterEditPopoverSqlTabContent/index.jsx  | 26 +++-----
 .../AdhocMetricEditPopover.test.tsx                |  7 +-
 .../features/rls/RowLevelSecurityModal.test.tsx    | 31 +++------
 9 files changed, 132 insertions(+), 144 deletions(-)

diff --git a/superset-frontend/spec/helpers/testing-library.tsx 
b/superset-frontend/spec/helpers/testing-library.tsx
index 61ccc7fb0d..625531f926 100644
--- a/superset-frontend/spec/helpers/testing-library.tsx
+++ b/superset-frontend/spec/helpers/testing-library.tsx
@@ -18,7 +18,13 @@
  */
 import '@testing-library/jest-dom/extend-expect';
 import { ReactNode, ReactElement } from 'react';
-import { render, RenderOptions } from '@testing-library/react';
+import {
+  render,
+  RenderOptions,
+  screen,
+  waitFor,
+  within,
+} from '@testing-library/react';
 import { ThemeProvider, supersetTheme } from '@superset-ui/core';
 import { BrowserRouter } from 'react-router-dom';
 import { Provider } from 'react-redux';
@@ -28,6 +34,7 @@ import reducerIndex from 'spec/helpers/reducerIndex';
 import { QueryParamProvider } from 'use-query-params';
 import { configureStore, Store } from '@reduxjs/toolkit';
 import { api } from 'src/hooks/apiResources/queryApi';
+import userEvent from '@testing-library/user-event';
 
 type Options = Omit<RenderOptions, 'queries'> & {
   useRedux?: boolean;
@@ -102,3 +109,18 @@ export function sleep(time: number) {
 
 export * from '@testing-library/react';
 export { customRender as render };
+
+export async function selectOption(option: string, selectName?: string) {
+  const select = screen.getByRole(
+    'combobox',
+    selectName ? { name: selectName } : {},
+  );
+  await userEvent.click(select);
+  const item = await waitFor(() =>
+    within(
+      // eslint-disable-next-line testing-library/no-node-access
+      document.querySelector('.rc-virtual-list')!,
+    ).getByText(option),
+  );
+  await userEvent.click(item);
+}
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
index 9411d3e7a4..392db3ae09 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
@@ -18,7 +18,13 @@
  */
 import fetchMock from 'fetch-mock';
 import userEvent from '@testing-library/user-event';
-import { render, screen, waitFor, within } from 'spec/helpers/testing-library';
+import {
+  render,
+  screen,
+  selectOption,
+  waitFor,
+  within,
+} from 'spec/helpers/testing-library';
 import {
   CHART_TYPE,
   DASHBOARD_ROOT_TYPE,
@@ -199,21 +205,7 @@ it('add new custom scoping', async () => {
   expect(screen.getByText('[new custom scoping]')).toBeInTheDocument();
   expect(screen.getByText('[new custom scoping]')).toHaveClass('active');
 
-  await waitFor(() =>
-    userEvent.click(screen.getByRole('combobox', { name: 'Select chart' })),
-  );
-  await waitFor(() => {
-    userEvent.click(
-      within(document.querySelector('.rc-virtual-list')!).getByText('chart 1'),
-    );
-  });
-
-  expect(
-    within(document.querySelector('.ant-select-selection-item')!).getByText(
-      'chart 1',
-    ),
-  ).toBeInTheDocument();
-
+  await selectOption('chart 1', 'Select chart');
   expect(
     document.querySelectorAll(
       '[data-test="scoping-tree-panel"] .ant-tree-checkbox-checked',
@@ -250,14 +242,8 @@ it('edit scope and save', async () => {
 
   // create custom scoping for chart 1 with unselected chart 2 (from global) 
and chart 4
   userEvent.click(screen.getByText('Add custom scoping'));
-  await waitFor(() =>
-    userEvent.click(screen.getByRole('combobox', { name: 'Select chart' })),
-  );
-  await waitFor(() => {
-    userEvent.click(
-      within(document.querySelector('.rc-virtual-list')!).getByText('chart 1'),
-    );
-  });
+  await selectOption('chart 1', 'Select chart');
+
   userEvent.click(
     within(document.querySelector('.ant-tree')!).getByText('chart 4'),
   );
diff --git 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js
 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js
index 627cc50c44..92cb69eebc 100644
--- 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js
+++ 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js
@@ -92,6 +92,7 @@ export default class AdhocFilter {
 
   equals(adhocFilter) {
     return (
+      adhocFilter.clause === this.clause &&
       adhocFilter.expressionType === this.expressionType &&
       adhocFilter.sqlExpression === this.sqlExpression &&
       adhocFilter.operator === this.operator &&
diff --git 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopover/index.jsx
 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopover/index.jsx
index a0e8f33b94..01169ff187 100644
--- 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopover/index.jsx
+++ 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopover/index.jsx
@@ -73,11 +73,12 @@ const FilterPopoverContentContainer = styled.div`
 
   .filter-edit-clause-info {
     font-size: ${({ theme }) => theme.typography.sizes.xs}px;
-    padding-left: ${({ theme }) => theme.gridUnit}px;
   }
 
   .filter-edit-clause-section {
-    display: inline-flex;
+    display: flex;
+    flex-direction: row;
+    gap: ${({ theme }) => theme.gridUnit * 5}px;
   }
 
   .adhoc-filter-simple-column-dropdown {
diff --git 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/AdhocFilterEditPopoverSqlTabContent.test.jsx
 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/AdhocFilterEditPopoverSqlTabContent.test.jsx
deleted file mode 100644
index 7366e7e0ef..0000000000
--- 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/AdhocFilterEditPopoverSqlTabContent.test.jsx
+++ /dev/null
@@ -1,73 +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.
- */
-/* eslint-disable no-unused-expressions */
-import sinon from 'sinon';
-import { shallow } from 'enzyme';
-
-import AdhocFilter from 
'src/explore/components/controls/FilterControl/AdhocFilter';
-import AdhocFilterEditPopoverSqlTabContent from '.';
-import { Clauses, ExpressionTypes } from '../types';
-
-const sqlAdhocFilter = new AdhocFilter({
-  expressionType: ExpressionTypes.Sql,
-  sqlExpression: 'value > 10',
-  clause: Clauses.Where,
-});
-
-function setup(overrides) {
-  const onChange = sinon.spy();
-  const props = {
-    adhocFilter: sqlAdhocFilter,
-    onChange,
-    options: [],
-    height: 100,
-    ...overrides,
-  };
-  const wrapper = shallow(<AdhocFilterEditPopoverSqlTabContent {...props} />);
-  return { wrapper, onChange };
-}
-
-describe('AdhocFilterEditPopoverSqlTabContent', () => {
-  it('renders the sql tab form', () => {
-    const { wrapper } = setup();
-    expect(wrapper).toExist();
-  });
-
-  it('passes the new clause to onChange after onSqlExpressionClauseChange', () 
=> {
-    const { wrapper, onChange } = setup();
-    wrapper.instance().onSqlExpressionClauseChange(Clauses.Having);
-    expect(onChange.calledOnce).toBe(true);
-    expect(
-      onChange.lastCall.args[0].equals(
-        sqlAdhocFilter.duplicateWith({ clause: Clauses.Having }),
-      ),
-    ).toBe(true);
-  });
-
-  it('passes the new query to onChange after onSqlExpressionChange', () => {
-    const { wrapper, onChange } = setup();
-    wrapper.instance().onSqlExpressionChange('value < 5');
-    expect(onChange.calledOnce).toBe(true);
-    expect(
-      onChange.lastCall.args[0].equals(
-        sqlAdhocFilter.duplicateWith({ sqlExpression: 'value < 5' }),
-      ),
-    ).toBe(true);
-  });
-});
diff --git 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/AdhocFilterEditPopoverSqlTabContent.test.tsx
 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/AdhocFilterEditPopoverSqlTabContent.test.tsx
new file mode 100644
index 0000000000..1d518a5c63
--- /dev/null
+++ 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/AdhocFilterEditPopoverSqlTabContent.test.tsx
@@ -0,0 +1,75 @@
+/**
+ * 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 { render, screen, selectOption } from 'spec/helpers/testing-library';
+import userEvent from '@testing-library/user-event';
+import { IAceEditorProps } from 'react-ace';
+import AdhocFilter from '../AdhocFilter';
+import { Clauses, ExpressionTypes } from '../types';
+import AdhocFilterEditPopoverSqlTabContent from '.';
+
+jest.mock('src/components/AsyncAceEditor', () => ({
+  SQLEditor: ({ value, onChange }: IAceEditorProps) => (
+    <textarea
+      defaultValue={value}
+      onChange={value => onChange?.(value.target.value)}
+    />
+  ),
+}));
+
+const adhocFilter = new AdhocFilter({
+  expressionType: ExpressionTypes.Sql,
+  sqlExpression: 'value > 10',
+  clause: Clauses.Where,
+});
+
+test('calls onChange when the SQL clause changes', async () => {
+  const onChange = jest.fn();
+  render(
+    <AdhocFilterEditPopoverSqlTabContent
+      adhocFilter={adhocFilter}
+      onChange={onChange}
+      options={[]}
+      height={100}
+    />,
+  );
+  await selectOption(Clauses.Having);
+  expect(onChange).toHaveBeenCalledWith(
+    expect.objectContaining({ clause: Clauses.Having }),
+  );
+});
+
+test('calls onChange when the SQL expression changes', () => {
+  const onChange = jest.fn();
+  const input = 'value < 20';
+  render(
+    <AdhocFilterEditPopoverSqlTabContent
+      adhocFilter={adhocFilter}
+      onChange={onChange}
+      options={[]}
+      height={100}
+    />,
+  );
+  const sqlEditor = screen.getByRole('textbox');
+  expect(sqlEditor).toBeInTheDocument();
+  userEvent.clear(sqlEditor);
+  userEvent.paste(sqlEditor, input);
+  expect(onChange).toHaveBeenCalledWith(
+    expect.objectContaining({ sqlExpression: input }),
+  );
+});
diff --git 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/index.jsx
 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/index.jsx
index 2ae0fd6164..f24dff5aab 100644
--- 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/index.jsx
+++ 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSqlTabContent/index.jsx
@@ -39,7 +39,6 @@ const propTypes = {
     ]),
   ).isRequired,
   height: PropTypes.number.isRequired,
-  activeKey: PropTypes.string.isRequired,
 };
 
 const StyledSelect = styled(Select)`
@@ -56,10 +55,6 @@ export default class AdhocFilterEditPopoverSqlTabContent 
extends Component {
     this.onSqlExpressionClauseChange =
       this.onSqlExpressionClauseChange.bind(this);
     this.handleAceEditorRef = this.handleAceEditorRef.bind(this);
-
-    this.selectProps = {
-      ariaLabel: t('Select column'),
-    };
   }
 
   componentDidUpdate() {
@@ -95,11 +90,6 @@ export default class AdhocFilterEditPopoverSqlTabContent 
extends Component {
   render() {
     const { adhocFilter, height, options } = this.props;
 
-    const clauseSelectProps = {
-      placeholder: t('choose WHERE or HAVING...'),
-      value: adhocFilter.clause,
-      onChange: this.onSqlExpressionClauseChange,
-    };
     const keywords = sqlKeywords.concat(
       options
         .map(option => {
@@ -115,7 +105,7 @@ export default class AdhocFilterEditPopoverSqlTabContent 
extends Component {
         })
         .filter(Boolean),
     );
-    const selectOptions = Object.keys(Clauses).map(clause => ({
+    const selectOptions = Object.values(Clauses).map(clause => ({
       label: clause,
       value: clause,
     }));
@@ -123,11 +113,15 @@ export default class AdhocFilterEditPopoverSqlTabContent 
extends Component {
     return (
       <span>
         <div className="filter-edit-clause-section">
-          <StyledSelect
-            options={selectOptions}
-            {...this.selectProps}
-            {...clauseSelectProps}
-          />
+          <div>
+            <StyledSelect
+              options={selectOptions}
+              ariaLabel={t('Select column')}
+              placeholder={t('choose WHERE or HAVING...')}
+              value={adhocFilter.clause}
+              onChange={this.onSqlExpressionClauseChange}
+            />
+          </div>
           <span className="filter-edit-clause-info">
             <strong>WHERE</strong> {t('Filters by columns')}
             <br />
diff --git 
a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/AdhocMetricEditPopover.test.tsx
 
b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/AdhocMetricEditPopover.test.tsx
index f0ebd1fc14..1aa7e3acaa 100644
--- 
a/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/AdhocMetricEditPopover.test.tsx
+++ 
b/superset-frontend/src/explore/components/controls/MetricControl/AdhocMetricEditPopover/AdhocMetricEditPopover.test.tsx
@@ -17,7 +17,7 @@
  * under the License.
  */
 import userEvent from '@testing-library/user-event';
-import { render, screen, waitFor, within } from 'spec/helpers/testing-library';
+import { render, screen, selectOption } from 'spec/helpers/testing-library';
 import AdhocMetric from 
'src/explore/components/controls/MetricControl/AdhocMetric';
 import AdhocMetricEditPopover from '.';
 
@@ -132,10 +132,7 @@ test('Clicking on "Save" should call onChange and 
onClose', async () => {
       name: 'Select saved metrics',
     }),
   );
-  const sumOption = await waitFor(() =>
-    within(document.querySelector('.rc-virtual-list')!).getByText('sum'),
-  );
-  userEvent.click(sumOption);
+  await selectOption('sum');
   userEvent.click(screen.getByRole('button', { name: 'Save' }));
   expect(props.onChange).toBeCalledTimes(1);
   expect(props.onClose).toBeCalledTimes(1);
diff --git a/superset-frontend/src/features/rls/RowLevelSecurityModal.test.tsx 
b/superset-frontend/src/features/rls/RowLevelSecurityModal.test.tsx
index b5954c8d96..dfd22abc72 100644
--- a/superset-frontend/src/features/rls/RowLevelSecurityModal.test.tsx
+++ b/superset-frontend/src/features/rls/RowLevelSecurityModal.test.tsx
@@ -17,7 +17,12 @@
  * under the License.
  */
 import fetchMock from 'fetch-mock';
-import { render, screen, waitFor, within } from 'spec/helpers/testing-library';
+import {
+  render,
+  screen,
+  selectOption,
+  waitFor,
+} from 'spec/helpers/testing-library';
 import { act } from 'react-dom/test-utils';
 import userEvent from '@testing-library/user-event';
 import RowLevelSecurityModal, {
@@ -230,17 +235,7 @@ describe('Rule modal', () => {
 
     expect(addButton).toBeDisabled();
 
-    const getSelect = () => screen.getByRole('combobox', { name: 'Tables' });
-    const getElementByClassName = (className: string) =>
-      document.querySelector(className)! as HTMLElement;
-
-    const findSelectOption = (text: string) =>
-      waitFor(() =>
-        within(getElementByClassName('.rc-virtual-list')).getByText(text),
-      );
-    const open = () => waitFor(() => userEvent.click(getSelect()));
-    await open();
-    userEvent.click(await findSelectOption('birth_names'));
+    await selectOption('birth_names', 'Tables');
     expect(addButton).toBeDisabled();
 
     const clause = await screen.findByTestId('clause-test');
@@ -257,17 +252,7 @@ describe('Rule modal', () => {
     const nameTextBox = screen.getByTestId('rule-name-test');
     userEvent.type(nameTextBox, 'name');
 
-    const getSelect = () => screen.getByRole('combobox', { name: 'Tables' });
-    const getElementByClassName = (className: string) =>
-      document.querySelector(className)! as HTMLElement;
-
-    const findSelectOption = (text: string) =>
-      waitFor(() =>
-        within(getElementByClassName('.rc-virtual-list')).getByText(text),
-      );
-    const open = () => waitFor(() => userEvent.click(getSelect()));
-    await open();
-    userEvent.click(await findSelectOption('birth_names'));
+    await selectOption('birth_names', 'Tables');
 
     const clause = await screen.findByTestId('clause-test');
     userEvent.type(clause, 'gender="girl"');

Reply via email to