Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions caravel/assets/javascripts/SqlLab/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export const STATE_BSSTYLE_MAP = {
running: 'warning',
success: 'success',
};

export const STATUS_OPTIONS = ['success', 'failed', 'running'];
71 changes: 71 additions & 0 deletions caravel/assets/javascripts/SqlLab/components/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as Actions from '../actions';
import React from 'react';

import TabbedSqlEditors from './TabbedSqlEditors';
import QueryAutoRefresh from './QueryAutoRefresh';
import QuerySearch from './QuerySearch';
import Alerts from './Alerts';

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
hash: window.location.hash,
};
}
componentDidMount() {
window.addEventListener('hashchange', this.onHashChanged.bind(this));
}
componentWillUnmount() {
window.removeEventListener('hashchange', this.onHashChanged.bind(this));
}
onHashChanged() {
this.setState({ hash: window.location.hash });
}
render() {
if (this.state.hash) {
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<QuerySearch />
</div>
</div>
</div>
);
}
return (
<div className="App SqlLab">
<div className="container-fluid">
<QueryAutoRefresh />
<Alerts alerts={this.props.alerts} />
<div className="row">
<div className="col-md-12">
<TabbedSqlEditors />
</div>
</div>
</div>
</div>
);
}
}

App.propTypes = {
alerts: React.PropTypes.array,
};

function mapStateToProps(state) {
return {
alerts: state.alerts,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}

export default connect(mapStateToProps, mapDispatchToProps)(App);
51 changes: 51 additions & 0 deletions caravel/assets/javascripts/SqlLab/components/DatabaseSelect.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const $ = window.$ = require('jquery');
import React from 'react';
import Select from 'react-select';

class DatabaseSelect extends React.Component {
constructor(props) {
super(props);
this.state = {
databaseLoading: false,
databaseOptions: [],
databaseId: null,
};
}
componentWillMount() {
this.fetchDatabaseOptions();
}
changeDb(db) {
const val = (db) ? db.value : null;
this.setState({ databaseId: val });
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 });
});
}
render() {
return (
<div>
<Select
name="select-db"
placeholder={`Select a database (${this.state.databaseOptions.length})`}
options={this.state.databaseOptions}
value={this.state.databaseId}
isLoading={this.state.databaseLoading}
autosize={false}
onChange={this.changeDb.bind(this)}
/>
</div>
);
}
}

DatabaseSelect.propTypes = {
onChange: React.PropTypes.func,
};

export default DatabaseSelect;
163 changes: 112 additions & 51 deletions caravel/assets/javascripts/SqlLab/components/QuerySearch.jsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,134 @@
const $ = window.$ = require('jquery');
import React from 'react';
import Select from 'react-select';
import { Button } from 'react-bootstrap';

import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '../actions';
import { Button } from 'react-bootstrap';
import Select from 'react-select';
import QueryTable from './QueryTable';
import DatabaseSelect from './DatabaseSelect';
import { STATUS_OPTIONS } from '../common';

class QuerySearch extends React.Component {
constructor(props) {
super(props);
this.state = {
queryText: '',
userLoading: false,
userOptions: [],
databaseId: null,
userId: null,
searchText: null,
status: 'success',
queriesArray: [],
};
}
changeQueryText(value) {
this.setState({ queryText: value });
componentWillMount() {
this.fetchUsers();
this.refreshQueries();
}
onChange(db) {
const val = (db) ? db.value : null;
this.setState({ databaseId: val });
}
insertParams(baseUrl, params) {
return baseUrl + '?' + params.join('&');
}
changeUser(user) {
const val = (user) ? user.value : null;
this.setState({ userId: val });
}
changeStatus(status) {
const val = (status) ? status.value : null;
this.setState({ status: val });
}
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 });
}
});
}
refreshQueries() {
const params = [
`userId=${this.state.userId}`,
`databaseId=${this.state.databaseId}`,
`searchText=${this.state.searchText}`,
`status=${this.state.status}`,
];

const url = this.insertParams('/caravel/search_queries', params);
$.getJSON(url, (data, status) => {
if (status === 'success') {
const newQueriesArray = [];
for (const id in data) {
newQueriesArray.push(data[id]);
}
this.setState({ queriesArray: newQueriesArray });
}
});
}
search() {
this.refreshQueries(this.props);
}
render() {
const queries = this.props.queries;
return (
<div>
<div className="pane-cell pane-west m-t-5">
<div className="panel panel-default Workspace">
<div className="panel-heading">
<h6>
<i className="fa fa-search" /> Search Queries
</h6>
</div>
<div className="panel-body">
<input type="text" className="form-control" placeholder="Query Text" />
<Select
name="select-user"
placeholder="[User]"
options={['maxime_beauchemin', 'someone else']}
value={'maxime_beauchemin'}
className="m-t-10"
autosize={false}
/>
</div>
<div className="row space-1">
<div className="col-sm-2">
<Select
name="select-user"
placeholder="[User]"
options={this.state.userOptions}
value={this.state.userId}
isLoading={this.state.userLoading}
autosize={false}
onChange={this.changeUser.bind(this)}
/>
</div>
<div className="col-sm-2">
<DatabaseSelect onChange={this.onChange.bind(this)} />
</div>
<div className="col-sm-4">
<input
type="text"
onChange={this.changeSearch.bind(this)}
className="form-control input-sm"
placeholder="Search Results"
/>
</div>
<div className="col-sm-2">
<Select
name="select-state"
placeholder="[Query Status]"
options={STATUS_OPTIONS.map((s) => ({ value: s, label: s }))}
value={this.state.status}
isLoading={false}
autosize={false}
onChange={this.changeStatus.bind(this)}
/>
</div>
<Button bsSize="small" bsStyle="success" onClick={this.search.bind(this)}>
Search
</Button>
</div>
<div className="pane-cell">
<QueryTable
columns={['state', 'started', 'duration', 'rows', 'sql', 'actions']}
queries={queries}
/>
</div>
<Button>Search!</Button>
<QueryTable
columns={[
'state', 'dbId', 'userId',
'progress', 'rows', 'sql',
]}
queries={this.state.queriesArray}
/>
</div>
);
}
}
QuerySearch.propTypes = {
queries: React.PropTypes.array,
};
QuerySearch.defaultProps = {
queries: [],
};

function mapStateToProps(state) {
return {
queries: state.queries,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}

export default connect(mapStateToProps, mapDispatchToProps)(QuerySearch);
export default QuerySearch;
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ QueryTable.propTypes = {
queries: React.PropTypes.array,
};
QueryTable.defaultProps = {
columns: ['state', 'started', 'duration', 'progress', 'rows', 'sql', 'actions'],
columns: ['started', 'duration', 'rows'],
queries: [],
};

Expand Down
Loading