You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2021/02/03 02:01:51 UTC
[superset] branch master updated: feat: add separate endpoint to
fetch function names for autocomplete (#12840)
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new ab3f4bd feat: add separate endpoint to fetch function names for autocomplete (#12840)
ab3f4bd is described below
commit ab3f4bd94b7dc75d7a64b07e494e83d730f614c7
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Tue Feb 2 18:01:01 2021 -0800
feat: add separate endpoint to fetch function names for autocomplete (#12840)
* WIP
* Add unit test for API
* Add spec
* Fix unit test
* Fix unit test
* Fix test
* Fix test
* Add period to error message
---
.../spec/javascripts/sqllab/SqlEditor_spec.jsx | 7 +++-
superset-frontend/src/SqlLab/actions/sqlLab.js | 22 ++++++++++
.../src/SqlLab/components/AceEditorWrapper.tsx | 5 +++
.../src/SqlLab/components/SqlEditor.jsx | 4 +-
.../src/SqlLab/components/SqlEditorLeftBar.jsx | 4 ++
.../src/SqlLab/reducers/getInitialState.js | 2 +
superset-frontend/src/SqlLab/reducers/sqlLab.js | 5 +++
superset/databases/api.py | 48 ++++++++++++++++++++--
superset/databases/schemas.py | 6 ++-
tests/databases/api_tests.py | 21 +++++++++-
10 files changed, 113 insertions(+), 11 deletions(-)
diff --git a/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx b/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx
index eca063c..2ba6268 100644
--- a/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx
+++ b/superset-frontend/spec/javascripts/sqllab/SqlEditor_spec.jsx
@@ -33,7 +33,10 @@ import ConnectedSouthPane from 'src/SqlLab/components/SouthPane';
import SqlEditor from 'src/SqlLab/components/SqlEditor';
import SqlEditorLeftBar from 'src/SqlLab/components/SqlEditorLeftBar';
import { Dropdown } from 'src/common/components';
-import { queryEditorSetSelectedText } from 'src/SqlLab/actions/sqlLab';
+import {
+ queryEditorSetFunctionNames,
+ queryEditorSetSelectedText,
+} from 'src/SqlLab/actions/sqlLab';
import { initialState, queries, table } from './fixtures';
@@ -45,7 +48,7 @@ const store = mockStore(initialState);
describe('SqlEditor', () => {
const mockedProps = {
- actions: { queryEditorSetSelectedText },
+ actions: { queryEditorSetFunctionNames, queryEditorSetSelectedText },
database: {},
queryEditorId: initialState.sqlLab.queryEditors[0].id,
latestQuery: queries[0],
diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js
index 647cfad..090d69c 100644
--- a/superset-frontend/src/SqlLab/actions/sqlLab.js
+++ b/superset-frontend/src/SqlLab/actions/sqlLab.js
@@ -57,6 +57,8 @@ export const QUERY_EDITOR_SET_QUERY_LIMIT = 'QUERY_EDITOR_SET_QUERY_LIMIT';
export const QUERY_EDITOR_SET_TEMPLATE_PARAMS =
'QUERY_EDITOR_SET_TEMPLATE_PARAMS';
export const QUERY_EDITOR_SET_SELECTED_TEXT = 'QUERY_EDITOR_SET_SELECTED_TEXT';
+export const QUERY_EDITOR_SET_FUNCTION_NAMES =
+ 'QUERY_EDITOR_SET_FUNCTION_NAMES';
export const QUERY_EDITOR_PERSIST_HEIGHT = 'QUERY_EDITOR_PERSIST_HEIGHT';
export const MIGRATE_QUERY_EDITOR = 'MIGRATE_QUERY_EDITOR';
export const MIGRATE_TAB_HISTORY = 'MIGRATE_TAB_HISTORY';
@@ -1300,3 +1302,23 @@ export function createCtasDatasource(vizOptions) {
});
};
}
+
+export function queryEditorSetFunctionNames(queryEditor, dbId) {
+ return function (dispatch) {
+ return SupersetClient.get({
+ endpoint: encodeURI(`/api/v1/database/${dbId}/function_names/`),
+ })
+ .then(({ json }) =>
+ dispatch({
+ type: QUERY_EDITOR_SET_FUNCTION_NAMES,
+ queryEditor,
+ functionNames: json.function_names,
+ }),
+ )
+ .catch(() =>
+ dispatch(
+ addDangerToast(t('An error occurred while fetching function names.')),
+ ),
+ );
+ };
+}
diff --git a/superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx b/superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx
index 95c6eb8..3c33f4c 100644
--- a/superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx
+++ b/superset-frontend/src/SqlLab/components/AceEditorWrapper.tsx
@@ -41,6 +41,7 @@ type HotKey = {
interface Props {
actions: {
queryEditorSetSelectedText: (edit: any, text: null | string) => void;
+ queryEditorSetFunctionNames: (queryEditor: object, dbId: number) => void;
addTable: (queryEditor: any, value: any, schema: any) => void;
};
autocomplete: boolean;
@@ -85,6 +86,10 @@ class AceEditorWrapper extends React.PureComponent<Props, State> {
componentDidMount() {
// Making sure no text is selected from previous mount
this.props.actions.queryEditorSetSelectedText(this.props.queryEditor, null);
+ this.props.actions.queryEditorSetFunctionNames(
+ this.props.queryEditor,
+ this.props.queryEditor.dbId,
+ );
this.setAutoCompleter(this.props);
}
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor.jsx b/superset-frontend/src/SqlLab/components/SqlEditor.jsx
index ee89a0f..3af2b1f 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor.jsx
@@ -474,9 +474,7 @@ class SqlEditor extends React.PureComponent {
sql={this.props.queryEditor.sql}
schemas={this.props.queryEditor.schemaOptions}
tables={this.props.queryEditor.tableOptions}
- functionNames={
- this.props.database ? this.props.database.function_names : []
- }
+ functionNames={this.props.queryEditor.functionNames}
extendedTables={this.props.tables}
height={`${aceEditorHeight}px`}
hotkeys={hotkeys}
diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx
index 550f623..7136ab0 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar.jsx
@@ -80,6 +80,10 @@ export default class SqlEditorLeftBar extends React.PureComponent {
onDbChange(db) {
this.props.actions.queryEditorSetDb(this.props.queryEditor, db.id);
+ this.props.actions.queryEditorSetFunctionNames(
+ this.props.queryEditor,
+ db.id,
+ );
}
onTableChange(tableName, schemaName) {
diff --git a/superset-frontend/src/SqlLab/reducers/getInitialState.js b/superset-frontend/src/SqlLab/reducers/getInitialState.js
index 60cd420..df55748 100644
--- a/superset-frontend/src/SqlLab/reducers/getInitialState.js
+++ b/superset-frontend/src/SqlLab/reducers/getInitialState.js
@@ -48,6 +48,7 @@ export default function getInitialState({
autorun: false,
templateParams: null,
dbId: defaultDbId,
+ functionNames: [],
queryLimit: common.conf.DEFAULT_SQLLAB_LIMIT,
validationResult: {
id: null,
@@ -80,6 +81,7 @@ export default function getInitialState({
autorun: activeTab.autorun,
templateParams: activeTab.template_params,
dbId: activeTab.database_id,
+ functionNames: [],
schema: activeTab.schema,
queryLimit: activeTab.query_limit,
validationResult: {
diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.js b/superset-frontend/src/SqlLab/reducers/sqlLab.js
index 8a94bcb..a5930d2 100644
--- a/superset-frontend/src/SqlLab/reducers/sqlLab.js
+++ b/superset-frontend/src/SqlLab/reducers/sqlLab.js
@@ -434,6 +434,11 @@ export default function sqlLabReducer(state = {}, action) {
dbId: action.dbId,
});
},
+ [actions.QUERY_EDITOR_SET_FUNCTION_NAMES]() {
+ return alterInArr(state, 'queryEditors', action.queryEditor, {
+ functionNames: action.functionNames,
+ });
+ },
[actions.QUERY_EDITOR_SET_SCHEMA]() {
return alterInArr(state, 'queryEditors', action.queryEditor, {
schema: action.schema,
diff --git a/superset/databases/api.py b/superset/databases/api.py
index e8273eb..ba665c1 100644
--- a/superset/databases/api.py
+++ b/superset/databases/api.py
@@ -53,6 +53,7 @@ from superset.databases.decorators import check_datasource_access
from superset.databases.filters import DatabaseFilter
from superset.databases.schemas import (
database_schemas_query_schema,
+ DatabaseFunctionNamesResponse,
DatabasePostSchema,
DatabasePutSchema,
DatabaseRelatedObjectsResponse,
@@ -83,6 +84,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
"schemas",
"test_connection",
"related_objects",
+ "function_names",
}
resource_name = "database"
class_permission_name = "Database"
@@ -126,7 +128,6 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
"explore_database_id",
"expose_in_sqllab",
"force_ctas_schema",
- "function_names",
"id",
]
add_columns = [
@@ -170,6 +171,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
}
openapi_spec_tag = "Database"
openapi_spec_component_schemas = (
+ DatabaseFunctionNamesResponse,
DatabaseRelatedObjectsResponse,
DatabaseTestConnectionSchema,
TableMetadataResponseSchema,
@@ -642,8 +644,8 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
500:
$ref: '#/components/responses/500'
"""
- dataset = DatabaseDAO.find_by_id(pk)
- if not dataset:
+ database = DatabaseDAO.find_by_id(pk)
+ if not database:
return self.response_404()
data = DatabaseDAO.get_related_objects(pk)
charts = [
@@ -799,3 +801,43 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
except DatabaseImportError as exc:
logger.exception("Import database failed")
return self.response_500(message=str(exc))
+
+ @expose("/<int:pk>/function_names/", methods=["GET"])
+ @protect()
+ @safe
+ @statsd_metrics
+ @event_logger.log_this_with_context(
+ action=lambda self, *args, **kwargs: f"{self.__class__.__name__}"
+ f".function_names",
+ log_to_statsd=False,
+ )
+ def function_names(self, pk: int) -> Response:
+ """Get function names supported by a database
+ ---
+ get:
+ description:
+ Get function names supported by a database
+ parameters:
+ - in: path
+ name: pk
+ schema:
+ type: integer
+ responses:
+ 200:
+ 200:
+ description: Query result
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/DatabaseFunctionNamesResponse"
+ 401:
+ $ref: '#/components/responses/401'
+ 404:
+ $ref: '#/components/responses/404'
+ 500:
+ $ref: '#/components/responses/500'
+ """
+ database = DatabaseDAO.find_by_id(pk)
+ if not database:
+ return self.response_404()
+ return self.response(200, function_names=database.function_names,)
diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py
index 2c705f3..0fbcf1d 100644
--- a/superset/databases/schemas.py
+++ b/superset/databases/schemas.py
@@ -413,11 +413,15 @@ class DatabaseRelatedObjectsResponse(Schema):
dashboards = fields.Nested(DatabaseRelatedDashboards)
+class DatabaseFunctionNamesResponse(Schema):
+ function_names = fields.List(fields.String())
+
+
class ImportV1DatabaseExtraSchema(Schema):
metadata_params = fields.Dict(keys=fields.Str(), values=fields.Raw())
engine_params = fields.Dict(keys=fields.Str(), values=fields.Raw())
metadata_cache_timeout = fields.Dict(keys=fields.Str(), values=fields.Integer())
- schemas_allowed_for_csv_upload = fields.List(fields.String)
+ schemas_allowed_for_csv_upload = fields.List(fields.String())
cost_estimate_enabled = fields.Boolean()
diff --git a/tests/databases/api_tests.py b/tests/databases/api_tests.py
index 8d603cc..a72c7eb 100644
--- a/tests/databases/api_tests.py
+++ b/tests/databases/api_tests.py
@@ -137,7 +137,6 @@ class TestDatabaseApi(SupersetTestCase):
"explore_database_id",
"expose_in_sqllab",
"force_ctas_schema",
- "function_names",
"id",
]
self.assertGreater(response["count"], 0)
@@ -589,7 +588,8 @@ class TestDatabaseApi(SupersetTestCase):
assert rv.status_code == 200
assert "can_read" in data["permissions"]
assert "can_write" in data["permissions"]
- assert len(data["permissions"]) == 2
+ assert "can_function_names" in data["permissions"]
+ assert len(data["permissions"]) == 3
def test_get_invalid_database_table_metadata(self):
"""
@@ -1125,3 +1125,20 @@ class TestDatabaseApi(SupersetTestCase):
db.session.delete(database)
db.session.commit()
+
+ @mock.patch("superset.db_engine_specs.base.BaseEngineSpec.get_function_names",)
+ def test_function_names(self, mock_get_function_names):
+ example_db = get_example_database()
+ if example_db.backend in {"hive", "presto"}:
+ return
+
+ mock_get_function_names.return_value = ["AVG", "MAX", "SUM"]
+
+ self.login(username="admin")
+ uri = "api/v1/database/1/function_names/"
+
+ rv = self.client.get(uri)
+ response = json.loads(rv.data.decode("utf-8"))
+
+ assert rv.status_code == 200
+ assert response == {"function_names": ["AVG", "MAX", "SUM"]}