You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by kg...@apache.org on 2021/08/18 10:24:15 UTC

[superset] branch master updated: chore(explore): make metric/column search input clearable (#16320)

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

kgabryje 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 2c595b0  chore(explore): make metric/column search input clearable (#16320)
2c595b0 is described below

commit 2c595b09eabb28e22a8bb3866d1fc940c6373753
Author: Kamil Gabryjelski <ka...@gmail.com>
AuthorDate: Wed Aug 18 12:23:23 2021 +0200

    chore(explore): make metric/column search input clearable (#16320)
    
    * chore(explore): make metric/column search input clearable
    
    * Fix typo
    
    * Fix test
---
 .../DatasourcePanel/DatasourcePanel.test.tsx       |   2 +-
 .../explore/components/DatasourcePanel/index.tsx   | 379 ++++++++++++---------
 2 files changed, 211 insertions(+), 170 deletions(-)

diff --git a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
index 0d06595..3448433 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
+++ b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
@@ -116,7 +116,7 @@ test('should render 0 search results', async () => {
   const searchInput = screen.getByPlaceholderText('Search Metrics & Columns');
 
   search('nothing', searchInput);
-  expect(await screen.findByText('Showing 0 of 0')).toBeInTheDocument();
+  expect(await screen.findAllByText('Showing 0 of 0')).toHaveLength(2);
 });
 
 test('should search and render matching columns', async () => {
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
index bc436cf..2094583 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
+++ b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx
@@ -16,12 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { useEffect, useMemo, useRef, useState } from 'react';
-import { styled, t } from '@superset-ui/core';
-import Collapse from 'src/components/Collapse';
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react';
 import { ControlConfig, DatasourceMeta } from '@superset-ui/chart-controls';
 import { debounce } from 'lodash';
 import { matchSorter, rankings } from 'match-sorter';
+import { css, styled, t } from '@superset-ui/core';
+import Collapse from 'src/components/Collapse';
+import { Input } from 'src/common/components';
 import { FAST_DEBOUNCE } from 'src/constants';
 import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
 import { ExploreActions } from 'src/explore/actions/exploreActions';
@@ -55,36 +62,39 @@ const ButtonContainer = styled.div`
 `;
 
 const DatasourceContainer = styled.div`
-  background-color: ${({ theme }) => theme.colors.grayscale.light4};
-  position: relative;
-  height: 100%;
-  display: flex;
-  flex-direction: column;
-  max-height: 100%;
-  .ant-collapse {
-    height: auto;
-  }
-  .field-selections {
-    padding: ${({ theme }) => `0 0 ${4 * theme.gridUnit}px`};
-    overflow: auto;
-  }
-  .field-length {
-    margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
-    font-size: ${({ theme }) => theme.typography.sizes.s}px;
-    color: ${({ theme }) => theme.colors.grayscale.light1};
-  }
-  .form-control.input-md {
-    width: calc(100% - ${({ theme }) => theme.gridUnit * 4}px);
-    margin: ${({ theme }) => theme.gridUnit * 2}px auto;
-  }
-  .type-label {
-    font-weight: ${({ theme }) => theme.typography.weights.light};
-    font-size: ${({ theme }) => theme.typography.sizes.s}px;
-    color: ${({ theme }) => theme.colors.grayscale.base};
-  }
-  .Control {
-    padding-bottom: 0;
-  }
+  ${({ theme }) => css`
+    background-color: ${theme.colors.grayscale.light4};
+    position: relative;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    max-height: 100%;
+    .ant-collapse {
+      height: auto;
+    }
+    .field-selections {
+      padding: 0 0 ${4 * theme.gridUnit}px;
+      overflow: auto;
+    }
+    .field-length {
+      margin-bottom: ${theme.gridUnit * 2}px;
+      font-size: ${theme.typography.sizes.s}px;
+      color: ${theme.colors.grayscale.light1};
+    }
+    .form-control.input-md {
+      width: calc(100% - ${theme.gridUnit * 4}px);
+      height: ${theme.gridUnit * 8}px;
+      margin: ${theme.gridUnit * 2}px auto;
+    }
+    .type-label {
+      font-weight: ${theme.typography.weights.light};
+      font-size: ${theme.typography.sizes.s}px;
+      color: ${theme.colors.grayscale.base};
+    }
+    .Control {
+      padding-bottom: 0;
+    }
+  `};
 `;
 
 const LabelWrapper = styled.div`
@@ -183,59 +193,62 @@ export default function DataSourcePanel({
   const DEFAULT_MAX_COLUMNS_LENGTH = 50;
   const DEFAULT_MAX_METRICS_LENGTH = 50;
 
-  const search = debounce((value: string) => {
-    if (value === '') {
-      setList({ columns, metrics });
-      return;
-    }
-    setList({
-      columns: matchSorter(columns, value, {
-        keys: [
-          {
-            key: 'verbose_name',
-            threshold: rankings.CONTAINS,
-          },
-          {
-            key: 'column_name',
-            threshold: rankings.CONTAINS,
-          },
-          {
-            key: item =>
-              [item.description, item.expression].map(
-                x => x?.replace(/[_\n\s]+/g, ' ') || '',
-              ),
-            threshold: rankings.CONTAINS,
-            maxRanking: rankings.CONTAINS,
-          },
-        ],
-        keepDiacritics: true,
-      }),
-      metrics: matchSorter(metrics, value, {
-        keys: [
-          {
-            key: 'verbose_name',
-            threshold: rankings.CONTAINS,
-          },
-          {
-            key: 'metric_name',
-            threshold: rankings.CONTAINS,
-          },
-          {
-            key: item =>
-              [item.description, item.expression].map(
-                x => x?.replace(/[_\n\s]+/g, ' ') || '',
-              ),
-            threshold: rankings.CONTAINS,
-            maxRanking: rankings.CONTAINS,
-          },
-        ],
-        keepDiacritics: true,
-        baseSort: (a, b) =>
-          Number(b.item.is_certified) - Number(a.item.is_certified) ||
-          String(a.rankedValue).localeCompare(b.rankedValue),
-      }),
-    });
-  }, FAST_DEBOUNCE);
+  const search = useCallback(
+    debounce((value: string) => {
+      if (value === '') {
+        setList({ columns, metrics });
+        return;
+      }
+      setList({
+        columns: matchSorter(columns, value, {
+          keys: [
+            {
+              key: 'verbose_name',
+              threshold: rankings.CONTAINS,
+            },
+            {
+              key: 'column_name',
+              threshold: rankings.CONTAINS,
+            },
+            {
+              key: item =>
+                [item.description, item.expression].map(
+                  x => x?.replace(/[_\n\s]+/g, ' ') || '',
+                ),
+              threshold: rankings.CONTAINS,
+              maxRanking: rankings.CONTAINS,
+            },
+          ],
+          keepDiacritics: true,
+        }),
+        metrics: matchSorter(metrics, value, {
+          keys: [
+            {
+              key: 'verbose_name',
+              threshold: rankings.CONTAINS,
+            },
+            {
+              key: 'metric_name',
+              threshold: rankings.CONTAINS,
+            },
+            {
+              key: item =>
+                [item.description, item.expression].map(
+                  x => x?.replace(/[_\n\s]+/g, ' ') || '',
+                ),
+              threshold: rankings.CONTAINS,
+              maxRanking: rankings.CONTAINS,
+            },
+          ],
+          keepDiacritics: true,
+          baseSort: (a, b) =>
+            Number(b.item.is_certified) - Number(a.item.is_certified) ||
+            String(a.rankedValue).localeCompare(b.rankedValue),
+        }),
+      });
+    }, FAST_DEBOUNCE),
+    [columns, metrics],
+  );
 
   useEffect(() => {
     setList({
@@ -245,93 +258,121 @@ export default function DataSourcePanel({
     setInputValue('');
   }, [columns, datasource, metrics]);
 
-  const metricSlice = showAllMetrics
-    ? lists.metrics
-    : lists.metrics.slice(0, DEFAULT_MAX_COLUMNS_LENGTH);
-  const columnSlice = showAllColumns
-    ? lists.columns
-    : lists.columns.slice(0, DEFAULT_MAX_METRICS_LENGTH);
+  const metricSlice = useMemo(
+    () =>
+      showAllMetrics
+        ? lists.metrics
+        : lists.metrics.slice(0, DEFAULT_MAX_METRICS_LENGTH),
+    [lists.metrics, showAllMetrics],
+  );
+  const columnSlice = useMemo(
+    () =>
+      showAllColumns
+        ? lists.columns
+        : lists.columns.slice(0, DEFAULT_MAX_COLUMNS_LENGTH),
+    [lists.columns, showAllColumns],
+  );
 
-  const mainBody = (
-    <>
-      <input
-        type="text"
-        onChange={evt => {
-          setInputValue(evt.target.value);
-          search(evt.target.value);
-        }}
-        value={inputValue}
-        className="form-control input-md"
-        placeholder={t('Search Metrics & Columns')}
-      />
-      <div className="field-selections">
-        <Collapse
-          bordered
-          defaultActiveKey={['metrics', 'column']}
-          expandIconPosition="right"
-          ghost
-        >
-          <Collapse.Panel
-            header={<span className="header">{t('Metrics')}</span>}
-            key="metrics"
+  const mainBody = useMemo(
+    () => (
+      <>
+        <Input
+          allowClear
+          onChange={evt => {
+            setInputValue(evt.target.value);
+            search(evt.target.value);
+          }}
+          value={inputValue}
+          className="form-control input-md"
+          placeholder={t('Search Metrics & Columns')}
+        />
+        <div className="field-selections">
+          <Collapse
+            bordered
+            defaultActiveKey={['metrics', 'column']}
+            expandIconPosition="right"
+            ghost
           >
-            <div className="field-length">
-              {t(`Showing %s of %s`, metricSlice.length, lists.metrics.length)}
-            </div>
-            {metricSlice.map(m => (
-              <LabelContainer key={m.metric_name} className="column">
-                {enableExploreDnd ? (
-                  <DatasourcePanelDragOption
-                    value={m}
-                    type={DndItemType.Metric}
-                  />
-                ) : (
-                  <StyledMetricOption metric={m} showType />
+            <Collapse.Panel
+              header={<span className="header">{t('Metrics')}</span>}
+              key="metrics"
+            >
+              <div className="field-length">
+                {t(
+                  `Showing %s of %s`,
+                  metricSlice.length,
+                  lists.metrics.length,
                 )}
-              </LabelContainer>
-            ))}
-            {lists.metrics.length > DEFAULT_MAX_METRICS_LENGTH ? (
-              <ButtonContainer>
-                <Button onClick={() => setShowAllMetrics(!showAllMetrics)}>
-                  {showAllMetrics ? t('Show less...') : t('Show all...')}
-                </Button>
-              </ButtonContainer>
-            ) : (
-              <></>
-            )}
-          </Collapse.Panel>
-          <Collapse.Panel
-            header={<span className="header">{t('Columns')}</span>}
-            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} className="column">
-                {enableExploreDnd ? (
-                  <DatasourcePanelDragOption
-                    value={col}
-                    type={DndItemType.Column}
-                  />
-                ) : (
-                  <StyledColumnOption column={col} showType />
+              </div>
+              {metricSlice.map(m => (
+                <LabelContainer key={m.metric_name} className="column">
+                  {enableExploreDnd ? (
+                    <DatasourcePanelDragOption
+                      value={m}
+                      type={DndItemType.Metric}
+                    />
+                  ) : (
+                    <StyledMetricOption metric={m} showType />
+                  )}
+                </LabelContainer>
+              ))}
+              {lists.metrics.length > DEFAULT_MAX_METRICS_LENGTH ? (
+                <ButtonContainer>
+                  <Button onClick={() => setShowAllMetrics(!showAllMetrics)}>
+                    {showAllMetrics ? t('Show less...') : t('Show all...')}
+                  </Button>
+                </ButtonContainer>
+              ) : (
+                <></>
+              )}
+            </Collapse.Panel>
+            <Collapse.Panel
+              header={<span className="header">{t('Columns')}</span>}
+              key="column"
+            >
+              <div className="field-length">
+                {t(
+                  `Showing %s of %s`,
+                  columnSlice.length,
+                  lists.columns.length,
                 )}
-              </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>
-      </div>
-    </>
+              </div>
+              {columnSlice.map(col => (
+                <LabelContainer key={col.column_name} className="column">
+                  {enableExploreDnd ? (
+                    <DatasourcePanelDragOption
+                      value={col}
+                      type={DndItemType.Column}
+                    />
+                  ) : (
+                    <StyledColumnOption column={col} showType />
+                  )}
+                </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>
+        </div>
+      </>
+    ),
+    [
+      columnSlice,
+      inputValue,
+      lists.columns.length,
+      lists.metrics.length,
+      metricSlice,
+      search,
+      showAllColumns,
+      showAllMetrics,
+    ],
   );
 
   return (