You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2024/03/28 12:28:07 UTC

(superset) branch 4.0 updated (5c567895cc -> bb5c0b4086)

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

michaelsmolina pushed a change to branch 4.0
in repository https://gitbox.apache.org/repos/asf/superset.git


    from 5c567895cc fix: Provide more inclusive error handling for saved queries (#27644)
     new a024b4ac1b fix(explore): drag and drop indicator UX (#27558)
     new 2fa1b35c16 perf(explore): virtualized datasource field sections (#27625)
     new bb5c0b4086 fix: reduce alert error to warning (#27744)

The 3 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.


Summary of changes:
 superset-frontend/package-lock.json                |  19 ++
 superset-frontend/package.json                     |   1 +
 .../DatasourcePanel/DatasourcePanel.test.tsx       |  12 ++
 .../DatasourcePanel/DatasourcePanelItem.test.tsx   | 168 +++++++++++++++
 .../DatasourcePanel/DatasourcePanelItem.tsx        | 234 +++++++++++++++++++++
 .../explore/components/DatasourcePanel/index.tsx   | 219 +++++--------------
 .../ExploreContainer/ExploreContainer.test.tsx}    |  64 +++---
 .../explore/components/ExploreContainer/index.tsx  |  60 ++++++
 .../components/ExploreViewContainer/index.jsx      |  44 ++--
 .../DndColumnSelectControl/DndSelectLabel.tsx      |  10 +-
 .../components/controls/OptionControls/index.tsx   |  92 ++++++--
 superset-frontend/src/pages/Chart/Chart.test.tsx   |   3 +
 superset/commands/report/alert.py                  |   2 +-
 13 files changed, 687 insertions(+), 241 deletions(-)
 create mode 100644 superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.test.tsx
 create mode 100644 superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.tsx
 copy superset-frontend/src/{dashboard/components/DashboardBuilder/DashboardWrapper.test.tsx => explore/components/ExploreContainer/ExploreContainer.test.tsx} (57%)
 create mode 100644 superset-frontend/src/explore/components/ExploreContainer/index.tsx


(superset) 02/03: perf(explore): virtualized datasource field sections (#27625)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 4.0
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 2fa1b35c16988d436c959be66ab9f9d958873748
Author: JUST.in DO IT <ju...@airbnb.com>
AuthorDate: Wed Mar 27 11:25:55 2024 -0700

    perf(explore): virtualized datasource field sections (#27625)
    
    (cherry picked from commit 38eecfc5d47b50f5ab24840d68e715ce2fb52709)
---
 superset-frontend/package-lock.json                |  19 ++
 superset-frontend/package.json                     |   1 +
 .../DatasourcePanel/DatasourcePanel.test.tsx       |  12 ++
 .../DatasourcePanel/DatasourcePanelItem.test.tsx   | 168 +++++++++++++++
 .../DatasourcePanel/DatasourcePanelItem.tsx        | 234 +++++++++++++++++++++
 .../explore/components/DatasourcePanel/index.tsx   | 219 +++++--------------
 .../components/ExploreViewContainer/index.jsx      |  36 ++--
 7 files changed, 504 insertions(+), 185 deletions(-)

diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json
index 38d97377c3..9148048f47 100644
--- a/superset-frontend/package-lock.json
+++ b/superset-frontend/package-lock.json
@@ -200,6 +200,7 @@
         "@types/react-table": "^7.7.19",
         "@types/react-transition-group": "^4.4.10",
         "@types/react-ultimate-pagination": "^1.2.0",
+        "@types/react-virtualized-auto-sizer": "^1.0.4",
         "@types/react-window": "^1.8.5",
         "@types/redux-localstorage": "^1.0.8",
         "@types/redux-mock-store": "^1.0.2",
@@ -22853,6 +22854,15 @@
         "@types/react": "*"
       }
     },
+    "node_modules/@types/react-virtualized-auto-sizer": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.4.tgz",
+      "integrity": "sha512-nhYwlFiYa8M3S+O2T9QO/e1FQUYMr/wJENUdf/O0dhRi1RS/93rjrYQFYdbUqtdFySuhrtnEDX29P6eKOttY+A==",
+      "dev": true,
+      "dependencies": {
+        "@types/react": "*"
+      }
+    },
     "node_modules/@types/react-window": {
       "version": "1.8.5",
       "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.5.tgz",
@@ -89600,6 +89610,15 @@
         "@types/react": "*"
       }
     },
+    "@types/react-virtualized-auto-sizer": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.4.tgz",
+      "integrity": "sha512-nhYwlFiYa8M3S+O2T9QO/e1FQUYMr/wJENUdf/O0dhRi1RS/93rjrYQFYdbUqtdFySuhrtnEDX29P6eKOttY+A==",
+      "dev": true,
+      "requires": {
+        "@types/react": "*"
+      }
+    },
     "@types/react-window": {
       "version": "1.8.5",
       "resolved": "https://registry.npmjs.org/@types/react-window/-/react-window-1.8.5.tgz",
diff --git a/superset-frontend/package.json b/superset-frontend/package.json
index c12e417aed..a028089f74 100644
--- a/superset-frontend/package.json
+++ b/superset-frontend/package.json
@@ -266,6 +266,7 @@
     "@types/react-table": "^7.7.19",
     "@types/react-transition-group": "^4.4.10",
     "@types/react-ultimate-pagination": "^1.2.0",
+    "@types/react-virtualized-auto-sizer": "^1.0.4",
     "@types/react-window": "^1.8.5",
     "@types/redux-localstorage": "^1.0.8",
     "@types/redux-mock-store": "^1.0.2",
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
index 95258f443e..452ee4609c 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
+++ b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
@@ -30,6 +30,17 @@ import {
 import { DatasourceType } from '@superset-ui/core';
 import DatasourceControl from 'src/explore/components/controls/DatasourceControl';
 
+jest.mock(
+  'react-virtualized-auto-sizer',
+  () =>
+    ({
+      children,
+    }: {
+      children: (params: { height: number }) => React.ReactChild;
+    }) =>
+      children({ height: 500 }),
+);
+
 const datasource: IDatasource = {
   id: 1,
   type: DatasourceType.Table,
@@ -69,6 +80,7 @@ const props: DatasourcePanelProps = {
   actions: {
     setControlValue: jest.fn(),
   },
+  width: 300,
 };
 
 const search = (value: string, input: HTMLElement) => {
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.test.tsx b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.test.tsx
new file mode 100644
index 0000000000..76c4d58e2d
--- /dev/null
+++ b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.test.tsx
@@ -0,0 +1,168 @@
+/**
+ * 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 React from 'react';
+
+import {
+  columns,
+  metrics,
+} from 'src/explore/components/DatasourcePanel/fixtures';
+import { fireEvent, render, within } from 'spec/helpers/testing-library';
+import DatasourcePanelItem from './DatasourcePanelItem';
+
+const mockData = {
+  metricSlice: metrics,
+  columnSlice: columns,
+  totalMetrics: Math.max(metrics.length, 10),
+  totalColumns: Math.max(columns.length, 13),
+  width: 300,
+  showAllMetrics: false,
+  onShowAllMetricsChange: jest.fn(),
+  showAllColumns: false,
+  onShowAllColumnsChange: jest.fn(),
+  collapseMetrics: false,
+  onCollapseMetricsChange: jest.fn(),
+  collapseColumns: false,
+  onCollapseColumnsChange: jest.fn(),
+};
+
+test('renders each item accordingly', () => {
+  const { getByText, getByTestId, rerender, container } = render(
+    <DatasourcePanelItem index={0} data={mockData} style={{}} />,
+    { useDnd: true },
+  );
+
+  expect(getByText('Metrics')).toBeInTheDocument();
+  rerender(<DatasourcePanelItem index={1} data={mockData} style={{}} />);
+  expect(
+    getByText(
+      `Showing ${mockData.metricSlice.length} of ${mockData.totalMetrics}`,
+    ),
+  ).toBeInTheDocument();
+  mockData.metricSlice.forEach((metric, metricIndex) => {
+    rerender(
+      <DatasourcePanelItem
+        index={metricIndex + 2}
+        data={mockData}
+        style={{}}
+      />,
+    );
+    expect(getByTestId('DatasourcePanelDragOption')).toBeInTheDocument();
+    expect(
+      within(getByTestId('DatasourcePanelDragOption')).getByText(
+        metric.metric_name,
+      ),
+    ).toBeInTheDocument();
+  });
+  rerender(
+    <DatasourcePanelItem
+      index={2 + mockData.metricSlice.length}
+      data={mockData}
+      style={{}}
+    />,
+  );
+  expect(container).toHaveTextContent('');
+
+  const startIndexOfColumnSection = mockData.metricSlice.length + 3;
+  rerender(
+    <DatasourcePanelItem
+      index={startIndexOfColumnSection}
+      data={mockData}
+      style={{}}
+    />,
+  );
+  expect(getByText('Columns')).toBeInTheDocument();
+  rerender(
+    <DatasourcePanelItem
+      index={startIndexOfColumnSection + 1}
+      data={mockData}
+      style={{}}
+    />,
+  );
+  expect(
+    getByText(
+      `Showing ${mockData.columnSlice.length} of ${mockData.totalColumns}`,
+    ),
+  ).toBeInTheDocument();
+  mockData.columnSlice.forEach((column, columnIndex) => {
+    rerender(
+      <DatasourcePanelItem
+        index={startIndexOfColumnSection + columnIndex + 2}
+        data={mockData}
+        style={{}}
+      />,
+    );
+    expect(getByTestId('DatasourcePanelDragOption')).toBeInTheDocument();
+    expect(
+      within(getByTestId('DatasourcePanelDragOption')).getByText(
+        column.column_name,
+      ),
+    ).toBeInTheDocument();
+  });
+});
+
+test('can collapse metrics and columns', () => {
+  mockData.onCollapseMetricsChange.mockClear();
+  mockData.onCollapseColumnsChange.mockClear();
+  const { queryByText, getByRole, rerender } = render(
+    <DatasourcePanelItem index={0} data={mockData} style={{}} />,
+    { useDnd: true },
+  );
+  fireEvent.click(getByRole('button'));
+  expect(mockData.onCollapseMetricsChange).toBeCalled();
+  expect(mockData.onCollapseColumnsChange).not.toBeCalled();
+
+  const startIndexOfColumnSection = mockData.metricSlice.length + 3;
+  rerender(
+    <DatasourcePanelItem
+      index={startIndexOfColumnSection}
+      data={mockData}
+      style={{}}
+    />,
+  );
+  fireEvent.click(getByRole('button'));
+  expect(mockData.onCollapseColumnsChange).toBeCalled();
+
+  rerender(
+    <DatasourcePanelItem
+      index={1}
+      data={{
+        ...mockData,
+        collapseMetrics: true,
+      }}
+      style={{}}
+    />,
+  );
+  expect(
+    queryByText(
+      `Showing ${mockData.metricSlice.length} of ${mockData.totalMetrics}`,
+    ),
+  ).not.toBeInTheDocument();
+
+  rerender(
+    <DatasourcePanelItem
+      index={2}
+      data={{
+        ...mockData,
+        collapseMetrics: true,
+      }}
+      style={{}}
+    />,
+  );
+  expect(queryByText('Columns')).toBeInTheDocument();
+});
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.tsx b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.tsx
new file mode 100644
index 0000000000..ab89019da2
--- /dev/null
+++ b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanelItem.tsx
@@ -0,0 +1,234 @@
+/**
+ * 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 React, { CSSProperties } from 'react';
+import { css, Metric, styled, t, useTheme } from '@superset-ui/core';
+
+import Icons from 'src/components/Icons';
+import DatasourcePanelDragOption from './DatasourcePanelDragOption';
+import { DndItemType } from '../DndItemType';
+import { DndItemValue } from './types';
+
+export type DataSourcePanelColumn = {
+  is_dttm?: boolean | null;
+  description?: string | null;
+  expression?: string | null;
+  is_certified?: number | null;
+  column_name?: string | null;
+  name?: string | null;
+  type?: string;
+};
+
+type Props = {
+  index: number;
+  style: CSSProperties;
+  data: {
+    metricSlice: Metric[];
+    columnSlice: DataSourcePanelColumn[];
+    totalMetrics: number;
+    totalColumns: number;
+    width: number;
+    showAllMetrics: boolean;
+    onShowAllMetricsChange: (showAll: boolean) => void;
+    showAllColumns: boolean;
+    onShowAllColumnsChange: (showAll: boolean) => void;
+    collapseMetrics: boolean;
+    onCollapseMetricsChange: (collapse: boolean) => void;
+    collapseColumns: boolean;
+    onCollapseColumnsChange: (collapse: boolean) => void;
+  };
+};
+
+export const DEFAULT_MAX_COLUMNS_LENGTH = 50;
+export const DEFAULT_MAX_METRICS_LENGTH = 50;
+export const ITEM_HEIGHT = 30;
+
+const Button = styled.button`
+  background: none;
+  border: none;
+  text-decoration: underline;
+  color: ${({ theme }) => theme.colors.primary.dark1};
+`;
+
+const ButtonContainer = styled.div`
+  text-align: center;
+  padding-top: 2px;
+`;
+
+const LabelWrapper = styled.div`
+  ${({ theme }) => css`
+    overflow: hidden;
+    text-overflow: ellipsis;
+    font-size: ${theme.typography.sizes.s}px;
+    background-color: ${theme.colors.grayscale.light4};
+    margin: ${theme.gridUnit * 2}px 0;
+    border-radius: 4px;
+    padding: 0 ${theme.gridUnit}px;
+
+    &:first-of-type {
+      margin-top: 0;
+    }
+    &:last-of-type {
+      margin-bottom: 0;
+    }
+
+    padding: 0;
+    cursor: pointer;
+    &:hover {
+      background-color: ${theme.colors.grayscale.light3};
+    }
+
+    & > span {
+      white-space: nowrap;
+    }
+
+    .option-label {
+      display: inline;
+    }
+
+    .metric-option {
+      & > svg {
+        min-width: ${theme.gridUnit * 4}px;
+      }
+      & > .option-label {
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+    }
+  `}
+`;
+
+const SectionHeaderButton = styled.button`
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border: none;
+  background: transparent;
+  width: 100%;
+  padding-inline: 0px;
+`;
+
+const SectionHeader = styled.span`
+  ${({ theme }) => `
+    font-size: ${theme.typography.sizes.m}px;
+    line-height: 1.3;
+  `}
+`;
+
+const DatasourcePanelItem: React.FC<Props> = ({ index, style, data }) => {
+  const {
+    metricSlice: _metricSlice,
+    columnSlice,
+    totalMetrics,
+    totalColumns,
+    width,
+    showAllMetrics,
+    onShowAllMetricsChange,
+    showAllColumns,
+    onShowAllColumnsChange,
+    collapseMetrics,
+    onCollapseMetricsChange,
+    collapseColumns,
+    onCollapseColumnsChange,
+  } = data;
+  const metricSlice = collapseMetrics ? [] : _metricSlice;
+
+  const EXTRA_LINES = collapseMetrics ? 1 : 2;
+  const isColumnSection = collapseMetrics
+    ? index >= 1
+    : index > metricSlice.length + EXTRA_LINES;
+  const HEADER_LINE = isColumnSection
+    ? metricSlice.length + EXTRA_LINES + 1
+    : 0;
+  const SUBTITLE_LINE = HEADER_LINE + 1;
+  const BOTTOM_LINE =
+    (isColumnSection ? columnSlice.length : metricSlice.length) +
+    (collapseMetrics ? HEADER_LINE : SUBTITLE_LINE) +
+    1;
+  const collapsed = isColumnSection ? collapseColumns : collapseMetrics;
+  const setCollapse = isColumnSection
+    ? onCollapseColumnsChange
+    : onCollapseMetricsChange;
+  const showAll = isColumnSection ? showAllColumns : showAllMetrics;
+  const setShowAll = isColumnSection
+    ? onShowAllColumnsChange
+    : onShowAllMetricsChange;
+  const theme = useTheme();
+
+  return (
+    <div
+      style={style}
+      css={css`
+        padding: 0 ${theme.gridUnit * 4}px;
+      `}
+    >
+      {index === HEADER_LINE && (
+        <SectionHeaderButton onClick={() => setCollapse(!collapsed)}>
+          <SectionHeader>
+            {isColumnSection ? t('Columns') : t('Metrics')}
+          </SectionHeader>
+          {collapsed ? (
+            <Icons.DownOutlined iconSize="s" />
+          ) : (
+            <Icons.UpOutlined iconSize="s" />
+          )}
+        </SectionHeaderButton>
+      )}
+      {index === SUBTITLE_LINE && !collapsed && (
+        <div className="field-length">
+          {isColumnSection
+            ? t(`Showing %s of %s`, columnSlice?.length, totalColumns)
+            : t(`Showing %s of %s`, metricSlice?.length, totalMetrics)}
+        </div>
+      )}
+      {index > SUBTITLE_LINE && index < BOTTOM_LINE && (
+        <LabelWrapper
+          key={
+            (isColumnSection
+              ? columnSlice[index - SUBTITLE_LINE - 1].column_name
+              : metricSlice[index - SUBTITLE_LINE - 1].metric_name) +
+            String(width)
+          }
+          className="column"
+        >
+          <DatasourcePanelDragOption
+            value={
+              isColumnSection
+                ? (columnSlice[index - SUBTITLE_LINE - 1] as DndItemValue)
+                : metricSlice[index - SUBTITLE_LINE - 1]
+            }
+            type={isColumnSection ? DndItemType.Column : DndItemType.Metric}
+          />
+        </LabelWrapper>
+      )}
+      {index === BOTTOM_LINE &&
+        !collapsed &&
+        (isColumnSection
+          ? totalColumns > DEFAULT_MAX_COLUMNS_LENGTH
+          : totalMetrics > DEFAULT_MAX_METRICS_LENGTH) && (
+          <ButtonContainer>
+            <Button onClick={() => setShowAll(!showAll)}>
+              {showAll ? t('Show less...') : t('Show all...')}
+            </Button>
+          </ButtonContainer>
+        )}
+    </div>
+  );
+};
+
+export default DatasourcePanelItem;
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
index 99f6b48b89..395b70061a 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
+++ b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { useEffect, useMemo, useRef, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
 import {
   css,
   DatasourceType,
@@ -27,10 +27,11 @@ import {
 } from '@superset-ui/core';
 
 import { ControlConfig } from '@superset-ui/chart-controls';
+import AutoSizer from 'react-virtualized-auto-sizer';
+import { FixedSizeList as List } from 'react-window';
 
 import { debounce, isArray } from 'lodash';
 import { matchSorter, rankings } from 'match-sorter';
-import Collapse from 'src/components/Collapse';
 import Alert from 'src/components/Alert';
 import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
 import { getDatasourceAsSaveableDataset } from 'src/utils/datasourceUtils';
@@ -38,23 +39,16 @@ import { Input } from 'src/components/Input';
 import { FAST_DEBOUNCE } from 'src/constants';
 import { ExploreActions } from 'src/explore/actions/exploreActions';
 import Control from 'src/explore/components/Control';
-import DatasourcePanelDragOption from './DatasourcePanelDragOption';
-import { DndItemType } from '../DndItemType';
-import { DndItemValue } from './types';
+import DatasourcePanelItem, {
+  ITEM_HEIGHT,
+  DataSourcePanelColumn,
+  DEFAULT_MAX_COLUMNS_LENGTH,
+  DEFAULT_MAX_METRICS_LENGTH,
+} from './DatasourcePanelItem';
 
 interface DatasourceControl extends ControlConfig {
   datasource?: IDatasource;
 }
-
-export interface DataSourcePanelColumn {
-  is_dttm?: boolean | null;
-  description?: string | null;
-  expression?: string | null;
-  is_certified?: number | null;
-  column_name?: string | null;
-  name?: string | null;
-  type?: string;
-}
 export interface IDatasource {
   metrics: Metric[];
   columns: DataSourcePanelColumn[];
@@ -76,22 +70,10 @@ export interface Props {
   };
   actions: Partial<ExploreActions> & Pick<ExploreActions, 'setControlValue'>;
   // we use this props control force update when this panel resize
-  shouldForceUpdate?: number;
+  width: number;
   formData?: QueryFormData;
 }
 
-const Button = styled.button`
-  background: none;
-  border: none;
-  text-decoration: underline;
-  color: ${({ theme }) => theme.colors.primary.dark1};
-`;
-
-const ButtonContainer = styled.div`
-  text-align: center;
-  padding-top: 2px;
-`;
-
 const DatasourceContainer = styled.div`
   ${({ theme }) => css`
     background-color: ${theme.colors.grayscale.light5};
@@ -104,8 +86,9 @@ const DatasourceContainer = styled.div`
       height: auto;
     }
     .field-selections {
-      padding: 0 0 ${4 * theme.gridUnit}px;
+      padding: 0 0 ${theme.gridUnit}px;
       overflow: auto;
+      height: 100%;
     }
     .field-length {
       margin-bottom: ${theme.gridUnit * 2}px;
@@ -127,56 +110,6 @@ const DatasourceContainer = styled.div`
   `};
 `;
 
-const LabelWrapper = styled.div`
-  ${({ theme }) => css`
-    overflow: hidden;
-    text-overflow: ellipsis;
-    font-size: ${theme.typography.sizes.s}px;
-    background-color: ${theme.colors.grayscale.light4};
-    margin: ${theme.gridUnit * 2}px 0;
-    border-radius: 4px;
-    padding: 0 ${theme.gridUnit}px;
-
-    &:first-of-type {
-      margin-top: 0;
-    }
-    &:last-of-type {
-      margin-bottom: 0;
-    }
-
-    padding: 0;
-    cursor: pointer;
-    &:hover {
-      background-color: ${theme.colors.grayscale.light3};
-    }
-
-    & > span {
-      white-space: nowrap;
-    }
-
-    .option-label {
-      display: inline;
-    }
-
-    .metric-option {
-      & > svg {
-        min-width: ${theme.gridUnit * 4}px;
-      }
-      & > .option-label {
-        overflow: hidden;
-        text-overflow: ellipsis;
-      }
-    }
-  `}
-`;
-
-const SectionHeader = styled.span`
-  ${({ theme }) => `
-    font-size: ${theme.typography.sizes.m}px;
-    line-height: 1.3;
-  `}
-`;
-
 const StyledInfoboxWrapper = styled.div`
   ${({ theme }) => css`
     margin: 0 ${theme.gridUnit * 2.5}px;
@@ -187,27 +120,14 @@ const StyledInfoboxWrapper = styled.div`
   `}
 `;
 
-const LabelContainer = (props: {
-  children: React.ReactElement;
-  className: string;
-}) => {
-  const labelRef = useRef<HTMLDivElement>(null);
-  const extendedProps = {
-    labelRef,
-  };
-  return (
-    <LabelWrapper className={props.className}>
-      {React.cloneElement(props.children, extendedProps)}
-    </LabelWrapper>
-  );
-};
+const BORDER_WIDTH = 2;
 
 export default function DataSourcePanel({
   datasource,
   formData,
   controls: { datasource: datasourceControl },
   actions,
-  shouldForceUpdate,
+  width,
 }: Props) {
   const { columns: _columns, metrics } = datasource;
   // display temporal column first
@@ -233,9 +153,8 @@ export default function DataSourcePanel({
   });
   const [showAllMetrics, setShowAllMetrics] = useState(false);
   const [showAllColumns, setShowAllColumns] = useState(false);
-
-  const DEFAULT_MAX_COLUMNS_LENGTH = 50;
-  const DEFAULT_MAX_METRICS_LENGTH = 50;
+  const [collapseMetrics, setCollapseMetrics] = useState(false);
+  const [collapseColumns, setCollapseColumns] = useState(false);
 
   const search = useMemo(
     () =>
@@ -385,78 +304,40 @@ export default function DataSourcePanel({
               />
             </StyledInfoboxWrapper>
           )}
-          <Collapse
-            defaultActiveKey={['metrics', 'column']}
-            expandIconPosition="right"
-            ghost
-          >
-            {metrics?.length && (
-              <Collapse.Panel
-                header={<SectionHeader>{t('Metrics')}</SectionHeader>}
-                key="metrics"
+          <AutoSizer>
+            {({ height }) => (
+              <List
+                width={width - BORDER_WIDTH}
+                height={height}
+                itemSize={ITEM_HEIGHT}
+                itemCount={
+                  (collapseMetrics ? 0 : metricSlice?.length) +
+                  (collapseColumns ? 0 : columnSlice.length) +
+                  2 + // Each section header row
+                  (collapseMetrics ? 0 : 2) +
+                  (collapseColumns ? 0 : 2)
+                }
+                itemData={{
+                  metricSlice,
+                  columnSlice,
+                  width,
+                  totalMetrics: lists?.metrics.length,
+                  totalColumns: lists?.columns.length,
+                  showAllMetrics,
+                  onShowAllMetricsChange: setShowAllMetrics,
+                  showAllColumns,
+                  onShowAllColumnsChange: setShowAllColumns,
+                  collapseMetrics,
+                  onCollapseMetricsChange: setCollapseMetrics,
+                  collapseColumns,
+                  onCollapseColumnsChange: setCollapseColumns,
+                }}
+                overscanCount={5}
               >
-                <div className="field-length">
-                  {t(
-                    `Showing %s of %s`,
-                    metricSlice?.length,
-                    lists?.metrics.length,
-                  )}
-                </div>
-                {metricSlice?.map?.((m: Metric) => (
-                  <LabelContainer
-                    key={m.metric_name + String(shouldForceUpdate)}
-                    className="column"
-                  >
-                    <DatasourcePanelDragOption
-                      value={m}
-                      type={DndItemType.Metric}
-                    />
-                  </LabelContainer>
-                ))}
-                {lists?.metrics?.length > DEFAULT_MAX_METRICS_LENGTH ? (
-                  <ButtonContainer>
-                    <Button onClick={() => setShowAllMetrics(!showAllMetrics)}>
-                      {showAllMetrics ? t('Show less...') : t('Show all...')}
-                    </Button>
-                  </ButtonContainer>
-                ) : (
-                  <></>
-                )}
-              </Collapse.Panel>
+                {DatasourcePanelItem}
+              </List>
             )}
-            <Collapse.Panel
-              header={<SectionHeader>{t('Columns')}</SectionHeader>}
-              key="column"
-            >
-              <div className="field-length">
-                {t(
-                  `Showing %s of %s`,
-                  columnSlice.length,
-                  lists.columns.length,
-                )}
-              </div>
-              {columnSlice.map(col => (
-                <LabelContainer
-                  key={col.column_name + String(shouldForceUpdate)}
-                  className="column"
-                >
-                  <DatasourcePanelDragOption
-                    value={col as DndItemValue}
-                    type={DndItemType.Column}
-                  />
-                </LabelContainer>
-              ))}
-              {lists.columns.length > DEFAULT_MAX_COLUMNS_LENGTH ? (
-                <ButtonContainer>
-                  <Button onClick={() => setShowAllColumns(!showAllColumns)}>
-                    {showAllColumns ? t('Show Less...') : t('Show all...')}
-                  </Button>
-                </ButtonContainer>
-              ) : (
-                <></>
-              )}
-            </Collapse.Panel>
-          </Collapse>
+          </AutoSizer>
         </div>
       </>
     ),
@@ -470,8 +351,10 @@ export default function DataSourcePanel({
       search,
       showAllColumns,
       showAllMetrics,
+      collapseMetrics,
+      collapseColumns,
       datasourceIsSaveable,
-      shouldForceUpdate,
+      width,
     ],
   );
 
diff --git a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx
index 0da43ebdc0..1aeb45cb15 100644
--- a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx
+++ b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx
@@ -229,6 +229,20 @@ const updateHistory = debounce(
   1000,
 );
 
+const defaultSidebarsWidth = {
+  controls_width: 320,
+  datasource_width: 300,
+};
+
+function getSidebarWidths(key) {
+  return getItem(key, defaultSidebarsWidth[key]);
+}
+
+function setSidebarWidths(key, dimension) {
+  const newDimension = Number(getSidebarWidths(key)) + dimension.width;
+  setItem(key, newDimension);
+}
+
 function ExploreViewContainer(props) {
   const dynamicPluginContext = usePluginContext();
   const dynamicPlugin = dynamicPluginContext.dynamicPlugins[props.vizType];
@@ -243,16 +257,13 @@ function ExploreViewContainer(props) {
   );
 
   const [isCollapsed, setIsCollapsed] = useState(false);
-  const [shouldForceUpdate, setShouldForceUpdate] = useState(-1);
+  const [width, setWidth] = useState(
+    getSidebarWidths(LocalStorageKeys.DatasourceWidth),
+  );
   const tabId = useTabId();
 
   const theme = useTheme();
 
-  const defaultSidebarsWidth = {
-    controls_width: 320,
-    datasource_width: 300,
-  };
-
   const addHistory = useCallback(
     async ({ isReplace = false, title } = {}) => {
       const formData = props.dashboardId
@@ -534,15 +545,6 @@ function ExploreViewContainer(props) {
     );
   }
 
-  function getSidebarWidths(key) {
-    return getItem(key, defaultSidebarsWidth[key]);
-  }
-
-  function setSidebarWidths(key, dimension) {
-    const newDimension = Number(getSidebarWidths(key)) + dimension.width;
-    setItem(key, newDimension);
-  }
-
   if (props.standalone) {
     return renderChartContainer();
   }
@@ -593,7 +595,7 @@ function ExploreViewContainer(props) {
         />
         <Resizable
           onResizeStop={(evt, direction, ref, d) => {
-            setShouldForceUpdate(d?.width);
+            setWidth(ref.getBoundingClientRect().width);
             setSidebarWidths(LocalStorageKeys.DatasourceWidth, d);
           }}
           defaultSize={{
@@ -627,7 +629,7 @@ function ExploreViewContainer(props) {
             datasource={props.datasource}
             controls={props.controls}
             actions={props.actions}
-            shouldForceUpdate={shouldForceUpdate}
+            width={width}
             user={props.user}
           />
         </Resizable>


(superset) 01/03: fix(explore): drag and drop indicator UX (#27558)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 4.0
in repository https://gitbox.apache.org/repos/asf/superset.git

commit a024b4ac1b6ae6d30337447f3557063c3adb5a51
Author: JUST.in DO IT <ju...@airbnb.com>
AuthorDate: Wed Mar 27 11:22:27 2024 -0700

    fix(explore): drag and drop indicator UX (#27558)
    
    (cherry picked from commit 736975419297898af59714363b5094ccee8ed0d1)
---
 .../ExploreContainer/ExploreContainer.test.tsx     | 85 ++++++++++++++++++++
 .../explore/components/ExploreContainer/index.tsx  | 60 ++++++++++++++
 .../components/ExploreViewContainer/index.jsx      |  8 +-
 .../DndColumnSelectControl/DndSelectLabel.tsx      | 10 ++-
 .../components/controls/OptionControls/index.tsx   | 92 +++++++++++++++++++---
 superset-frontend/src/pages/Chart/Chart.test.tsx   |  3 +
 6 files changed, 237 insertions(+), 21 deletions(-)

diff --git a/superset-frontend/src/explore/components/ExploreContainer/ExploreContainer.test.tsx b/superset-frontend/src/explore/components/ExploreContainer/ExploreContainer.test.tsx
new file mode 100644
index 0000000000..50922256ea
--- /dev/null
+++ b/superset-frontend/src/explore/components/ExploreContainer/ExploreContainer.test.tsx
@@ -0,0 +1,85 @@
+/**
+ * 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 React from 'react';
+import { fireEvent, render } from 'spec/helpers/testing-library';
+import { OptionControlLabel } from 'src/explore/components/controls/OptionControls';
+
+import ExploreContainer, { DraggingContext } from '.';
+import OptionWrapper from '../controls/DndColumnSelectControl/OptionWrapper';
+
+const MockChildren = () => {
+  const dragging = React.useContext(DraggingContext);
+  return (
+    <div data-test="mock-children" className={dragging ? 'dragging' : ''}>
+      {dragging ? 'dragging' : 'not dragging'}
+    </div>
+  );
+};
+
+test('should render children', () => {
+  const { getByTestId, getByText } = render(
+    <ExploreContainer>
+      <MockChildren />
+    </ExploreContainer>,
+    { useRedux: true, useDnd: true },
+  );
+  expect(getByTestId('mock-children')).toBeInTheDocument();
+  expect(getByText('not dragging')).toBeInTheDocument();
+});
+
+test('should update the style on dragging state', () => {
+  const defaultProps = {
+    label: <span>Test label</span>,
+    tooltipTitle: 'This is a tooltip title',
+    onRemove: jest.fn(),
+    onMoveLabel: jest.fn(),
+    onDropLabel: jest.fn(),
+    type: 'test',
+    index: 0,
+  };
+  const { container, getByText } = render(
+    <ExploreContainer>
+      <OptionControlLabel
+        {...defaultProps}
+        index={1}
+        label={<span>Label 1</span>}
+      />
+      <OptionWrapper
+        {...defaultProps}
+        index={2}
+        label="Label 2"
+        clickClose={() => {}}
+        onShiftOptions={() => {}}
+      />
+      <MockChildren />
+    </ExploreContainer>,
+    {
+      useRedux: true,
+      useDnd: true,
+    },
+  );
+  expect(container.getElementsByClassName('dragging')).toHaveLength(0);
+  fireEvent.dragStart(getByText('Label 1'));
+  expect(container.getElementsByClassName('dragging')).toHaveLength(1);
+  fireEvent.dragEnd(getByText('Label 1'));
+  expect(container.getElementsByClassName('dragging')).toHaveLength(0);
+  // don't show dragging state for the sorting item
+  fireEvent.dragStart(getByText('Label 2'));
+  expect(container.getElementsByClassName('dragging')).toHaveLength(0);
+});
diff --git a/superset-frontend/src/explore/components/ExploreContainer/index.tsx b/superset-frontend/src/explore/components/ExploreContainer/index.tsx
new file mode 100644
index 0000000000..3c64373948
--- /dev/null
+++ b/superset-frontend/src/explore/components/ExploreContainer/index.tsx
@@ -0,0 +1,60 @@
+/**
+ * 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 React, { useEffect } from 'react';
+import { styled } from '@superset-ui/core';
+import { useDragDropManager } from 'react-dnd';
+
+export const DraggingContext = React.createContext(false);
+const StyledDiv = styled.div`
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  min-height: 0;
+`;
+const ExploreContainer: React.FC<{}> = ({ children }) => {
+  const dragDropManager = useDragDropManager();
+  const [dragging, setDragging] = React.useState(
+    dragDropManager.getMonitor().isDragging(),
+  );
+
+  useEffect(() => {
+    const monitor = dragDropManager.getMonitor();
+    const unsub = monitor.subscribeToStateChange(() => {
+      const item = monitor.getItem() || {};
+      // don't show dragging state for the sorting item
+      if ('dragIndex' in item) {
+        return;
+      }
+      const isDragging = monitor.isDragging();
+      setDragging(isDragging);
+    });
+
+    return () => {
+      unsub();
+    };
+  }, [dragDropManager]);
+
+  return (
+    <DraggingContext.Provider value={dragging}>
+      <StyledDiv>{children}</StyledDiv>
+    </DraggingContext.Provider>
+  );
+};
+
+export default ExploreContainer;
diff --git a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx
index d3a32a2bd6..0da43ebdc0 100644
--- a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx
+++ b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx
@@ -68,6 +68,7 @@ import ConnectedControlPanelsContainer from '../ControlPanelsContainer';
 import SaveModal from '../SaveModal';
 import DataSourcePanel from '../DatasourcePanel';
 import ConnectedExploreChartHeader from '../ExploreChartHeader';
+import ExploreContainer from '../ExploreContainer';
 
 const propTypes = {
   ...ExploreChartPanel.propTypes,
@@ -90,13 +91,6 @@ const propTypes = {
   isSaveModalVisible: PropTypes.bool,
 };
 
-const ExploreContainer = styled.div`
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-  min-height: 0;
-`;
-
 const ExplorePanelContainer = styled.div`
   ${({ theme }) => css`
     background: ${theme.colors.grayscale.light5};
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx
index 397d6f54e0..f4da2d1729 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndSelectLabel.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { ReactNode, useMemo } from 'react';
+import React, { ReactNode, useContext, useMemo } from 'react';
 import { useDrop } from 'react-dnd';
 import { t, useTheme } from '@superset-ui/core';
 import ControlHeader from 'src/explore/components/ControlHeader';
@@ -31,6 +31,7 @@ import {
 } from 'src/explore/components/DatasourcePanel/types';
 import Icons from 'src/components/Icons';
 import { DndItemType } from '../../DndItemType';
+import { DraggingContext } from '../../ExploreContainer';
 
 export type DndSelectLabelProps = {
   name: string;
@@ -43,18 +44,20 @@ export type DndSelectLabelProps = {
   valuesRenderer: () => ReactNode;
   displayGhostButton?: boolean;
   onClickGhostButton: () => void;
+  isLoading?: boolean;
 };
 
 export default function DndSelectLabel({
   displayGhostButton = true,
   accept,
   valuesRenderer,
+  isLoading,
   ...props
 }: DndSelectLabelProps) {
   const theme = useTheme();
 
   const [{ isOver, canDrop }, datasourcePanelDrop] = useDrop({
-    accept,
+    accept: isLoading ? [] : accept,
 
     drop: (item: DatasourcePanelDndItem) => {
       props.onDrop(item);
@@ -70,6 +73,7 @@ export default function DndSelectLabel({
       type: monitor.getItemType(),
     }),
   });
+  const isDragging = useContext(DraggingContext);
 
   const values = useMemo(() => valuesRenderer(), [valuesRenderer]);
 
@@ -94,6 +98,8 @@ export default function DndSelectLabel({
         data-test="dnd-labels-container"
         canDrop={canDrop}
         isOver={isOver}
+        isDragging={isDragging}
+        isLoading={isLoading}
       >
         {values}
         {displayGhostButton && renderGhostButton()}
diff --git a/superset-frontend/src/explore/components/controls/OptionControls/index.tsx b/superset-frontend/src/explore/components/controls/OptionControls/index.tsx
index 90ae73c637..2e2bf1f5d7 100644
--- a/superset-frontend/src/explore/components/controls/OptionControls/index.tsx
+++ b/superset-frontend/src/explore/components/controls/OptionControls/index.tsx
@@ -18,7 +18,7 @@
  */
 import React, { useRef } from 'react';
 import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd';
-import { styled, t, useTheme } from '@superset-ui/core';
+import { styled, t, useTheme, keyframes, css } from '@superset-ui/core';
 import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
 import { Tooltip } from 'src/components/Tooltip';
 import Icons from 'src/components/Icons';
@@ -103,21 +103,89 @@ export const LabelsContainer = styled.div`
   border-radius: ${({ theme }) => theme.gridUnit}px;
 `;
 
+const borderPulse = keyframes`
+  0% {
+    right: 100%;
+  }
+  50% {
+    left: 4px;
+  }
+  90% {
+    right: 4px;
+  }
+  100% {
+    left: 100%;
+  }
+`;
+
 export const DndLabelsContainer = styled.div<{
   canDrop?: boolean;
   isOver?: boolean;
+  isDragging?: boolean;
+  isLoading?: boolean;
 }>`
-  padding: ${({ theme }) => theme.gridUnit}px;
-  border: ${({ canDrop, isOver, theme }) => {
-    if (canDrop) {
-      return `dashed 1px ${theme.colors.info.dark1}`;
-    }
-    if (isOver && !canDrop) {
-      return `dashed 1px ${theme.colors.error.dark1}`;
-    }
-    return `solid 1px ${theme.colors.grayscale.light2}`;
-  }};
-  border-radius: ${({ theme }) => theme.gridUnit}px;
+  ${({ theme, isLoading, canDrop, isDragging, isOver }) => `
+  position: relative;
+  padding: ${theme.gridUnit}px;
+  border: ${
+    !isLoading && isDragging
+      ? `dashed 1px ${
+          canDrop ? theme.colors.info.dark1 : theme.colors.error.dark1
+        }`
+      : `solid 1px ${
+          isLoading && isDragging
+            ? theme.colors.warning.light1
+            : theme.colors.grayscale.light2
+        }`
+  };
+  border-radius: ${theme.gridUnit}px;
+  &:before,
+  &:after {
+    content: ' ';
+    position: absolute;
+    border-radius: ${theme.gridUnit}px;
+  }
+  &:before {
+    display: ${isDragging || isLoading ? 'block' : 'none'};
+    background-color: ${
+      canDrop ? theme.colors.primary.base : theme.colors.error.light1
+    };
+    z-index: ${theme.zIndex.aboveDashboardCharts};
+    opacity: ${theme.opacity.light};
+    top: 1px;
+    right: 1px;
+    bottom: 1px;
+    left: 1px;
+  }
+  &:after {
+    display: ${isLoading || (canDrop && isOver) ? 'block' : 'none'};
+    background-color: ${
+      isLoading ? theme.colors.grayscale.light3 : theme.colors.primary.base
+    };
+    z-index: ${theme.zIndex.dropdown};
+    opacity: ${theme.opacity.mediumLight};
+    top: ${-theme.gridUnit}px;
+    right: ${-theme.gridUnit}px;
+    bottom: ${-theme.gridUnit}px;
+    left: ${-theme.gridUnit}px;
+    cursor: ${isLoading ? 'wait' : 'auto'};
+  }
+  `}
+
+  &:before {
+    ${({ theme, isLoading }) =>
+      isLoading &&
+      css`
+        animation: ${borderPulse} 2s ease-in infinite;
+        background: linear-gradient(currentColor 0 0) 0 100%/0% 3px no-repeat;
+        background-size: 100% ${theme.gridUnit / 2}px;
+        top: auto;
+        right: ${theme.gridUnit}px;
+        left: ${theme.gridUnit}px;
+        bottom: -${theme.gridUnit / 2}px;
+        height: ${theme.gridUnit / 2}px;
+      `};
+  }
 `;
 
 export const AddControlLabel = styled.div<{
diff --git a/superset-frontend/src/pages/Chart/Chart.test.tsx b/superset-frontend/src/pages/Chart/Chart.test.tsx
index 674a33d44d..f8fb9fdcbb 100644
--- a/superset-frontend/src/pages/Chart/Chart.test.tsx
+++ b/superset-frontend/src/pages/Chart/Chart.test.tsx
@@ -65,6 +65,7 @@ describe('ChartPage', () => {
     const { getByTestId } = render(<ChartPage />, {
       useRouter: true,
       useRedux: true,
+      useDnd: true,
     });
     await waitFor(() =>
       expect(fetchMock.calls(exploreApiRoute).length).toBe(1),
@@ -110,6 +111,7 @@ describe('ChartPage', () => {
       const { getByTestId } = render(<ChartPage />, {
         useRouter: true,
         useRedux: true,
+        useDnd: true,
       });
       await waitFor(() =>
         expect(fetchMock.calls(exploreApiRoute).length).toBe(1),
@@ -156,6 +158,7 @@ describe('ChartPage', () => {
         {
           useRouter: true,
           useRedux: true,
+          useDnd: true,
         },
       );
       await waitFor(() =>


(superset) 03/03: fix: reduce alert error to warning (#27744)

Posted by mi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

michaelsmolina pushed a commit to branch 4.0
in repository https://gitbox.apache.org/repos/asf/superset.git

commit bb5c0b4086a1732ba3d0fd9e02de2b91b85bf90c
Author: Elizabeth Thompson <es...@gmail.com>
AuthorDate: Wed Mar 27 18:10:22 2024 -0700

    fix: reduce alert error to warning (#27744)
    
    (cherry picked from commit 70da454bbce107c624efda9535f50f7b3ce411b2)
---
 superset/commands/report/alert.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/superset/commands/report/alert.py b/superset/commands/report/alert.py
index 5604fe0566..0e5d5401f7 100644
--- a/superset/commands/report/alert.py
+++ b/superset/commands/report/alert.py
@@ -171,7 +171,7 @@ class AlertCommand(BaseCommand):
             logger.warning("A timeout occurred while executing the alert query: %s", ex)
             raise AlertQueryTimeout() from ex
         except Exception as ex:
-            logger.exception("An error occurred when running alert query")
+            logger.warning("An error occurred when running alert query")
             # The exception message here can reveal to much information to malicious
             # users, so we raise a generic message.
             raise AlertQueryError(