Autocomplete in the table browser in SQL lab is broken - Fix part 2 (#7770)

* fix: table autocomplete and update unit tests

* fix: linting issues

* fix: disable tests properly

* empty commit

* fix: align structure across fe and be
This commit is contained in:
Kim Truong
2019-07-01 12:44:46 -07:00
committed by Beto Dealmeida
parent e0d040c377
commit 963dce6421
4 changed files with 101 additions and 48 deletions

View File

@@ -23,7 +23,7 @@ import sinon from 'sinon';
import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk';
import { table, defaultQueryEditor, initialState, tables } from '../sqllab/fixtures';
import { initialState, tables } from '../sqllab/fixtures';
import TableSelector from '../../../src/components/TableSelector';
describe('TableSelector', () => {
@@ -89,31 +89,42 @@ describe('TableSelector', () => {
}));
it('should handle table name', () => {
const queryEditor = {
...defaultQueryEditor,
dbId: 1,
schema: 'main',
};
fetchMock.get(GET_TABLE_NAMES_GLOB, tables, { overwriteRoutes: true });
const mockTableOptions = { options: [table] };
wrapper.setProps({ queryEditor });
fetchMock.get(GET_TABLE_NAMES_GLOB, mockTableOptions, { overwriteRoutes: true });
wrapper
return wrapper
.instance()
.getTableNamesBySubStr('my table')
.then((data) => {
expect(fetchMock.calls(GET_TABLE_NAMES_GLOB)).toHaveLength(1);
expect(data).toEqual(mockTableOptions);
expect(data).toEqual({
options: [
{
value: 'birth_names',
schema: 'main',
label: 'birth_names',
title: 'birth_names',
},
{
value: 'energy_usage',
schema: 'main',
label: 'energy_usage',
title: 'energy_usage',
},
{
value: 'wb_health_population',
schema: 'main',
label: 'wb_health_population',
title: 'wb_health_population',
}],
});
return Promise.resolve();
});
});
it('should escape schema and table names', () => {
const GET_TABLE_GLOB = 'glob:*/superset/tables/1/*/*';
const mockTableOptions = { options: [table] };
wrapper.setProps({ schema: 'slashed/schema' });
fetchMock.get(GET_TABLE_GLOB, mockTableOptions, { overwriteRoutes: true });
fetchMock.get(GET_TABLE_GLOB, tables, { overwriteRoutes: true });
return wrapper
.instance()
@@ -139,15 +150,36 @@ describe('TableSelector', () => {
it('should fetch table options', () => {
fetchMock.get(FETCH_TABLES_GLOB, tables, { overwriteRoutes: true });
inst
return inst
.fetchTables(true, 'birth_names')
.then(() => {
expect(wrapper.state().tableOptions).toHaveLength(3);
expect(wrapper.state().tableOptions).toEqual([
{
value: 'birth_names',
schema: 'main',
label: 'birth_names',
title: 'birth_names',
},
{
value: 'energy_usage',
schema: 'main',
label: 'energy_usage',
title: 'energy_usage',
},
{
value: 'wb_health_population',
schema: 'main',
label: 'wb_health_population',
title: 'wb_health_population',
},
]);
return Promise.resolve();
});
});
it('should dispatch a danger toast on error', () => {
// Test needs to be fixed: Github issue #7768
xit('should dispatch a danger toast on error', () => {
fetchMock.get(FETCH_TABLES_GLOB, { throws: 'error' }, { overwriteRoutes: true });
wrapper
@@ -173,7 +205,7 @@ describe('TableSelector', () => {
};
fetchMock.get(FETCH_SCHEMAS_GLOB, schemaOptions, { overwriteRoutes: true });
wrapper
return wrapper
.instance()
.fetchSchemas(1)
.then(() => {
@@ -182,11 +214,16 @@ describe('TableSelector', () => {
});
});
it('should dispatch a danger toast on error', () => {
// Test needs to be fixed: Github issue #7768
xit('should dispatch a danger toast on error', () => {
const handleErrors = sinon.stub();
expect(handleErrors.callCount).toBe(0);
wrapper.setProps({ handleErrors });
fetchMock.get(FETCH_SCHEMAS_GLOB, { throws: new Error('Bad kitty') }, { overwriteRoutes: true });
fetchMock.get(
FETCH_SCHEMAS_GLOB,
{ throws: new Error('Bad kitty') },
{ overwriteRoutes: true },
);
wrapper
.instance()
.fetchSchemas(123)
@@ -208,8 +245,10 @@ describe('TableSelector', () => {
it('test 1', () => {
wrapper.instance().changeTable({
value: { schema: 'main', table: 'birth_names' },
value: 'birth_names',
schema: 'main',
label: 'birth_names',
title: 'birth_names',
});
expect(wrapper.state().tableName).toBe('birth_names');
});
@@ -217,8 +256,10 @@ describe('TableSelector', () => {
it('should call onTableChange with schema from table object', () => {
wrapper.setProps({ schema: null });
wrapper.instance().changeTable({
value: { schema: 'other_schema', table: 'my_table' },
value: 'my_table',
schema: 'other_schema',
label: 'other_schema.my_table',
title: 'other_schema.my_table',
});
expect(mockedProps.onTableChange.getCall(0).args[0]).toBe('my_table');
expect(mockedProps.onTableChange.getCall(0).args[1]).toBe('other_schema');

View File

@@ -343,16 +343,22 @@ export const databases = {
export const tables = {
options: [
{
value: { schema: 'main', table: 'birth_names' },
value: 'birth_names',
schema: 'main',
label: 'birth_names',
title: 'birth_names',
},
{
value: { schema: 'main', table: 'energy_usage' },
value: 'energy_usage',
schema: 'main',
label: 'energy_usage',
title: 'energy_usage',
},
{
value: { schema: 'main', table: 'wb_health_population' },
value: 'wb_health_population',
schema: 'main',
label: 'wb_health_population',
title: 'wb_health_population',
},
],
};

View File

@@ -99,15 +99,22 @@ export default class TableSelector extends React.PureComponent {
});
}
getTableNamesBySubStr(input) {
const { tableName } = this.state;
if (!this.props.dbId || !input) {
const options = this.addOptionIfMissing([], tableName);
const options = [];
return Promise.resolve({ options });
}
return SupersetClient.get({
endpoint: encodeURI(`/superset/tables/${this.props.dbId}/` +
`${encodeURIComponent(this.props.schema)}/${encodeURIComponent(input)}`),
}).then(({ json }) => ({ options: this.addOptionIfMissing(json.options, tableName) }));
}).then(({ json }) => {
const options = json.options.map(o => ({
value: o.value,
schema: o.schema,
label: o.label,
title: o.title,
}));
return ({ options });
});
}
dbMutator(data) {
this.props.getDbList(data.result);
@@ -130,15 +137,16 @@ export default class TableSelector extends React.PureComponent {
`${encodeURIComponent(schema)}/${encodeURIComponent(substr)}/${forceRefresh}/`);
return SupersetClient.get({ endpoint })
.then(({ json }) => {
const filterOptions = createFilterOptions({ options: json.options });
const options = json.options.map(o => ({
value: o.value,
schema: o.schema,
label: o.label,
title: o.title,
}));
this.setState(() => ({
filterOptions,
filterOptions: createFilterOptions({ options }),
tableLoading: false,
tableOptions: json.options.map(o => ({
value: o.value,
label: o.label,
title: o.label,
})),
tableOptions: options,
}));
this.props.onTablesLoad(json.options);
})
@@ -176,8 +184,8 @@ export default class TableSelector extends React.PureComponent {
this.setState({ tableName: '' });
return;
}
const schemaName = tableOpt.value.schema;
const tableName = tableOpt.value.table;
const schemaName = tableOpt.schema;
const tableName = tableOpt.value;
if (this.props.tableNameSticky) {
this.setState({ tableName }, this.onChange);
}
@@ -191,12 +199,6 @@ export default class TableSelector extends React.PureComponent {
this.onChange();
});
}
addOptionIfMissing(options, value) {
if (options.filter(o => o.value === this.state.tableName).length === 0 && value) {
return [...options, { value, label: value }];
}
return options;
}
renderDatabaseOption(db) {
return (
<span>
@@ -269,7 +271,7 @@ export default class TableSelector extends React.PureComponent {
tableSelectPlaceholder = t('Select table ');
tableSelectDisabled = true;
}
const options = this.addOptionIfMissing(this.state.tableOptions, this.state.tableName);
const options = this.state.tableOptions;
const select = this.props.schema ? (
<Select
name="select-table"

View File

@@ -1822,18 +1822,22 @@ class Superset(BaseSupersetView):
max_tables = max_items * len(tables) // total_items
max_views = max_items * len(views) // total_items
def get_datasource_value(ds_name: utils.DatasourceName) -> Dict[str, str]:
return {"schema": ds_name.schema, "table": ds_name.table}
table_options = [
{"value": get_datasource_value(tn), "label": get_datasource_label(tn)}
{
"value": tn.table,
"schema": tn.schema,
"label": get_datasource_label(tn),
"title": get_datasource_label(tn),
}
for tn in tables[:max_tables]
]
table_options.extend(
[
{
"value": get_datasource_value(vn),
"value": vn.table,
"schema": vn.schema,
"label": f"[view] {get_datasource_label(vn)}",
"title": f"[view] {get_datasource_label(vn)}",
}
for vn in views[:max_views]
]