[datasets] new, listview (react) (#9197)

* [datasets] new, react listview

* add hidden columns to support filtering by columns not rendered

* throw exception if config is incorrect

* fix database filter

* update endpoints to point to datasets; fix translation strings

* move Link into src/components

* add add new record button to datasets listview
This commit is contained in:
ʈᵃᵢ
2020-03-13 12:35:00 -07:00
committed by GitHub
parent f80fadff0e
commit 5767fb15cd
21 changed files with 766 additions and 128 deletions

View File

@@ -17,11 +17,12 @@
* under the License.
*/
import React from 'react';
import { mount } from 'enzyme';
import { mount, shallow } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { MenuItem, Pagination } from 'react-bootstrap';
import ListView from 'src/components/ListView/ListView';
import { areArraysShallowEqual } from 'src/reduxUtils';
describe('ListView', () => {
const mockedProps = {
@@ -53,10 +54,6 @@ describe('ListView', () => {
pageSize: 1,
fetchData: jest.fn(() => []),
loading: false,
filterTypes: {
id: [],
name: [{ name: 'sw', label: 'Starts With' }],
},
bulkActions: [{ name: 'do something', onSelect: jest.fn() }],
};
const wrapper = mount(<ListView {...mockedProps} />);
@@ -71,15 +68,15 @@ describe('ListView', () => {
it('calls fetchData on mount', () => {
expect(wrapper.find(ListView)).toHaveLength(1);
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Array [],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [],
},
]
`);
Array [
Object {
"filters": Array [],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [],
},
]
`);
});
it('calls fetchData on sort', () => {
@@ -90,20 +87,20 @@ describe('ListView', () => {
expect(mockedProps.fetchData).toHaveBeenCalled();
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Array [],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
Array [
Object {
"filters": Array [],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
});
it('calls fetchData on filter', () => {
@@ -140,27 +137,27 @@ describe('ListView', () => {
wrapper.update();
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Array [
Object {
"Header": "name",
"id": "name",
"operator": "sw",
"value": "foo",
},
],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
Array [
Object {
"filters": Array [
Object {
"Header": "name",
"id": "name",
"operator": "sw",
"value": "foo",
},
],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
});
it('calls fetchData on page change', () => {
@@ -170,27 +167,27 @@ describe('ListView', () => {
wrapper.update();
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Array [
Object {
"Header": "name",
"id": "name",
"operator": "sw",
"value": "foo",
},
],
"pageIndex": 1,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
Array [
Object {
"filters": Array [
Object {
"Header": "name",
"id": "name",
"operator": "sw",
"value": "foo",
},
],
"pageIndex": 1,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
});
it('handles bulk actions on 1 row', () => {
act(() => {
@@ -212,6 +209,39 @@ describe('ListView', () => {
.last()
.props();
bulkActionsProps.onSelect(bulkActionsProps.eventKey);
expect(mockedProps.bulkActions[0].onSelect.mock.calls[0])
.toMatchInlineSnapshot(`
Array [
Array [
Object {
"id": 1,
"name": "data 1",
},
],
]
`);
});
it('handles bulk actions on all rows', () => {
act(() => {
wrapper
.find('input[title="Toggle All Rows Selected"]')
.at(0)
.prop('onChange')({ target: { value: 'on' } });
wrapper
.find('.dropdown-toggle')
.children('button')
.at(1)
.props()
.onClick();
});
wrapper.update();
const bulkActionsProps = wrapper
.find(MenuItem)
.last()
.props();
bulkActionsProps.onSelect(bulkActionsProps.eventKey);
expect(mockedProps.bulkActions[0].onSelect.mock.calls[0])
.toMatchInlineSnapshot(`
@@ -221,45 +251,26 @@ describe('ListView', () => {
"id": 1,
"name": "data 1",
},
Object {
"id": 2,
"name": "data 2",
},
],
]
`);
});
it('handles bulk actions on all rows', () => {
act(() => {
wrapper
.find('input[title="Toggle All Rows Selected"]')
.at(0)
.prop('onChange')({ target: { value: 'on' } });
wrapper
.find('.dropdown-toggle')
.children('button')
.at(1)
.props()
.onClick();
});
wrapper.update();
const bulkActionsProps = wrapper
.find(MenuItem)
.last()
.props();
bulkActionsProps.onSelect(bulkActionsProps.eventKey);
expect(mockedProps.bulkActions[0].onSelect.mock.calls[0])
.toMatchInlineSnapshot(`
Array [
Array [
Object {
"id": 1,
"name": "data 1",
},
Object {
"id": 2,
"name": "data 2",
},
],
]
`);
it('Throws an exception if filter missing in columns', () => {
expect.assertions(1);
const props = {
...mockedProps,
filters: [...mockedProps.filters, { id: 'some_column' }],
};
try {
shallow(<ListView {...props} />);
} catch (e) {
expect(e).toMatchInlineSnapshot(
`[ListViewError: Invalid filter config, some_column is not present in columns]`,
);
}
});
});

View File

@@ -19,7 +19,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import Link from '../../../src/SqlLab/components/Link';
import Link from '../../../src/components/Link';
describe('Link', () => {
const mockedProps = {

View File

@@ -19,7 +19,7 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import Link from '../../../src/SqlLab/components/Link';
import Link from '../../../src/components/Link';
import TableElement from '../../../src/SqlLab/components/TableElement';
import ColumnElement from '../../../src/SqlLab/components/ColumnElement';
import { mockedActions, table } from './fixtures';

View File

@@ -30,6 +30,7 @@ const mockStore = configureStore([thunk]);
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 mockCharts = [...new Array(3)].map((_, i) => ({
@@ -43,7 +44,16 @@ const mockCharts = [...new Array(3)].map((_, i) => ({
fetchMock.get(chartsInfoEndpoint, {
permissions: ['can_list', 'can_edit'],
filters: [],
filters: {
slice_name: [],
description: [],
viz_type: [],
datasource_name: [],
owners: [],
},
});
fetchMock.get(chartssOwnersEndpoint, {
result: [],
});
fetchMock.get(chartsEndpoint, {
result: mockCharts,
@@ -69,6 +79,11 @@ describe('ChartList', () => {
expect(callsI).toHaveLength(1);
});
it('fetches owners', () => {
const callsO = fetchMock.calls(/chart\/related\/owners/);
expect(callsO).toHaveLength(1);
});
it('fetches data', () => {
wrapper.update();
const callsD = fetchMock.calls(/chart\/\?q/);

View File

@@ -30,6 +30,7 @@ const mockStore = configureStore([thunk]);
const store = mockStore({});
const dashboardsInfoEndpoint = 'glob:*/api/v1/dashboard/_info*';
const dashboardOwnersEndpoint = 'glob:*/api/v1/dashboard/related/owners*';
const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*';
const mockDashboards = [...new Array(3)].map((_, i) => ({
@@ -45,7 +46,15 @@ const mockDashboards = [...new Array(3)].map((_, i) => ({
fetchMock.get(dashboardsInfoEndpoint, {
permissions: ['can_list', 'can_edit'],
filters: [],
filters: {
dashboard_title: [],
slug: [],
owners: [],
published: [],
},
});
fetchMock.get(dashboardOwnersEndpoint, {
result: [],
});
fetchMock.get(dashboardsEndpoint, {
result: mockDashboards,
@@ -71,6 +80,11 @@ describe('DashboardList', () => {
expect(callsI).toHaveLength(1);
});
it('fetches owners', () => {
const callsO = fetchMock.calls(/dashboard\/related\/owners/);
expect(callsO).toHaveLength(1);
});
it('fetches data', () => {
wrapper.update();
const callsD = fetchMock.calls(/dashboard\/\?q/);

View File

@@ -0,0 +1,98 @@
/**
* 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 { mount } from 'enzyme';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
import DatasetList from 'src/views/datasetList/DatasetList';
import ListView from 'src/components/ListView/ListView';
// store needed for withToasts(datasetTable)
const mockStore = configureStore([thunk]);
const store = mockStore({});
const datasetsInfoEndpoint = 'glob:*/api/v1/dataset/_info*';
const datasetsOwnersEndpoint = 'glob:*/api/v1/dataset/related/owners*';
const datasetsEndpoint = 'glob:*/api/v1/dataset/?*';
const mockdatasets = [...new Array(3)].map((_, i) => ({
changed_by_name: 'user',
changed_by_url: 'changed_by_url',
changed_by: 'user',
changed_on: new Date().toISOString(),
database_name: `db ${i}`,
explore_url: `/explore/table/${i}`,
id: i,
schema: `schema ${i}`,
table_name: `coolest table ${i}`,
}));
fetchMock.get(datasetsInfoEndpoint, {
permissions: ['can_list', 'can_edit'],
filters: {
database: [],
schema: [],
table_name: [],
owners: [],
is_sqllab_view: [],
},
});
fetchMock.get(datasetsOwnersEndpoint, {
result: [],
});
fetchMock.get(datasetsEndpoint, {
result: mockdatasets,
dataset_count: 3,
});
describe('DatasetList', () => {
const mockedProps = {};
const wrapper = mount(<DatasetList {...mockedProps} />, {
context: { store },
});
it('renders', () => {
expect(wrapper.find(DatasetList)).toHaveLength(1);
});
it('renders a ListView', () => {
expect(wrapper.find(ListView)).toHaveLength(1);
});
it('fetches info', () => {
const callsI = fetchMock.calls(/dataset\/_info/);
expect(callsI).toHaveLength(1);
});
it('fetches owners', () => {
const callsO = fetchMock.calls(/dataset\/related\/owners/);
expect(callsO).toHaveLength(1);
});
it('fetches data', () => {
wrapper.update();
const callsD = fetchMock.calls(/dataset\/\?q/);
expect(callsD).toHaveLength(1);
expect(callsD[0][0]).toMatchInlineSnapshot(
`"/http//localhost/api/v1/dataset/?q={%22order_column%22:%22changed_on%22,%22order_direction%22:%22desc%22,%22page%22:0,%22page_size%22:25}"`,
);
});
});