Skip to content

Commit

Permalink
[dashboard] new, bulk actions for delete & export (#8979)
Browse files Browse the repository at this point in the history
* bulk actions for dashboards list view

* add confirm component

* finish bulk actions work

* remove loading component

* fix sortby double render bug, lint, fix specs

* adds spec for bulk actions

* fix spec

* spec ConfirmStatusChange

* lint

* tslint

* address review feedback

* tslint fixes

* guard against empty filterTypes

* persist dom events

* tslint
  • Loading branch information
nytai authored and mistercrunch committed Jan 27, 2020
1 parent aecc82e commit a0cda32
Show file tree
Hide file tree
Showing 15 changed files with 822 additions and 539 deletions.
15 changes: 12 additions & 3 deletions superset/assets/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion superset/assets/package.json
Expand Up @@ -136,7 +136,7 @@
"react-split": "^2.0.4",
"react-sticky": "^6.0.2",
"react-syntax-highlighter": "^7.0.4",
"react-table": "^7.0.0-beta.26",
"react-table": "^7.0.0-rc.15",
"react-transition-group": "^2.5.3",
"react-virtualized": "9.19.1",
"react-virtualized-select": "^3.1.3",
Expand All @@ -162,6 +162,7 @@
"@types/jest": "^23.3.5",
"@types/react": "^16.4.18",
"@types/react-dom": "^16.0.9",
"@types/react-table": "^7.0.2",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.8.0",
Expand Down
@@ -0,0 +1,60 @@
/**
* 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 { Modal, Button } from 'react-bootstrap';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';

describe('ConfirmStatusChange', () => {
const mockedProps = {
title: 'please confirm',
description: 'are you sure?',
onConfirm: jest.fn(),
};
const wrapper = mount(
<ConfirmStatusChange {...mockedProps}>
{confirm => (
<>
<button id="btn1" onClick={confirm} />
</>
)}
</ConfirmStatusChange>,
);

it('opens a confirm modal', () => {
wrapper
.find('#btn1')
.props()
.onClick('foo');

wrapper.update();

expect(wrapper.find(Modal).exists()).toBeTruthy();
});

it('calls the function on confirm', () => {
wrapper
.find(Button)
.last()
.props()
.onClick();

expect(mockedProps.onConfirm).toHaveBeenCalledWith('foo');
});
});
198 changes: 140 additions & 58 deletions superset/assets/spec/javascripts/components/ListView/ListView_spec.jsx
Expand Up @@ -50,59 +50,67 @@ describe('ListView', () => {
id: [],
name: [{ name: 'sw', label: 'Starts With' }],
},
bulkActions: [{ name: 'do something', onSelect: jest.fn() }],
};
const wrapper = mount(<ListView {...mockedProps} />);

afterEach(() => {
mockedProps.fetchData.mockClear();
mockedProps.bulkActions.forEach(ba => {
ba.onSelect.mockClear();
});
});

it('calls fetchData on mount', () => {
expect(wrapper.find(ListView)).toHaveLength(1);
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Object {},
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [],
},
]
`);
Array [
Object {
"filters": Array [],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [],
},
]
`);
});

it('calls fetchData on sort', () => {
wrapper
.find('[data-test="sort-header"]')
.first()
.at(1)
.simulate('click');

expect(mockedProps.fetchData).toHaveBeenCalled();
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Object {},
"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', () => {
act(() => {
wrapper
.find('.dropdown-toggle')
.children('button')
.at(0)
.props()
.onClick();

wrapper
.find(MenuItem)
.at(0)
.props()
.onSelect({ id: 'name', Header: 'name' });
});
Expand All @@ -124,25 +132,27 @@ describe('ListView', () => {
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",
},
],
},
]
`);
Array [
Object {
"filters": Array [
Object {
"Header": "name",
"filterId": "sw",
"id": "name",
"value": "foo",
},
],
"pageIndex": 0,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
});

it('calls fetchData on page change', () => {
Expand All @@ -152,23 +162,95 @@ describe('ListView', () => {
wrapper.update();

expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"filters": Array [
Object {
"Header": "name",
"filterId": "sw",
"id": "name",
"value": "foo",
},
],
"pageIndex": 1,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
]
`);
});
it('handles bulk actions on 1 row', () => {
act(() => {
wrapper
.find('input[title="Toggle Row 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",
},
],
]
`);
});
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 [
Object {
"filters": Object {
"name": Object {
"filterId": "sw",
"filterValue": "foo",
},
Array [
Object {
"id": 1,
"name": "data 1",
},
Object {
"id": 2,
"name": "data 2",
},
"pageIndex": 1,
"pageSize": 1,
"sortBy": Array [
Object {
"desc": false,
"id": "id",
},
],
},
],
]
`);
});
Expand Down
Expand Up @@ -23,8 +23,7 @@ import configureStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';

import ListView from 'src/components/ListView/ListView';
import DashboardTable from '../../../src/welcome/DashboardTable';
import Loading from '../../../src/components/Loading';
import DashboardTable from 'src/welcome/DashboardTable';

// store needed for withToasts(DashboardTable)
const mockStore = configureStore([thunk]);
Expand All @@ -43,11 +42,6 @@ function setup() {
describe('DashboardTable', () => {
beforeEach(fetchMock.resetHistory);

it('renders a Loading initially', () => {
const wrapper = setup();
expect(wrapper.find(Loading)).toHaveLength(1);
});

it('fetches dashboards and renders a ListView', done => {
const wrapper = setup();

Expand Down

0 comments on commit a0cda32

Please sign in to comment.