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

tai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 92f2353  feat: filters for database list view (#10772)
92f2353 is described below

commit 92f2353f80568c419d1cd183e54b2115479b80a8
Author: ʈᵃᵢ <[email protected]>
AuthorDate: Fri Sep 4 16:23:38 2020 -0700

    feat: filters for database list view (#10772)
---
 .../components/ListView/ListView_spec.jsx          | 11 ++++++
 .../views/CRUD/data/database/DatabaseList_spec.jsx | 29 +++++++++++++++
 .../src/components/ListView/Filters.tsx            | 18 +++++++---
 .../src/components/ListView/ListView.tsx           | 12 ++++++-
 superset-frontend/src/components/ListView/types.ts |  4 ++-
 superset-frontend/src/components/ListView/utils.ts | 24 ++++++++-----
 superset-frontend/src/components/SearchInput.tsx   |  7 ++--
 .../src/views/CRUD/data/database/DatabaseList.tsx  | 42 +++++++++++++++++++++-
 8 files changed, 128 insertions(+), 19 deletions(-)

diff --git 
a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx 
b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
index a60f5ed..120b349 100644
--- a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
+++ b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
@@ -23,6 +23,7 @@ import { QueryParamProvider } from 'use-query-params';
 import { supersetTheme, ThemeProvider } from '@superset-ui/style';
 
 import Button from 'src/components/Button';
+import { Empty } from 'src/common/components';
 import CardCollection from 'src/components/ListView/CardCollection';
 import { CardSortSelect } from 'src/components/ListView/CardSortSelect';
 import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
@@ -346,6 +347,16 @@ describe('ListView', () => {
     );
   });
 
+  it('renders and empty state when there is no data', () => {
+    const props = {
+      ...mockedProps,
+      data: [],
+    };
+
+    const wrapper2 = factory(props);
+    expect(wrapper2.find(Empty)).toExist();
+  });
+
   it('renders UI filters', () => {
     expect(wrapper.find(ListViewFilters)).toExist();
   });
diff --git 
a/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseList_spec.jsx
 
b/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseList_spec.jsx
index 25f0fbd..9c3ea6a 100644
--- 
a/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseList_spec.jsx
+++ 
b/superset-frontend/spec/javascripts/views/CRUD/data/database/DatabaseList_spec.jsx
@@ -26,6 +26,7 @@ import DatabaseList from 
'src/views/CRUD/data/database/DatabaseList';
 import DatabaseModal from 'src/views/CRUD/data/database/DatabaseModal';
 import SubMenu from 'src/components/Menu/SubMenu';
 import ListView from 'src/components/ListView';
+import Filters from 'src/components/ListView/Filters';
 import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
 import { act } from 'react-dom/test-utils';
 
@@ -116,4 +117,32 @@ describe('DatabaseList', () => {
 
     expect(fetchMock.calls(/database\/0/, 'DELETE')).toHaveLength(1);
   });
+
+  it('filters', async () => {
+    const filtersWrapper = wrapper.find(Filters);
+    act(() => {
+      filtersWrapper
+        .find('[name="expose_in_sqllab"]')
+        .first()
+        .props()
+        .onSelect(true);
+
+      filtersWrapper
+        .find('[name="allow_run_async"]')
+        .first()
+        .props()
+        .onSelect(false);
+
+      filtersWrapper
+        .find('[name="database_name"]')
+        .first()
+        .props()
+        .onSubmit('fooo');
+    });
+    await waitForComponentToPaint(wrapper);
+
+    expect(fetchMock.lastCall()[0]).toMatchInlineSnapshot(
+      
`"http://localhost/api/v1/database/?q=(filters:!((col:expose_in_sqllab,opr:eq,value:!t),(col:allow_run_async,opr:eq,value:!f),(col:database_name,opr:ct,value:fooo)),order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
+    );
+  });
 });
diff --git a/superset-frontend/src/components/ListView/Filters.tsx 
b/superset-frontend/src/components/ListView/Filters.tsx
index a205031..370e3dc 100644
--- a/superset-frontend/src/components/ListView/Filters.tsx
+++ b/superset-frontend/src/components/ListView/Filters.tsx
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { useState } from 'react';
+import React, { useState, ReactNode } from 'react';
 import { styled, withTheme, SupersetThemeProps } from '@superset-ui/style';
 
 import {
@@ -36,7 +36,7 @@ import {
 import { filterSelectStyles } from './utils';
 
 interface BaseFilter {
-  Header: string;
+  Header: ReactNode;
   initialValue: any;
 }
 interface SelectFilterProps extends BaseFilter {
@@ -130,7 +130,7 @@ function SelectFilter({
 
   return (
     <FilterContainer>
-      <FilterTitle>{Header}</FilterTitle>
+      <FilterTitle>{Header}:</FilterTitle>
       {fetchSelects ? (
         <PaginatedSelect
           data-test="filters-select"
@@ -168,9 +168,15 @@ const StyledSelectFilter = withTheme(SelectFilter);
 interface SearchHeaderProps extends BaseFilter {
   Header: string;
   onSubmit: (val: string) => void;
+  name: string;
 }
 
-function SearchFilter({ Header, initialValue, onSubmit }: SearchHeaderProps) {
+function SearchFilter({
+  Header,
+  name,
+  initialValue,
+  onSubmit,
+}: SearchHeaderProps) {
   const [value, setValue] = useState(initialValue || '');
   const handleSubmit = () => onSubmit(value);
   const onClear = () => {
@@ -183,6 +189,7 @@ function SearchFilter({ Header, initialValue, onSubmit }: 
SearchHeaderProps) {
       <SearchInput
         data-test="filters-search"
         placeholder={Header}
+        name={name}
         value={value}
         onChange={e => {
           setValue(e.currentTarget.value);
@@ -244,12 +251,13 @@ function UIFilters({
               />
             );
           }
-          if (input === 'search') {
+          if (input === 'search' && typeof Header === 'string') {
             return (
               <SearchFilter
                 Header={Header}
                 initialValue={initialValue}
                 key={id}
+                name={id}
                 onSubmit={(value: string) => updateFilterValue(index, value)}
               />
             );
diff --git a/superset-frontend/src/components/ListView/ListView.tsx 
b/superset-frontend/src/components/ListView/ListView.tsx
index 54fdeae..cafec4b 100644
--- a/superset-frontend/src/components/ListView/ListView.tsx
+++ b/superset-frontend/src/components/ListView/ListView.tsx
@@ -16,9 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { t } from '@superset-ui/translation';
 import React, { useState } from 'react';
+import { t } from '@superset-ui/translation';
 import { Alert } from 'react-bootstrap';
+import { Empty } from 'src/common/components';
 import styled from '@superset-ui/style';
 import cx from 'classnames';
 import Button from 'src/components/Button';
@@ -140,6 +141,10 @@ const ViewModeContainer = styled.div`
   }
 `;
 
+const EmptyWrapper = styled.div`
+  margin: ${({ theme }) => theme.gridUnit * 40}px 0;
+`;
+
 const ViewModeToggle = ({
   mode,
   setMode,
@@ -348,6 +353,11 @@ function ListView<T extends object = any>({
               loading={loading}
             />
           )}
+          {!loading && rows.length === 0 && (
+            <EmptyWrapper>
+              <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
+            </EmptyWrapper>
+          )}
         </div>
       </div>
 
diff --git a/superset-frontend/src/components/ListView/types.ts 
b/superset-frontend/src/components/ListView/types.ts
index 1161a88..2efb5f0 100644
--- a/superset-frontend/src/components/ListView/types.ts
+++ b/superset-frontend/src/components/ListView/types.ts
@@ -16,6 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { ReactNode } from 'react';
+
 export interface SortColumn {
   id: string;
   desc?: boolean;
@@ -36,7 +38,7 @@ export interface CardSortSelectOption {
 }
 
 export interface Filter {
-  Header: string;
+  Header: ReactNode;
   id: string;
   operators?: SelectOption[];
   operator?:
diff --git a/superset-frontend/src/components/ListView/utils.ts 
b/superset-frontend/src/components/ListView/utils.ts
index 026d9e7..c8ecc95 100644
--- a/superset-frontend/src/components/ListView/utils.ts
+++ b/superset-frontend/src/components/ListView/utils.ts
@@ -227,15 +227,21 @@ export function useListViewState({
   }, [query]);
 
   const applyFilterValue = (index: number, value: any) => {
-    // skip redunundant updates
-    if (internalFilters[index].value === value) {
-      return;
-    }
-    const update = { ...internalFilters[index], value };
-    const updatedFilters = updateInList(internalFilters, index, update);
-    setInternalFilters(updatedFilters);
-    setAllFilters(convertFilters(updatedFilters));
-    gotoPage(0); // clear pagination on filter
+    setInternalFilters(currentInternalFilters => {
+      // skip redunundant updates
+      if (currentInternalFilters[index].value === value) {
+        return currentInternalFilters;
+      }
+      const update = { ...currentInternalFilters[index], value };
+      const updatedFilters = updateInList(
+        currentInternalFilters,
+        index,
+        update,
+      );
+      setAllFilters(convertFilters(updatedFilters));
+      gotoPage(0); // clear pagination on filter
+      return updatedFilters;
+    });
   };
 
   return {
diff --git a/superset-frontend/src/components/SearchInput.tsx 
b/superset-frontend/src/components/SearchInput.tsx
index 314c9a4..2d00775 100644
--- a/superset-frontend/src/components/SearchInput.tsx
+++ b/superset-frontend/src/components/SearchInput.tsx
@@ -20,12 +20,13 @@ import styled from '@superset-ui/style';
 import React from 'react';
 import Icon from 'src/components/Icon';
 
-interface Props {
+interface SearchInputProps {
   onSubmit: () => void;
   onClear: () => void;
   value: string;
   onChange: React.EventHandler<React.ChangeEvent<HTMLInputElement>>;
   placeholder?: string;
+  name?: string;
 }
 
 const SearchInputWrapper = styled.div`
@@ -68,8 +69,9 @@ export default function SearchInput({
   onClear,
   onSubmit,
   placeholder = 'Search',
+  name,
   value,
-}: Props) {
+}: SearchInputProps) {
   return (
     <SearchInputWrapper>
       <SearchIcon
@@ -89,6 +91,7 @@ export default function SearchInput({
         placeholder={placeholder}
         onChange={onChange}
         value={value}
+        name={name}
       />
       {value && (
         <ClearIcon
diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx 
b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx
index 646ed37..a852619 100644
--- a/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx
+++ b/superset-frontend/src/views/CRUD/data/database/DatabaseList.tsx
@@ -245,7 +245,47 @@ function DatabaseList({ addDangerToast, addSuccessToast }: 
DatabaseListProps) {
     [canDelete, canCreate],
   );
 
-  const filters: Filters = [];
+  const filters: Filters = useMemo(
+    () => [
+      {
+        Header: t('Expose in SQL Lab'),
+        id: 'expose_in_sqllab',
+        input: 'select',
+        operator: 'eq',
+        unfilteredLabel: 'All',
+        selects: [
+          { label: 'Yes', value: true },
+          { label: 'No', value: false },
+        ],
+      },
+      {
+        Header: (
+          <TooltipWrapper
+            label="allow-run-async-filter-header"
+            tooltip={t('Asynchronous Query Execution')}
+            placement="top"
+          >
+            <span>{t('AQE')}</span>
+          </TooltipWrapper>
+        ),
+        id: 'allow_run_async',
+        input: 'select',
+        operator: 'eq',
+        unfilteredLabel: 'All',
+        selects: [
+          { label: 'Yes', value: true },
+          { label: 'No', value: false },
+        ],
+      },
+      {
+        Header: t('Search'),
+        id: 'database_name',
+        input: 'search',
+        operator: 'ct',
+      },
+    ],
+    [],
+  );
 
   return (
     <>

Reply via email to