Add a Async Select that fetches options from given endpoint (#1909)
* Add a Async Select that fetches options from given endpoint * update it statement
This commit is contained in:
parent
94d20168da
commit
f0917c62f2
|
|
@ -1,61 +0,0 @@
|
|||
const $ = window.$ = require('jquery');
|
||||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
|
||||
const propTypes = {
|
||||
onChange: React.PropTypes.func,
|
||||
actions: React.PropTypes.object,
|
||||
databaseId: React.PropTypes.number,
|
||||
valueRenderer: React.PropTypes.func,
|
||||
};
|
||||
|
||||
class DatabaseSelect extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
databaseLoading: false,
|
||||
databaseOptions: [],
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
this.fetchDatabaseOptions();
|
||||
}
|
||||
changeDb(db) {
|
||||
this.props.onChange(db);
|
||||
}
|
||||
fetchDatabaseOptions() {
|
||||
this.setState({ databaseLoading: true });
|
||||
const url = '/databaseasync/api/read?_flt_0_expose_in_sqllab=1';
|
||||
$.get(url, (data) => {
|
||||
const options = data.result.map((db) => ({ value: db.id, label: db.database_name }));
|
||||
this.setState({ databaseOptions: options, databaseLoading: false });
|
||||
this.props.actions.setDatabases(data.result);
|
||||
if (data.result.length === 0) {
|
||||
this.props.actions.addAlert({
|
||||
bsStyle: 'danger',
|
||||
msg: "It seems you don't have access to any database",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Select
|
||||
name="select-db"
|
||||
placeholder={`Select a database (${this.state.databaseOptions.length})`}
|
||||
options={this.state.databaseOptions}
|
||||
value={this.props.databaseId}
|
||||
isLoading={this.state.databaseLoading}
|
||||
autosize={false}
|
||||
onChange={this.changeDb.bind(this)}
|
||||
valueRenderer={this.props.valueRenderer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DatabaseSelect.propTypes = propTypes;
|
||||
|
||||
export default DatabaseSelect;
|
||||
|
|
@ -3,10 +3,10 @@ import React from 'react';
|
|||
import { Button } from 'react-bootstrap';
|
||||
import Select from 'react-select';
|
||||
import QueryTable from './QueryTable';
|
||||
import DatabaseSelect from './DatabaseSelect';
|
||||
import { now, epochTimeXHoursAgo,
|
||||
epochTimeXDaysAgo, epochTimeXYearsAgo } from '../../modules/dates';
|
||||
import { STATUS_OPTIONS, TIME_OPTIONS } from '../constants';
|
||||
import AsyncSelect from '../../components/AsyncSelect';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
|
|
@ -28,9 +28,6 @@ class QuerySearch extends React.PureComponent {
|
|||
queriesLoading: true,
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
this.fetchUsers();
|
||||
}
|
||||
componentDidMount() {
|
||||
this.refreshQueries();
|
||||
}
|
||||
|
|
@ -89,18 +86,23 @@ class QuerySearch extends React.PureComponent {
|
|||
changeSearch(event) {
|
||||
this.setState({ searchText: event.target.value });
|
||||
}
|
||||
fetchUsers() {
|
||||
this.setState({ userLoading: true });
|
||||
const url = '/users/api/read';
|
||||
$.getJSON(url, (data, status) => {
|
||||
if (status === 'success') {
|
||||
const options = [];
|
||||
for (let i = 0; i < data.pks.length; i++) {
|
||||
options.push({ value: data.pks[i], label: data.result[i].username });
|
||||
}
|
||||
this.setState({ userOptions: options, userLoading: false });
|
||||
}
|
||||
});
|
||||
userMutator(data) {
|
||||
const options = [];
|
||||
for (let i = 0; i < data.pks.length; i++) {
|
||||
options.push({ value: data.pks[i], label: data.result[i].username });
|
||||
}
|
||||
return options;
|
||||
}
|
||||
dbMutator(data) {
|
||||
const options = data.result.map((db) => ({ value: db.id, label: db.database_name }));
|
||||
this.props.actions.setDatabases(data.result);
|
||||
if (data.result.length === 0) {
|
||||
this.props.actions.addAlert({
|
||||
bsStyle: 'danger',
|
||||
msg: "It seems you don't have access to any database",
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
refreshQueries() {
|
||||
this.setState({ queriesLoading: true });
|
||||
|
|
@ -125,21 +127,19 @@ class QuerySearch extends React.PureComponent {
|
|||
<div>
|
||||
<div id="search-header" className="row space-1">
|
||||
<div className="col-sm-2">
|
||||
<Select
|
||||
name="select-user"
|
||||
placeholder="[User]"
|
||||
options={this.state.userOptions}
|
||||
<AsyncSelect
|
||||
dataEndpoint="/users/api/read"
|
||||
mutator={this.userMutator}
|
||||
value={this.state.userId}
|
||||
isLoading={this.state.userLoading}
|
||||
autosize={false}
|
||||
onChange={this.changeUser.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-sm-2">
|
||||
<DatabaseSelect
|
||||
<AsyncSelect
|
||||
onChange={this.onChange.bind(this)}
|
||||
databaseId={this.state.databaseId}
|
||||
actions={this.props.actions}
|
||||
dataEndpoint="/databaseasync/api/read?_flt_0_expose_in_sqllab=1"
|
||||
value={this.state.databaseId}
|
||||
mutator={this.dbMutator.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-sm-4">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import Select from 'react-select';
|
||||
import { Label, Button } from 'react-bootstrap';
|
||||
import TableElement from './TableElement';
|
||||
import DatabaseSelect from './DatabaseSelect';
|
||||
import AsyncSelect from '../../components/AsyncSelect';
|
||||
|
||||
const propTypes = {
|
||||
queryEditor: React.PropTypes.object.isRequired,
|
||||
|
|
@ -44,6 +44,17 @@ class SqlEditorLeftBar extends React.PureComponent {
|
|||
this.fetchSchemas(val);
|
||||
}
|
||||
}
|
||||
dbMutator(data) {
|
||||
const options = data.result.map((db) => ({ value: db.id, label: db.database_name }));
|
||||
this.props.actions.setDatabases(data.result);
|
||||
if (data.result.length === 0) {
|
||||
this.props.actions.addAlert({
|
||||
bsStyle: 'danger',
|
||||
msg: "It seems you don't have access to any database",
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
resetState() {
|
||||
this.props.actions.resetState();
|
||||
}
|
||||
|
|
@ -103,15 +114,17 @@ class SqlEditorLeftBar extends React.PureComponent {
|
|||
<div className="clearfix sql-toolbar scrollbar-content">
|
||||
{networkAlert}
|
||||
<div>
|
||||
<DatabaseSelect
|
||||
<AsyncSelect
|
||||
dataEndpoint="/databaseasync/api/read?_flt_0_expose_in_sqllab=1"
|
||||
onChange={this.onChange.bind(this)}
|
||||
databaseId={this.props.queryEditor.dbId}
|
||||
actions={this.props.actions}
|
||||
value={this.props.queryEditor.dbId}
|
||||
valueRenderer={(o) => (
|
||||
<div>
|
||||
<span className="text-muted">Database:</span> {o.label}
|
||||
</div>
|
||||
)}
|
||||
mutator={this.dbMutator.bind(this)}
|
||||
placeholder="Select a database"
|
||||
/>
|
||||
</div>
|
||||
<div className="m-t-5">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
const $ = window.$ = require('jquery');
|
||||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
|
||||
const propTypes = {
|
||||
dataEndpoint: React.PropTypes.string.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
mutator: React.PropTypes.func.isRequired,
|
||||
value: React.PropTypes.number,
|
||||
valueRenderer: React.PropTypes.func,
|
||||
placeholder: React.PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
placeholder: 'Select ...',
|
||||
valueRenderer: (o) => (<div>{o.label}</div>),
|
||||
};
|
||||
|
||||
class AsyncSelect extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
this.fetchOptions();
|
||||
}
|
||||
fetchOptions() {
|
||||
this.setState({ isLoading: true });
|
||||
const mutator = this.props.mutator;
|
||||
$.get(this.props.dataEndpoint, (data) => {
|
||||
this.setState({ options: mutator ? mutator(data) : data, isLoading: false });
|
||||
});
|
||||
}
|
||||
onChange(opt) {
|
||||
this.props.onChange(opt);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Select
|
||||
placeholder={this.props.placeholder}
|
||||
options={this.state.options}
|
||||
value={this.props.value}
|
||||
isLoading={this.state.isLoading}
|
||||
onChange={this.onChange.bind(this)}
|
||||
valueRenderer={this.props.valueRenderer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AsyncSelect.propTypes = propTypes;
|
||||
AsyncSelect.defaultProps = defaultProps;
|
||||
|
||||
export default AsyncSelect;
|
||||
|
|
@ -1,34 +1,35 @@
|
|||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
import DatabaseSelect from '../../../javascripts/SqlLab/components/DatabaseSelect';
|
||||
import AsyncSelect from '../../../javascripts/components/AsyncSelect';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe('DatabaseSelect', () => {
|
||||
describe('AsyncSelect', () => {
|
||||
const mockedProps = {
|
||||
actions: {},
|
||||
dataEndpoint: '/slicemodelview/api/read',
|
||||
onChange: sinon.spy(),
|
||||
mutator: () => {},
|
||||
};
|
||||
it('is valid element', () => {
|
||||
expect(
|
||||
React.isValidElement(<DatabaseSelect {...mockedProps} />)
|
||||
React.isValidElement(<AsyncSelect {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('has one select', () => {
|
||||
const wrapper = shallow(
|
||||
<DatabaseSelect {...mockedProps} />
|
||||
<AsyncSelect {...mockedProps} />
|
||||
);
|
||||
expect(wrapper.find(Select)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('calls onChange on select change', () => {
|
||||
const onChange = sinon.spy();
|
||||
const wrapper = shallow(
|
||||
<DatabaseSelect onChange={onChange} />
|
||||
<AsyncSelect {...mockedProps} />
|
||||
);
|
||||
wrapper.find(Select).simulate('change', { value: 1 });
|
||||
expect(onChange).to.have.property('callCount', 1);
|
||||
expect(mockedProps.onChange).to.have.property('callCount', 1);
|
||||
});
|
||||
});
|
||||
|
|
@ -18,14 +18,8 @@ describe('QuerySearch', () => {
|
|||
});
|
||||
const wrapper = shallow(<QuerySearch {...mockedProps} />);
|
||||
|
||||
it('should have four Select', () => {
|
||||
expect(wrapper.find(Select)).to.have.length(4);
|
||||
});
|
||||
|
||||
it('updates userId on user selects change', () => {
|
||||
wrapper.find('[name="select-user"]')
|
||||
.simulate('change', { value: 1 });
|
||||
expect(wrapper.state().userId).to.equal(1);
|
||||
it('should have three Select', () => {
|
||||
expect(wrapper.find(Select)).to.have.length(3);
|
||||
});
|
||||
|
||||
it('updates fromTime on user selects from time', () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue