mirror of https://github.com/apache/superset.git
[superset-client] replace misc ajax calls (#6135)
* [superset-client][misc] replace ajax calls in DashboardTable, TableLoader, utils, common * [superset-client][misc] replace ajax calls in AsyncSelect, HeaderActions, Deck.gl * [superset-client][misc] fix tests * [superset-client] remove unneeded functional setState calls * [superset-client] make welcome a redux app for toasts * [superset-client] make Profile a redux app for toasts * [superset-client] TableLoader don't pass toast props to dom nodes * tweak deckgl Multi syntax
This commit is contained in:
parent
96228adda9
commit
a71e6eb0a3
|
@ -1,14 +1,22 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import sinon from 'sinon';
|
import fetchMock from 'fetch-mock';
|
||||||
|
|
||||||
import AsyncSelect from '../../../src/components/AsyncSelect';
|
import AsyncSelect from '../../../src/components/AsyncSelect';
|
||||||
|
|
||||||
describe('AsyncSelect', () => {
|
describe('AsyncSelect', () => {
|
||||||
|
afterAll(fetchMock.reset);
|
||||||
|
afterEach(fetchMock.resetHistory);
|
||||||
|
|
||||||
|
const dataEndpoint = '/chart/api/read';
|
||||||
|
const dataGlob = 'glob:*/chart/api/read';
|
||||||
|
fetchMock.get(dataGlob, []);
|
||||||
|
fetchMock.resetHistory();
|
||||||
|
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
dataEndpoint: '/chart/api/read',
|
dataEndpoint,
|
||||||
onChange: sinon.spy(),
|
onChange: () => {},
|
||||||
placeholder: 'Select...',
|
placeholder: 'Select...',
|
||||||
mutator: () => [
|
mutator: () => [
|
||||||
{ value: 1, label: 'main' },
|
{ value: 1, label: 'main' },
|
||||||
|
@ -16,6 +24,7 @@ describe('AsyncSelect', () => {
|
||||||
],
|
],
|
||||||
valueRenderer: opt => opt.label,
|
valueRenderer: opt => opt.label,
|
||||||
};
|
};
|
||||||
|
|
||||||
it('is valid element', () => {
|
it('is valid element', () => {
|
||||||
expect(
|
expect(
|
||||||
React.isValidElement(<AsyncSelect {...mockedProps} />),
|
React.isValidElement(<AsyncSelect {...mockedProps} />),
|
||||||
|
@ -30,52 +39,81 @@ describe('AsyncSelect', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onChange on select change', () => {
|
it('calls onChange on select change', () => {
|
||||||
|
const onChangeSpy = jest.fn();
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<AsyncSelect {...mockedProps} />,
|
<AsyncSelect {...mockedProps} onChange={onChangeSpy} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
wrapper.find(Select).simulate('change', { value: 1 });
|
wrapper.find(Select).simulate('change', { value: 1 });
|
||||||
expect(mockedProps.onChange).toHaveProperty('callCount', 1);
|
expect(onChangeSpy.mock.calls).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('auto select', () => {
|
describe('auto select', () => {
|
||||||
let server;
|
it('should not call onChange if autoSelect=false', (done) => {
|
||||||
beforeEach(() => {
|
expect.assertions(2);
|
||||||
server = sinon.fakeServer.create();
|
|
||||||
server.respondWith([
|
|
||||||
200, { 'Content-Type': 'application/json' }, JSON.stringify({}),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
afterEach(() => {
|
|
||||||
server.restore();
|
|
||||||
});
|
|
||||||
it('should be off by default', () => {
|
|
||||||
const wrapper = shallow(
|
|
||||||
<AsyncSelect {...mockedProps} />,
|
|
||||||
);
|
|
||||||
wrapper.instance().fetchOptions();
|
|
||||||
const spy = sinon.spy(wrapper.instance(), 'onChange');
|
|
||||||
expect(spy.callCount).toBe(0);
|
|
||||||
});
|
|
||||||
it('should auto select first option', () => {
|
|
||||||
const wrapper = shallow(
|
|
||||||
<AsyncSelect {...mockedProps} autoSelect />,
|
|
||||||
);
|
|
||||||
const spy = sinon.spy(wrapper.instance(), 'onChange');
|
|
||||||
server.respond();
|
|
||||||
|
|
||||||
expect(spy.callCount).toBe(1);
|
const onChangeSpy = jest.fn();
|
||||||
expect(spy.calledWith(wrapper.instance().state.options[0])).toBe(true);
|
shallow(
|
||||||
});
|
<AsyncSelect {...mockedProps} onChange={onChangeSpy} />,
|
||||||
it('should not auto select when value prop is set', () => {
|
|
||||||
const wrapper = shallow(
|
|
||||||
<AsyncSelect {...mockedProps} value={2} autoSelect />,
|
|
||||||
);
|
);
|
||||||
const spy = sinon.spy(wrapper.instance(), 'onChange');
|
|
||||||
wrapper.instance().fetchOptions();
|
|
||||||
server.respond();
|
|
||||||
|
|
||||||
expect(spy.callCount).toBe(0);
|
setTimeout(() => {
|
||||||
|
expect(fetchMock.calls(dataGlob)).toHaveLength(1);
|
||||||
|
expect(onChangeSpy.mock.calls).toHaveLength(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should auto select the first option if autoSelect=true', (done) => {
|
||||||
|
expect.assertions(3);
|
||||||
|
|
||||||
|
const onChangeSpy = jest.fn();
|
||||||
|
const wrapper = shallow(
|
||||||
|
<AsyncSelect {...mockedProps} onChange={onChangeSpy} autoSelect />,
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(fetchMock.calls(dataGlob)).toHaveLength(1);
|
||||||
|
expect(onChangeSpy.mock.calls).toHaveLength(1);
|
||||||
|
expect(onChangeSpy).toBeCalledWith(wrapper.instance().state.options[0]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not auto select when value prop is set and autoSelect=true', (done) => {
|
||||||
|
expect.assertions(3);
|
||||||
|
|
||||||
|
const onChangeSpy = jest.fn();
|
||||||
|
const wrapper = shallow(
|
||||||
|
<AsyncSelect {...mockedProps} value={2} onChange={onChangeSpy} autoSelect />,
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(fetchMock.calls(dataGlob)).toHaveLength(1);
|
||||||
|
expect(onChangeSpy.mock.calls).toHaveLength(0);
|
||||||
expect(wrapper.find(Select)).toHaveLength(1);
|
expect(wrapper.find(Select)).toHaveLength(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onAsyncError if there is an error fetching options', (done) => {
|
||||||
|
expect.assertions(3);
|
||||||
|
|
||||||
|
const errorEndpoint = 'async/error/';
|
||||||
|
const errorGlob = 'glob:*async/error/';
|
||||||
|
fetchMock.get(errorGlob, { throws: 'error' });
|
||||||
|
|
||||||
|
const onAsyncError = jest.fn();
|
||||||
|
shallow(
|
||||||
|
<AsyncSelect {...mockedProps} dataEndpoint={errorEndpoint} onAsyncError={onAsyncError} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(fetchMock.calls(errorGlob)).toHaveLength(1);
|
||||||
|
expect(onAsyncError.mock.calls).toHaveLength(1);
|
||||||
|
expect(onAsyncError).toBeCalledWith('error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Col, Row, Tab } from 'react-bootstrap';
|
import { Col, Row, Tab } from 'react-bootstrap';
|
||||||
import { mount } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { user } from './fixtures';
|
import { user } from './fixtures';
|
||||||
import App from '../../../src/profile/components/App';
|
import App from '../../../src/profile/components/App';
|
||||||
|
@ -14,13 +14,15 @@ describe('App', () => {
|
||||||
React.isValidElement(<App {...mockedProps} />),
|
React.isValidElement(<App {...mockedProps} />),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders 2 Col', () => {
|
it('renders 2 Col', () => {
|
||||||
const wrapper = mount(<App {...mockedProps} />);
|
const wrapper = shallow(<App {...mockedProps} />);
|
||||||
expect(wrapper.find(Row)).toHaveLength(1);
|
expect(wrapper.find(Row)).toHaveLength(1);
|
||||||
expect(wrapper.find(Col)).toHaveLength(2);
|
expect(wrapper.find(Col)).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders 4 Tabs', () => {
|
it('renders 4 Tabs', () => {
|
||||||
const wrapper = mount(<App {...mockedProps} />);
|
const wrapper = shallow(<App {...mockedProps} />);
|
||||||
expect(wrapper.find(Tab)).toHaveLength(4);
|
expect(wrapper.find(Tab)).toHaveLength(4);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
import { user } from './fixtures';
|
import { user } from './fixtures';
|
||||||
import CreatedContent from '../../../src/profile/components/CreatedContent';
|
import CreatedContent from '../../../src/profile/components/CreatedContent';
|
||||||
import TableLoader from '../../../src/components/TableLoader';
|
import TableLoader from '../../../src/components/TableLoader';
|
||||||
|
|
||||||
|
// store needed for withToasts(TableLoader)
|
||||||
|
const mockStore = configureStore([thunk]);
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
describe('CreatedContent', () => {
|
describe('CreatedContent', () => {
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
user,
|
user,
|
||||||
};
|
};
|
||||||
it('is valid', () => {
|
|
||||||
expect(
|
|
||||||
React.isValidElement(<CreatedContent {...mockedProps} />),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it('renders 2 TableLoader', () => {
|
it('renders 2 TableLoader', () => {
|
||||||
const wrapper = mount(<CreatedContent {...mockedProps} />);
|
const wrapper = shallow(<CreatedContent {...mockedProps} />, { context: { store } });
|
||||||
expect(wrapper.find(TableLoader)).toHaveLength(2);
|
expect(wrapper.find(TableLoader)).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders 2 titles', () => {
|
it('renders 2 titles', () => {
|
||||||
const wrapper = mount(<CreatedContent {...mockedProps} />);
|
const wrapper = shallow(<CreatedContent {...mockedProps} />, { context: { store } });
|
||||||
expect(wrapper.find('h3')).toHaveLength(2);
|
expect(wrapper.find('h3')).toHaveLength(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
import { user } from './fixtures';
|
import { user } from './fixtures';
|
||||||
import Favorites from '../../../src/profile/components/Favorites';
|
import Favorites from '../../../src/profile/components/Favorites';
|
||||||
import TableLoader from '../../../src/components/TableLoader';
|
import TableLoader from '../../../src/components/TableLoader';
|
||||||
|
|
||||||
|
// store needed for withToasts(TableLoader)
|
||||||
|
const mockStore = configureStore([thunk]);
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
describe('Favorites', () => {
|
describe('Favorites', () => {
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
user,
|
user,
|
||||||
};
|
};
|
||||||
it('is valid', () => {
|
|
||||||
expect(
|
|
||||||
React.isValidElement(<Favorites {...mockedProps} />),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it('renders 2 TableLoader', () => {
|
it('renders 2 TableLoader', () => {
|
||||||
const wrapper = mount(<Favorites {...mockedProps} />);
|
const wrapper = shallow(<Favorites {...mockedProps} />, { context: { store } });
|
||||||
expect(wrapper.find(TableLoader)).toHaveLength(2);
|
expect(wrapper.find(TableLoader)).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders 2 titles', () => {
|
it('renders 2 titles', () => {
|
||||||
const wrapper = mount(<Favorites {...mockedProps} />);
|
const wrapper = shallow(<Favorites {...mockedProps} />, { context: { store } });
|
||||||
expect(wrapper.find('h3')).toHaveLength(2);
|
expect(wrapper.find('h3')).toHaveLength(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
import { user } from './fixtures';
|
import { user } from './fixtures';
|
||||||
import RecentActivity from '../../../src/profile/components/RecentActivity';
|
import RecentActivity from '../../../src/profile/components/RecentActivity';
|
||||||
import TableLoader from '../../../src/components/TableLoader';
|
import TableLoader from '../../../src/components/TableLoader';
|
||||||
|
|
||||||
|
|
||||||
describe('RecentActivity', () => {
|
describe('RecentActivity', () => {
|
||||||
const mockedProps = {
|
const mockedProps = {
|
||||||
user,
|
user,
|
||||||
|
@ -15,8 +14,9 @@ describe('RecentActivity', () => {
|
||||||
React.isValidElement(<RecentActivity {...mockedProps} />),
|
React.isValidElement(<RecentActivity {...mockedProps} />),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders a TableLoader', () => {
|
it('renders a TableLoader', () => {
|
||||||
const wrapper = mount(<RecentActivity {...mockedProps} />);
|
const wrapper = shallow(<RecentActivity {...mockedProps} />);
|
||||||
expect(wrapper.find(TableLoader)).toHaveLength(1);
|
expect(wrapper.find(TableLoader)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,30 +1,47 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import sinon from 'sinon';
|
import thunk from 'redux-thunk';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
import fetchMock from 'fetch-mock';
|
||||||
|
import { Table } from 'reactable';
|
||||||
|
|
||||||
import DashboardTable from '../../../src/welcome/DashboardTable';
|
import DashboardTable from '../../../src/welcome/DashboardTable';
|
||||||
|
import Loading from '../../../src/components/Loading';
|
||||||
|
|
||||||
const $ = window.$ = require('jquery');
|
// store needed for withToasts(TableLoader)
|
||||||
|
const mockStore = configureStore([thunk]);
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
|
const dashboardsEndpoint = 'glob:*/dashboardasync/api/read*';
|
||||||
|
const mockDashboards = [
|
||||||
|
{ id: 1, url: 'url', dashboard_title: 'title' },
|
||||||
|
];
|
||||||
|
|
||||||
|
fetchMock.get(dashboardsEndpoint, { result: mockDashboards });
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
// use mount because data fetching is triggered on mount
|
||||||
|
return mount(<DashboardTable />, { context: { store } });
|
||||||
|
}
|
||||||
|
|
||||||
describe('DashboardTable', () => {
|
describe('DashboardTable', () => {
|
||||||
const mockedProps = {};
|
afterEach(fetchMock.resetHistory);
|
||||||
let stub;
|
|
||||||
beforeEach(() => {
|
it('renders a Loading initially', () => {
|
||||||
stub = sinon.stub($, 'getJSON');
|
const wrapper = setup();
|
||||||
});
|
expect(wrapper.find(Loading)).toHaveLength(1);
|
||||||
afterEach(() => {
|
|
||||||
stub.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is valid', () => {
|
it('fetches dashboards and renders a Table', (done) => {
|
||||||
expect(
|
const wrapper = setup();
|
||||||
React.isValidElement(<DashboardTable {...mockedProps} />),
|
|
||||||
).toBe(true);
|
setTimeout(() => {
|
||||||
});
|
expect(fetchMock.calls(dashboardsEndpoint)).toHaveLength(1);
|
||||||
it('renders', () => {
|
// there's a delay between response and updating state, so manually set it
|
||||||
const wrapper = mount(<DashboardTable {...mockedProps} />);
|
// rather than adding a timeout which could introduce flakiness
|
||||||
expect(stub.callCount).toBe(1);
|
wrapper.setState({ dashaboards: mockDashboards });
|
||||||
expect(wrapper.find('img')).toHaveLength(1);
|
expect(wrapper.find(Table)).toHaveLength(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,8 +21,8 @@ $(document).ready(function () {
|
||||||
$('#language-picker a').click(function (ev) {
|
$('#language-picker a').click(function (ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
const targetUrl = ev.currentTarget.href;
|
SupersetClient.get({ endpoint: ev.currentTarget.href })
|
||||||
$.ajax(targetUrl).then(() => {
|
.then(() => {
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
|
import { SupersetClient } from '@superset-ui/core';
|
||||||
import { t } from '../locales';
|
import { t } from '../locales';
|
||||||
|
|
||||||
const $ = window.$ = require('jquery');
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
dataEndpoint: PropTypes.string.isRequired,
|
dataEndpoint: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
@ -32,31 +31,38 @@ class AsyncSelect extends React.PureComponent {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
options: [],
|
options: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.onChange = this.onChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.fetchOptions();
|
this.fetchOptions();
|
||||||
}
|
}
|
||||||
onChange(opt) {
|
|
||||||
this.props.onChange(opt);
|
onChange(option) {
|
||||||
|
this.props.onChange(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchOptions() {
|
fetchOptions() {
|
||||||
this.setState({ isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
const mutator = this.props.mutator;
|
const { mutator, dataEndpoint } = this.props;
|
||||||
$.get(this.props.dataEndpoint)
|
|
||||||
.done((data) => {
|
|
||||||
this.setState({ options: mutator ? mutator(data) : data, isLoading: false });
|
|
||||||
|
|
||||||
if (!this.props.value && this.props.autoSelect && this.state.options.length) {
|
return SupersetClient.get({ endpoint: dataEndpoint })
|
||||||
this.onChange(this.state.options[0]);
|
.then(({ json }) => {
|
||||||
|
const options = mutator ? mutator(json) : json;
|
||||||
|
|
||||||
|
this.setState({ options, isLoading: false });
|
||||||
|
|
||||||
|
if (!this.props.value && this.props.autoSelect && options.length > 0) {
|
||||||
|
this.onChange(options[0]);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.fail((xhr) => {
|
.catch((error) => {
|
||||||
this.props.onAsyncError(xhr.responseText);
|
this.props.onAsyncError(error.error || error.statusText || error);
|
||||||
})
|
|
||||||
.always(() => {
|
|
||||||
this.setState({ isLoading: false });
|
this.setState({ isLoading: false });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -65,7 +71,7 @@ class AsyncSelect extends React.PureComponent {
|
||||||
options={this.state.options}
|
options={this.state.options}
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
isLoading={this.state.isLoading}
|
isLoading={this.state.isLoading}
|
||||||
onChange={this.onChange.bind(this)}
|
onChange={this.onChange}
|
||||||
valueRenderer={this.props.valueRenderer}
|
valueRenderer={this.props.valueRenderer}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Table, Tr, Td } from 'reactable';
|
import { Table, Tr, Td } from 'reactable';
|
||||||
import $ from 'jquery';
|
import { SupersetClient } from '@superset-ui/core';
|
||||||
|
|
||||||
|
import withToasts from '../messageToasts/enhancers/withToasts';
|
||||||
|
import { t } from '../locales';
|
||||||
import Loading from '../components/Loading';
|
import Loading from '../components/Loading';
|
||||||
import '../../stylesheets/reactable-pagination.css';
|
import '../../stylesheets/reactable-pagination.css';
|
||||||
|
|
||||||
|
@ -9,9 +12,10 @@ const propTypes = {
|
||||||
dataEndpoint: PropTypes.string.isRequired,
|
dataEndpoint: PropTypes.string.isRequired,
|
||||||
mutator: PropTypes.func,
|
mutator: PropTypes.func,
|
||||||
columns: PropTypes.arrayOf(PropTypes.string),
|
columns: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
addDangerToast: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class TableLoader extends React.PureComponent {
|
class TableLoader extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -19,20 +23,34 @@ export default class TableLoader extends React.PureComponent {
|
||||||
data: [],
|
data: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
$.get(this.props.dataEndpoint, (data) => {
|
const { dataEndpoint, mutator } = this.props;
|
||||||
let actualData = data;
|
|
||||||
if (this.props.mutator) {
|
SupersetClient.get({ endpoint: dataEndpoint })
|
||||||
actualData = this.props.mutator(data);
|
.then(({ json }) => {
|
||||||
}
|
const data = mutator ? mutator(json) : json;
|
||||||
this.setState({ data: actualData, isLoading: false });
|
this.setState({ data, isLoading: false });
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.setState({ isLoading: false });
|
||||||
|
this.props.addDangerToast(t('An error occurred'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.isLoading) {
|
if (this.state.isLoading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
const tableProps = Object.assign({}, this.props);
|
|
||||||
|
const {
|
||||||
|
addDangerToast,
|
||||||
|
addInfoToast,
|
||||||
|
addSuccessToast,
|
||||||
|
addWarningToast,
|
||||||
|
...tableProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
let { columns } = this.props;
|
let { columns } = this.props;
|
||||||
if (!columns && this.state.data.length > 0) {
|
if (!columns && this.state.data.length > 0) {
|
||||||
columns = Object.keys(this.state.data[0]).filter(col => col[0] !== '_');
|
columns = Object.keys(this.state.data[0]).filter(col => col[0] !== '_');
|
||||||
|
@ -70,4 +88,7 @@ export default class TableLoader extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TableLoader.propTypes = propTypes;
|
TableLoader.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default withToasts(TableLoader);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import $ from 'jquery';
|
import { SupersetClient } from '@superset-ui/core';
|
||||||
import { DropdownButton, MenuItem } from 'react-bootstrap';
|
import { DropdownButton, MenuItem } from 'react-bootstrap';
|
||||||
|
|
||||||
import CssEditor from './CssEditor';
|
import CssEditor from './CssEditor';
|
||||||
|
@ -52,13 +52,19 @@ class HeaderActionsDropdown extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
injectCustomCss(this.state.css);
|
injectCustomCss(this.state.css);
|
||||||
|
|
||||||
$.get('/csstemplateasyncmodelview/api/read', data => {
|
SupersetClient.get({ endpoint: '/csstemplateasyncmodelview/api/read' })
|
||||||
const cssTemplates = data.result.map(row => ({
|
.then(({ json }) => {
|
||||||
|
const cssTemplates = json.result.map(row => ({
|
||||||
value: row.template_name,
|
value: row.template_name,
|
||||||
css: row.css,
|
css: row.css,
|
||||||
label: row.template_name,
|
label: row.template_name,
|
||||||
}));
|
}));
|
||||||
this.setState({ cssTemplates });
|
this.setState({ cssTemplates });
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.props.addDangerToast(
|
||||||
|
t('An error occurred while fetching available CSS templates'),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* eslint camelcase: 0 */
|
/* eslint camelcase: 0 */
|
||||||
import d3 from 'd3';
|
import d3 from 'd3';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import { SupersetClient } from '@superset-ui/core';
|
||||||
import { formatDate, UTC } from './dates';
|
import { formatDate, UTC } from './dates';
|
||||||
|
|
||||||
const siFormatter = d3.format('.3s');
|
const siFormatter = d3.format('.3s');
|
||||||
|
@ -119,9 +120,11 @@ function showApiMessage(resp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleCheckbox(apiUrlPrefix, selector) {
|
export function toggleCheckbox(apiUrlPrefix, selector) {
|
||||||
const apiUrl = apiUrlPrefix + $(selector)[0].checked;
|
SupersetClient.get({ endpoint: apiUrlPrefix + $(selector)[0].checked })
|
||||||
$.get(apiUrl).fail(function (xhr) {
|
.then(() => {})
|
||||||
const resp = xhr.responseJSON;
|
.catch((response) => {
|
||||||
|
// @TODO utility function to read this
|
||||||
|
const resp = response.responseJSON;
|
||||||
if (resp && resp.message) {
|
if (resp && resp.message) {
|
||||||
showApiMessage(resp);
|
showApiMessage(resp);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import App from './components/App';
|
import App from './components/App';
|
||||||
|
import messageToastReducer from '../messageToasts/reducers';
|
||||||
|
import { initEnhancer } from '../reduxUtils';
|
||||||
import { appSetup } from '../common';
|
import { appSetup } from '../common';
|
||||||
|
|
||||||
import './main.css';
|
import './main.css';
|
||||||
|
@ -10,8 +16,21 @@ appSetup();
|
||||||
const profileViewContainer = document.getElementById('app');
|
const profileViewContainer = document.getElementById('app');
|
||||||
const bootstrap = JSON.parse(profileViewContainer.getAttribute('data-bootstrap'));
|
const bootstrap = JSON.parse(profileViewContainer.getAttribute('data-bootstrap'));
|
||||||
|
|
||||||
|
const store = createStore(
|
||||||
|
combineReducers({
|
||||||
|
messageToasts: messageToastReducer,
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
compose(
|
||||||
|
applyMiddleware(thunk),
|
||||||
|
initEnhancer(false),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const Application = () => (
|
const Application = () => (
|
||||||
|
<Provider store={store}>
|
||||||
<App user={bootstrap.user} />
|
<App user={bootstrap.user} />
|
||||||
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default hot(module)(Application);
|
export default hot(module)(Application);
|
||||||
|
|
|
@ -33,4 +33,5 @@ export default class RecentActivity extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RecentActivity.propTypes = propTypes;
|
RecentActivity.propTypes = propTypes;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import $ from 'jquery';
|
import { SupersetClient } from '@superset-ui/core';
|
||||||
|
|
||||||
import DeckGLContainer from '../DeckGLContainer';
|
import DeckGLContainer from '../DeckGLContainer';
|
||||||
import { getExploreLongUrl } from '../../../explore/exploreUtils';
|
import { getExploreLongUrl } from '../../../explore/exploreUtils';
|
||||||
import layerGenerators from '../layers';
|
import layerGenerators from '../layers';
|
||||||
|
@ -47,11 +48,13 @@ class DeckMulti extends React.PureComponent {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = getExploreLongUrl(subsliceCopy.form_data, 'json');
|
SupersetClient.get({
|
||||||
$.get(url, (data) => {
|
endpoint: getExploreLongUrl(subsliceCopy.form_data, 'json'),
|
||||||
|
})
|
||||||
|
.then(({ json }) => {
|
||||||
const layer = layerGenerators[subsliceCopy.form_data.viz_type](
|
const layer = layerGenerators[subsliceCopy.form_data.viz_type](
|
||||||
subsliceCopy.form_data,
|
subsliceCopy.form_data,
|
||||||
data,
|
json,
|
||||||
);
|
);
|
||||||
this.setState({
|
this.setState({
|
||||||
subSlicesLayers: {
|
subSlicesLayers: {
|
||||||
|
@ -59,7 +62,8 @@ class DeckMulti extends React.PureComponent {
|
||||||
[subsliceCopy.slice_id]: layer,
|
[subsliceCopy.slice_id]: layer,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
.catch(() => {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +71,7 @@ class DeckMulti extends React.PureComponent {
|
||||||
const { payload, viewport, formData, setControlValue } = this.props;
|
const { payload, viewport, formData, setControlValue } = this.props;
|
||||||
const { subSlicesLayers } = this.state;
|
const { subSlicesLayers } = this.state;
|
||||||
|
|
||||||
const layers = Object.keys(subSlicesLayers).map(k => subSlicesLayers[k]);
|
const layers = Object.values(subSlicesLayers);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DeckGLContainer
|
<DeckGLContainer
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import messageToastReducer from '../messageToasts/reducers';
|
||||||
|
import { initEnhancer } from '../reduxUtils';
|
||||||
|
|
||||||
import { appSetup } from '../common';
|
import { appSetup } from '../common';
|
||||||
import Welcome from './Welcome';
|
import Welcome from './Welcome';
|
||||||
|
|
||||||
|
@ -9,8 +16,21 @@ const container = document.getElementById('app');
|
||||||
const bootstrap = JSON.parse(container.getAttribute('data-bootstrap'));
|
const bootstrap = JSON.parse(container.getAttribute('data-bootstrap'));
|
||||||
const user = { ...bootstrap.user };
|
const user = { ...bootstrap.user };
|
||||||
|
|
||||||
|
const store = createStore(
|
||||||
|
combineReducers({
|
||||||
|
messageToasts: messageToastReducer,
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
compose(
|
||||||
|
applyMiddleware(thunk),
|
||||||
|
initEnhancer(false),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
|
<Provider store={store}>
|
||||||
<Welcome user={user} />
|
<Welcome user={user} />
|
||||||
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default hot(module)(App);
|
export default hot(module)(App);
|
||||||
|
|
|
@ -1,33 +1,40 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Table, Tr, Td, unsafe } from 'reactable';
|
import { Table, Tr, Td, unsafe } from 'reactable';
|
||||||
|
import { SupersetClient } from '@superset-ui/core';
|
||||||
|
import withToasts from '../messageToasts/enhancers/withToasts';
|
||||||
|
import { t } from '../locales';
|
||||||
|
|
||||||
import Loading from '../components/Loading';
|
import Loading from '../components/Loading';
|
||||||
import '../../stylesheets/reactable-pagination.css';
|
import '../../stylesheets/reactable-pagination.css';
|
||||||
|
|
||||||
const $ = window.$ = require('jquery');
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
search: PropTypes.string,
|
search: PropTypes.string,
|
||||||
|
addDangerToast: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class DashboardTable extends React.PureComponent {
|
class DashboardTable extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
dashboards: false,
|
dashboards: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const url = (
|
SupersetClient.get({
|
||||||
'/dashboardasync/api/read' +
|
endpoint: '/dashboardasync/api/read?_oc_DashboardModelViewAsync=changed_on&_od_DashboardModelViewAsync=desc',
|
||||||
'?_oc_DashboardModelViewAsync=changed_on' +
|
})
|
||||||
'&_od_DashboardModelViewAsync=desc');
|
.then(({ json }) => {
|
||||||
$.getJSON(url, (data) => {
|
this.setState({ dashboards: json.result });
|
||||||
this.setState({ dashboards: data.result });
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.props.addDangerToast(t('An error occurred while fethching Dashboards'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.dashboards) {
|
if (this.state.dashboards.length > 0) {
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
className="table"
|
className="table"
|
||||||
|
@ -58,8 +65,11 @@ export default class DashboardTable extends React.PureComponent {
|
||||||
</Table>
|
</Table>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DashboardTable.propTypes = propTypes;
|
DashboardTable.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default withToasts(DashboardTable);
|
||||||
|
|
Loading…
Reference in New Issue