Files
superset2/superset/assets/javascripts/SqlLab/components/SqlEditorLeftBar.jsx
Maxime Beauchemin d522292b01 [sql lab] option to disable cross schema search (#4551)
* [sql lab] disable cross schema search

This is killing our metastore as people type it emits large
all-table-dump as they hit the keystroke. It never returns as it times
out and hammers the poor metastore.

Also some improvements around the disabling the table select on the left
panel and having the table name not be sticky.

* typo
2018-03-09 14:54:39 -08:00

245 lines
8.1 KiB
JavaScript

/* global notify */
import React from 'react';
import PropTypes from 'prop-types';
import { 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 $ = window.$ = 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.addAlert({
bsStyle: 'danger',
msg: 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 });
notify.error(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: [] });
notify.error(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={() => notify.error(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>
<div className="m-t-5">
{this.props.queryEditor.schema &&
<Select
name="select-table"
ref="selectTable"
isLoading={this.state.tableLoading}
placeholder={t('Add a table (%s)', this.state.tableOptions.length)}
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;