diff --git a/superset/assets/spec/javascripts/components/ListView/ListView_spec.jsx b/superset/assets/spec/javascripts/components/ListView/ListView_spec.jsx index b69ef26b9ef93..79ddbd98c4153 100644 --- a/superset/assets/spec/javascripts/components/ListView/ListView_spec.jsx +++ b/superset/assets/spec/javascripts/components/ListView/ListView_spec.jsx @@ -18,30 +18,154 @@ */ import React from 'react'; import { mount } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { MenuItem, Pagination } from 'react-bootstrap'; import ListView from 'src/components/ListView/ListView'; describe('ListView', () => { const mockedProps = { + title: 'Data Table', columns: [ { accessor: 'id', Header: 'ID', + sortable: true, + }, + { + accessor: 'name', + Header: 'Name', + filterable: true, }, ], - data: [], - count: 0, + data: [ + { id: 1, name: 'data 1' }, + { id: 2, name: 'data 2' }, + ], + count: 2, pageSize: 1, fetchData: jest.fn(() => []), loading: false, }; const wrapper = mount(); - it('renders', () => { - expect(wrapper.find(ListView)).toHaveLength(1); + afterEach(() => { + mockedProps.fetchData.mockClear(); }); it('calls fetchData on mount', () => { - expect(mockedProps.fetchData).toHaveBeenCalled(); + expect(wrapper.find(ListView)).toHaveLength(1); + expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "filters": Object {}, + "pageIndex": 0, + "pageSize": 1, + "sortBy": Array [], + }, + ] + `); + }); + + it('calls fetchData on sort', () => { + wrapper + .find('[data-test="sort-header"]') + .first() + .simulate('click'); + expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "filters": Object {}, + "pageIndex": 0, + "pageSize": 1, + "sortBy": Array [ + Object { + "desc": false, + "id": "id", + }, + ], + }, + ] + `); + }); + + it('calls fetchData on filter', () => { + act(() => { + wrapper + .find('.dropdown-toggle') + .children('button') + .props() + .onClick(); + + wrapper + .find(MenuItem) + .props() + .onSelect({ id: 'name', Header: 'name' }); + }); + wrapper.update(); + + act(() => { + wrapper.find('.filter-inputs input[type="text"]').prop('onChange')({ + currentTarget: { value: 'foo' }, + }); + }); + wrapper.update(); + + act(() => { + wrapper + .find('[data-test="apply-filters"]') + .last() + .prop('onClick')(); + }); + wrapper.update(); + + expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "filters": Object { + "name": Object { + "filterId": "sw", + "filterValue": "foo", + }, + }, + "pageIndex": 0, + "pageSize": 1, + "sortBy": Array [ + Object { + "desc": false, + "id": "id", + }, + ], + }, + ] + `); + }); + + it('calls fetchData on page change', () => { + act(() => { + wrapper.find(Pagination).prop('onSelect')(2); + }); + wrapper.update(); + + expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "filters": Object { + "name": Object { + "filterId": "sw", + "filterValue": "foo", + }, + }, + "pageIndex": 1, + "pageSize": 1, + "sortBy": Array [ + Object { + "desc": false, + "id": "id", + }, + ], + }, + ] + `); }); }); diff --git a/superset/assets/spec/javascripts/views/dashboardList/DashboardList_spec.jsx b/superset/assets/spec/javascripts/views/dashboardList/DashboardList_spec.jsx index 9eeb52a98b48b..51026e64812c8 100644 --- a/superset/assets/spec/javascripts/views/dashboardList/DashboardList_spec.jsx +++ b/superset/assets/spec/javascripts/views/dashboardList/DashboardList_spec.jsx @@ -32,7 +32,7 @@ const store = mockStore({}); const dashboardsInfoEndpoint = 'glob:*/api/v1/dashboard/_info*'; const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*'; -const mockDashboards = [...new Array(3)].map(i => ({ +const mockDashboards = [...new Array(3)].map((_, i) => ({ id: i, url: 'url', dashboard_title: `title ${i}`, @@ -53,7 +53,6 @@ fetchMock.get(dashboardsEndpoint, { }); describe('DashboardList', () => { - beforeEach(fetchMock.resetHistory); const mockedProps = {}; const wrapper = mount(, { context: { store }, @@ -66,4 +65,18 @@ describe('DashboardList', () => { it('renders a ListView', () => { expect(wrapper.find(ListView)).toHaveLength(1); }); + + it('fetches info', () => { + const callsI = fetchMock.calls(/dashboard\/_info/); + expect(callsI).toHaveLength(1); + }); + + it('fetches data', () => { + wrapper.update(); + const callsD = fetchMock.calls(/dashboard\/\?q/); + expect(callsD).toHaveLength(1); + expect(callsD[0][0]).toMatchInlineSnapshot( + `"/http//localhost/api/v1/dashboard/?q={%22order_column%22:%22changed_on%22,%22order_direction%22:%22desc%22,%22page%22:0,%22page_size%22:25}"`, + ); + }); }); diff --git a/superset/assets/src/components/ListView/ListView.tsx b/superset/assets/src/components/ListView/ListView.tsx index 11a86e3b967ee..fe9a78ca8c952 100644 --- a/superset/assets/src/components/ListView/ListView.tsx +++ b/superset/assets/src/components/ListView/ListView.tsx @@ -17,7 +17,7 @@ * under the License. */ import { t } from '@superset-ui/translation'; -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useMemo } from 'react'; import { Button, Col, @@ -44,7 +44,6 @@ interface Props { className?: string; title?: string; initialSort?: SortColumn[]; - filterable?: boolean; filterTypes?: FilterTypeMap; } @@ -58,7 +57,6 @@ const ListView: FunctionComponent = ({ initialSort = [], className = '', title = '', - filterable = false, filterTypes = {}, }) => { const { @@ -85,7 +83,8 @@ const ListView: FunctionComponent = ({ initialPageSize, initialSort, }); - const filterableColumns = columns.filter((c) => c.filterable); + const filterableColumns = useMemo(() => columns.filter((c) => c.filterable), [columns]); + const filterable = Boolean(columns.length); const removeFilterAndApply = (index: number) => { const updated = removeFromList(filterToggles, index); @@ -125,15 +124,16 @@ const ListView: FunctionComponent = ({ Header, id: id || accessor, })) - .map((filter: FilterToggle) => ( + .map((ft: FilterToggle) => ( - setFilterToggles([...filterToggles, fltr]) + key={ft.id} + eventKey={ft} + onSelect={(fltr: FilterToggle) => { + setFilterToggles([...filterToggles, fltr]); + } } > - {filter.Header} + {ft.Header} ))} @@ -143,7 +143,7 @@ const ListView: FunctionComponent = ({ {filterToggles.map((ft, i) => ( - + {ft.Header} @@ -193,28 +193,28 @@ const ListView: FunctionComponent = ({ ))} - {filterToggles.length > 0 && ( + {filterToggles.length && ( <> - {filterToggles.length > 0 && ( - - Apply - - )} + + Apply + - {' '} + > )} - )} + ) + } = ({ of {count} - + ); }; diff --git a/superset/assets/src/components/ListView/TableCollection.tsx b/superset/assets/src/components/ListView/TableCollection.tsx index f3a89ec896c8a..f914b2bb92b5f 100644 --- a/superset/assets/src/components/ListView/TableCollection.tsx +++ b/superset/assets/src/components/ListView/TableCollection.tsx @@ -42,7 +42,7 @@ export default function TableCollection({ {headerGroups.map((headerGroup) => ( {headerGroup.headers.map((column: any) => ( - + {column.render('Header')} {' '} {column.sortable && ( diff --git a/superset/assets/src/components/ListView/utils.ts b/superset/assets/src/components/ListView/utils.ts index e2c71dd3f0851..2fe136ede4d1a 100644 --- a/superset/assets/src/components/ListView/utils.ts +++ b/superset/assets/src/components/ListView/utils.ts @@ -53,11 +53,11 @@ function updateInList(list: any[], index: number, update: any): any[] { // convert filters from UI objects to data objects export function convertFilters(fts: FilterToggle[]) { return fts - .filter((ft) => ft.filterValue) - .reduce((acc, elem) => { - acc[elem.id] = { - filterId: elem.filterId || 'sw', - filterValue: elem.filterValue, + .filter(ft => ft.filterValue) + .reduce((acc, ft) => { + acc[ft.id] = { + filterId: ft.filterId || 'sw', + filterValue: ft.filterValue, }; return acc; }, {}); diff --git a/superset/assets/src/views/dashboardList/DashboardList.tsx b/superset/assets/src/views/dashboardList/DashboardList.tsx index e12a3516b35df..d0f8eee2ca6b5 100644 --- a/superset/assets/src/views/dashboardList/DashboardList.tsx +++ b/superset/assets/src/views/dashboardList/DashboardList.tsx @@ -254,7 +254,6 @@ class DashboardList extends React.PureComponent { fetchData={this.fetchData} loading={this.state.loading} initialSort={this.initialSort} - filterable={true} filterTypes={this.state.filterTypes} />