Files
superset2/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
Krist Wongsuphasawat cdd348ab94 Minor improvements to SQL Lab UI (#5662)
* Remove "for"

* add space

* Separate control to select table from database and schema.

* Adjust schema displays

* Fix caret and arrow position in Select and Tab

* Reduce space after caret in tab header

* Use translator

* Align icons in the pop-up menu in Sql Lab

* Add new table in front of the list (so it will appear on top)

* shorten message

* reduce line
2018-08-19 22:43:00 -07:00

257 lines
8.5 KiB
JavaScript

/* global window */
/* eslint no-undef: 2 */
import React from 'react';
import PropTypes from 'prop-types';
import { ControlLabel, Button } from 'react-bootstrap';
import Select from 'react-virtualized-select';
import createFilterOptions from 'react-select-fast-filter-options';
import TableElement from './TableElement';
import AsyncSelect from '../../components/AsyncSelect';
import { t } from '../../locales';
const $ = require('jquery');
const propTypes = {
queryEditor: PropTypes.object.isRequired,
height: PropTypes.number.isRequired,
tables: PropTypes.array,
actions: PropTypes.object,
database: PropTypes.object,
};
const defaultProps = {
tables: [],
actions: {},
};
class SqlEditorLeftBar extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
schemaLoading: false,
schemaOptions: [],
tableLoading: false,
tableOptions: [],
};
}
componentWillMount() {
this.fetchSchemas(this.props.queryEditor.dbId);
this.fetchTables(this.props.queryEditor.dbId, this.props.queryEditor.schema);
}
onDatabaseChange(db) {
const val = db ? db.value : null;
this.setState({ schemaOptions: [] });
this.props.actions.queryEditorSetSchema(this.props.queryEditor, null);
this.props.actions.queryEditorSetDb(this.props.queryEditor, val);
if (!(db)) {
this.setState({ tableOptions: [] });
} else {
this.fetchTables(val, this.props.queryEditor.schema);
this.fetchSchemas(val);
}
}
getTableNamesBySubStr(input) {
if (!this.props.queryEditor.dbId || !input) {
return Promise.resolve({ options: [] });
}
const url = `/superset/tables/${this.props.queryEditor.dbId}/` +
`${this.props.queryEditor.schema}/${input}`;
return $.get(url).then(data => ({ options: data.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.addDangerToast(t('It seems you don\'t have access to any database'));
}
return options;
}
resetState() {
this.props.actions.resetState();
}
fetchTables(dbId, schema, substr) {
// This can be large so it shouldn't be put in the Redux store
if (dbId && schema) {
this.setState({ tableLoading: true, tableOptions: [] });
const url = `/superset/tables/${dbId}/${schema}/${substr}/`;
$.get(url).done((data) => {
const filterOptions = createFilterOptions({ options: data.options });
this.setState({
filterOptions,
tableLoading: false,
tableOptions: data.options,
tableLength: data.tableLength,
});
})
.fail(() => {
this.setState({ tableLoading: false, tableOptions: [], tableLength: 0 });
this.props.actions.addDangerToast(t('Error while fetching table list'));
});
} else {
this.setState({ tableLoading: false, tableOptions: [], filterOptions: null });
}
}
changeTable(tableOpt) {
if (!tableOpt) {
this.setState({ tableName: '' });
return;
}
const namePieces = tableOpt.value.split('.');
let tableName = namePieces[0];
let schemaName = this.props.queryEditor.schema;
if (namePieces.length === 1) {
this.setState({ tableName });
} else {
schemaName = namePieces[0];
tableName = namePieces[1];
this.setState({ tableName });
this.props.actions.queryEditorSetSchema(this.props.queryEditor, schemaName);
this.fetchTables(this.props.queryEditor.dbId, schemaName);
}
this.props.actions.addTable(this.props.queryEditor, tableName, schemaName);
}
changeSchema(schemaOpt) {
const schema = (schemaOpt) ? schemaOpt.value : null;
this.props.actions.queryEditorSetSchema(this.props.queryEditor, schema);
this.fetchTables(this.props.queryEditor.dbId, schema);
}
fetchSchemas(dbId) {
const actualDbId = dbId || this.props.queryEditor.dbId;
if (actualDbId) {
this.setState({ schemaLoading: true });
const url = `/superset/schemas/${actualDbId}/`;
$.get(url).done((data) => {
const schemaOptions = data.schemas.map(s => ({ value: s, label: s }));
this.setState({ schemaOptions, schemaLoading: false });
})
.fail(() => {
this.setState({ schemaLoading: false, schemaOptions: [] });
this.props.actions.addDangerToast(t('Error while fetching schema list'));
});
}
}
closePopover(ref) {
this.refs[ref].hide();
}
render() {
const shouldShowReset = window.location.search === '?reset=1';
const tableMetaDataHeight = this.props.height - 130; // 130 is the height of the selects above
let tableSelectPlaceholder;
let tableSelectDisabled = false;
if (this.props.database && this.props.database.allow_multi_schema_metadata_fetch) {
tableSelectPlaceholder = t('Type to search ...');
} else {
tableSelectPlaceholder = t('Select table ');
tableSelectDisabled = true;
}
return (
<div className="clearfix sql-toolbar">
<div>
<AsyncSelect
dataEndpoint={
'/databaseasync/api/' +
'read?_flt_0_expose_in_sqllab=1&' +
'_oc_DatabaseAsync=database_name&' +
'_od_DatabaseAsync=asc'
}
onChange={this.onDatabaseChange.bind(this)}
onAsyncError={() => {
this.props.actions.addDangerToast(t('Error while fetching database list'));
}}
value={this.props.queryEditor.dbId}
databaseId={this.props.queryEditor.dbId}
actions={this.props.actions}
valueRenderer={o => (
<div>
<span className="text-muted">{t('Database:')}</span> {o.label}
</div>
)}
mutator={this.dbMutator.bind(this)}
placeholder={t('Select a database')}
autoSelect
/>
</div>
<div className="m-t-5">
<Select
name="select-schema"
placeholder={t('Select a schema (%s)', this.state.schemaOptions.length)}
options={this.state.schemaOptions}
value={this.props.queryEditor.schema}
valueRenderer={o => (
<div>
<span className="text-muted">{t('Schema:')}</span> {o.label}
</div>
)}
isLoading={this.state.schemaLoading}
autosize={false}
onChange={this.changeSchema.bind(this)}
/>
</div>
<hr />
<div className="m-t-5">
<ControlLabel>
{t('See table schema')}
&nbsp;
<small>
({this.state.tableOptions.length}
&nbsp;{t('in')}&nbsp;
<i>
{this.props.queryEditor.schema}
</i>)
</small>
</ControlLabel>
{this.props.queryEditor.schema &&
<Select
name="select-table"
ref="selectTable"
isLoading={this.state.tableLoading}
placeholder={t('Select table or type table name')}
autosize={false}
onChange={this.changeTable.bind(this)}
filterOptions={this.state.filterOptions}
options={this.state.tableOptions}
/>
}
{!this.props.queryEditor.schema &&
<Select
async
name="async-select-table"
ref="selectTable"
placeholder={tableSelectPlaceholder}
disabled={tableSelectDisabled}
autosize={false}
onChange={this.changeTable.bind(this)}
loadOptions={this.getTableNamesBySubStr.bind(this)}
/>
}
</div>
<hr />
<div className="m-t-5">
<div className="scrollbar-container">
<div className="scrollbar-content" style={{ height: tableMetaDataHeight }}>
{this.props.tables.map(table => (
<TableElement
table={table}
key={table.id}
actions={this.props.actions}
/>
))}
</div>
</div>
</div>
{shouldShowReset &&
<Button bsSize="small" bsStyle="danger" onClick={this.resetState.bind(this)}>
<i className="fa fa-bomb" /> {t('Reset State')}
</Button>
}
</div>
);
}
}
SqlEditorLeftBar.propTypes = propTypes;
SqlEditorLeftBar.defaultProps = defaultProps;
export default SqlEditorLeftBar;