diff --git a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
index eba56f1f64da..61aca5f8b695 100644
--- a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
+++ b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx
@@ -35,6 +35,10 @@ const mockedProps = {
Header: 'ID',
sortable: true,
},
+ {
+ accessor: 'age',
+ Header: 'Age',
+ },
{
accessor: 'name',
Header: 'Name',
@@ -287,6 +291,7 @@ Array [
});
describe('ListView with new UI filters', () => {
+ const fetchSelectsMock = jest.fn(() => []);
const newFiltersProps = {
...mockedProps,
useNewUIFilters: true,
@@ -304,6 +309,13 @@ describe('ListView with new UI filters', () => {
input: 'search',
operator: 'ct',
},
+ {
+ Header: 'Age',
+ id: 'age',
+ input: 'select',
+ fetchSelects: fetchSelectsMock,
+ operator: 'eq',
+ },
],
};
@@ -320,11 +332,15 @@ describe('ListView with new UI filters', () => {
expect(wrapper.find(ListViewFilters)).toHaveLength(1);
});
+ it('fetched selects if function is provided', () => {
+ expect(fetchSelectsMock).toHaveBeenCalled();
+ });
+
it('calls fetchData on filter', () => {
act(() => {
wrapper
.find('[data-test="filters-select"]')
- .last()
+ .first()
.props()
.onChange({ value: 'bar' });
});
@@ -332,7 +348,7 @@ describe('ListView with new UI filters', () => {
act(() => {
wrapper
.find('[data-test="filters-search"]')
- .last()
+ .first()
.props()
.onChange({ currentTarget: { value: 'something' } });
});
@@ -348,42 +364,42 @@ describe('ListView with new UI filters', () => {
});
expect(newFiltersProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
- Array [
- Object {
- "filters": Array [
- Object {
- "id": "id",
- "operator": "eq",
- "value": "bar",
- },
- ],
- "pageIndex": 0,
- "pageSize": 1,
- "sortBy": Array [],
- },
- ]
- `);
+Array [
+ Object {
+ "filters": Array [
+ Object {
+ "id": "id",
+ "operator": "eq",
+ "value": "bar",
+ },
+ ],
+ "pageIndex": 0,
+ "pageSize": 1,
+ "sortBy": Array [],
+ },
+]
+`);
expect(newFiltersProps.fetchData.mock.calls[1]).toMatchInlineSnapshot(`
- Array [
- Object {
- "filters": Array [
- Object {
- "id": "id",
- "operator": "eq",
- "value": "bar",
- },
- Object {
- "id": "name",
- "operator": "ct",
- "value": "something",
- },
- ],
- "pageIndex": 0,
- "pageSize": 1,
- "sortBy": Array [],
- },
- ]
- `);
+Array [
+ Object {
+ "filters": Array [
+ Object {
+ "id": "id",
+ "operator": "eq",
+ "value": "bar",
+ },
+ Object {
+ "id": "name",
+ "operator": "ct",
+ "value": "something",
+ },
+ ],
+ "pageIndex": 0,
+ "pageSize": 1,
+ "sortBy": Array [],
+ },
+]
+`);
});
});
diff --git a/superset-frontend/spec/javascripts/views/chartList/ChartList_spec.jsx b/superset-frontend/spec/javascripts/views/chartList/ChartList_spec.jsx
index 60c8ccb1f554..faf0c096d1e1 100644
--- a/superset-frontend/spec/javascripts/views/chartList/ChartList_spec.jsx
+++ b/superset-frontend/spec/javascripts/views/chartList/ChartList_spec.jsx
@@ -32,6 +32,8 @@ const store = mockStore({});
const chartsInfoEndpoint = 'glob:*/api/v1/chart/_info*';
const chartssOwnersEndpoint = 'glob:*/api/v1/chart/related/owners*';
const chartsEndpoint = 'glob:*/api/v1/chart/?*';
+const chartsVizTypesEndpoint = 'glob:*/api/v1/chart/viz_types';
+const chartsDtasourcesEndpoint = 'glob:*/api/v1/chart/datasources';
const mockCharts = [...new Array(3)].map((_, i) => ({
changed_on: new Date().toISOString(),
@@ -40,6 +42,7 @@ const mockCharts = [...new Array(3)].map((_, i) => ({
slice_name: `cool chart ${i}`,
url: 'url',
viz_type: 'bar',
+ datasource_name: `ds${i}`,
}));
fetchMock.get(chartsInfoEndpoint, {
@@ -60,6 +63,16 @@ fetchMock.get(chartsEndpoint, {
chart_count: 3,
});
+fetchMock.get(chartsVizTypesEndpoint, {
+ result: [],
+ count: 0,
+});
+
+fetchMock.get(chartsDtasourcesEndpoint, {
+ result: [],
+ count: 0,
+});
+
describe('ChartList', () => {
const mockedProps = {};
const wrapper = mount(, {
diff --git a/superset-frontend/src/components/ListView/Filters.tsx b/superset-frontend/src/components/ListView/Filters.tsx
index 69d35a1440cc..25b2c5b0bd9d 100644
--- a/superset-frontend/src/components/ListView/Filters.tsx
+++ b/superset-frontend/src/components/ListView/Filters.tsx
@@ -20,7 +20,7 @@ import React, { useState } from 'react';
import styled from '@emotion/styled';
import { withTheme } from 'emotion-theming';
-import StyledSelect from 'src/components/StyledSelect';
+import StyledSelect, { AsyncStyledSelect } from 'src/components/StyledSelect';
import SearchInput from 'src/components/SearchInput';
import { Filter, Filters, FilterValue, InternalFilter } from './types';
@@ -32,6 +32,7 @@ interface SelectFilterProps extends BaseFilter {
onSelect: (selected: any) => any;
selects: Filter['selects'];
emptyLabel?: string;
+ fetchSelects?: Filter['fetchSelects'];
}
const FilterContainer = styled.div`
@@ -51,11 +52,13 @@ function SelectFilter({
emptyLabel = 'None',
initialValue,
onSelect,
+ fetchSelects,
}: SelectFilterProps) {
const clearFilterSelect = {
label: emptyLabel,
value: CLEAR_SELECT_FILTER_VALUE,
};
+
const options = React.useMemo(() => [clearFilterSelect, ...selects], [
emptyLabel,
selects,
@@ -73,17 +76,34 @@ function SelectFilter({
selected.value === CLEAR_SELECT_FILTER_VALUE ? undefined : selected.value,
);
};
+ const fetchAndFormatSelects = async () => {
+ if (!fetchSelects) return { options: [clearFilterSelect] };
+ const selectValues = await fetchSelects();
+ return { options: [clearFilterSelect, ...selectValues] };
+ };
return (
{Header}:
-
+ {fetchSelects ? (
+
+ ) : (
+
+ )}
);
}
@@ -134,33 +154,36 @@ function UIFilters({
}: UIFiltersProps) {
return (
- {filters.map(({ Header, input, selects, unfilteredLabel }, index) => {
- const initialValue =
- internalFilters[index] && internalFilters[index].value;
- if (input === 'select') {
- return (
- updateFilterValue(index, value)}
- />
- );
- }
- if (input === 'search') {
- return (
- updateFilterValue(index, value)}
- />
- );
- }
- return null;
- })}
+ {filters.map(
+ ({ Header, input, selects, unfilteredLabel, fetchSelects }, index) => {
+ const initialValue =
+ internalFilters[index] && internalFilters[index].value;
+ if (input === 'select') {
+ return (
+ updateFilterValue(index, value)}
+ />
+ );
+ }
+ if (input === 'search') {
+ return (
+ updateFilterValue(index, value)}
+ />
+ );
+ }
+ return null;
+ },
+ )}
);
}
diff --git a/superset-frontend/src/components/ListView/types.ts b/superset-frontend/src/components/ListView/types.ts
index de85949a2f01..76acae3b7a3e 100644
--- a/superset-frontend/src/components/ListView/types.ts
+++ b/superset-frontend/src/components/ListView/types.ts
@@ -36,6 +36,8 @@ export interface Filter {
input?: 'text' | 'textarea' | 'select' | 'checkbox' | 'search';
unfilteredLabel?: string;
selects?: Select[];
+ onFilterOpen?: () => void;
+ fetchSelects?: () => Promise