From 4f6d3b4991cbcfd211f1d686fbb68431ac6d9de4 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Wed, 21 Dec 2022 21:39:41 -0300 Subject: [PATCH 1/8] chore: deprecate /superset/tables & move to api/v1 --- .../cypress/integration/sqllab/query.test.ts | 2 +- .../components/SqlEditor/SqlEditor.test.jsx | 2 +- .../SqlEditorLeftBar.test.jsx | 2 +- .../src/hooks/apiResources/tables.test.ts | 6 +- .../src/hooks/apiResources/tables.ts | 23 +++- .../AddDataset/LeftPanel/LeftPanel.test.tsx | 2 +- .../dataset/AddDataset/LeftPanel/index.tsx | 28 +++-- superset/constants.py | 1 + superset/databases/api.py | 77 ++++++++++++ superset/databases/commands/exceptions.py | 5 + superset/databases/commands/tables.py | 116 ++++++++++++++++++ superset/databases/schemas.py | 14 +++ superset/views/core.py | 1 + 13 files changed, 256 insertions(+), 23 deletions(-) create mode 100644 superset/databases/commands/tables.py diff --git a/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.ts b/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.ts index f75a29bc886d..0ea3b7bda19a 100644 --- a/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/sqllab/query.test.ts @@ -82,7 +82,7 @@ describe('SqlLab query panel', () => { }); it.skip('successfully saves a query', () => { - cy.intercept('superset/tables/**').as('getTables'); + cy.intercept('api/v1/database/**/tables/**').as('getTables'); cy.intercept('savedqueryviewapi/**').as('getSavedQuery'); const query = diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx index 614878e49d9f..c788216303ae 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.jsx @@ -54,7 +54,7 @@ jest.mock('src/SqlLab/components/SqlEditorLeftBar', () => () => ( const MOCKED_SQL_EDITOR_HEIGHT = 500; fetchMock.get('glob:*/api/v1/database/*', { result: [] }); -fetchMock.get('glob:*/superset/tables/*', { options: [] }); +fetchMock.get('glob:*/api/v1/database/*/tables/*', { options: [] }); fetchMock.post('glob:*/sql_json/*', { result: [] }); const middlewares = [thunk]; diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx index fa8363f9e2aa..208a9d4de15a 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx @@ -42,7 +42,7 @@ const mockStore = configureStore(middlewares); const store = mockStore(initialState); fetchMock.get('glob:*/api/v1/database/*/schemas/?*', { result: [] }); -fetchMock.get('glob:*/superset/tables/**', { +fetchMock.get('glob:*/api/v1/database/*/tables/*', { options: [ { label: 'ab_user', diff --git a/superset-frontend/src/hooks/apiResources/tables.test.ts b/superset-frontend/src/hooks/apiResources/tables.test.ts index 6ff19cb401fb..02fb4de8d385 100644 --- a/superset-frontend/src/hooks/apiResources/tables.test.ts +++ b/superset-frontend/src/hooks/apiResources/tables.test.ts @@ -103,7 +103,9 @@ describe('useTables hook', () => { }); expect(SupersetClient.get).toHaveBeenCalledTimes(1); expect(SupersetClient.get).toHaveBeenCalledWith({ - endpoint: `/superset/tables/${expectDbId}/${expectedSchema}/${forceRefresh}/`, + endpoint: `/api/v1/database/${expectDbId}/tables/?q=(force:!${ + forceRefresh ? 't' : 'f' + },schema_name:${expectedSchema})`, }); expect(result.current.data).toEqual(expectedData); await act(async () => { @@ -111,7 +113,7 @@ describe('useTables hook', () => { }); expect(SupersetClient.get).toHaveBeenCalledTimes(2); expect(SupersetClient.get).toHaveBeenCalledWith({ - endpoint: `/superset/tables/${expectDbId}/${expectedSchema}/true/`, + endpoint: `/api/v1/database/${expectDbId}/tables/?q=(force:!t,schema_name:${expectedSchema})`, }); expect(result.current.data).toEqual(expectedData); }); diff --git a/superset-frontend/src/hooks/apiResources/tables.ts b/superset-frontend/src/hooks/apiResources/tables.ts index 994a7380f496..af2fa778f1de 100644 --- a/superset-frontend/src/hooks/apiResources/tables.ts +++ b/superset-frontend/src/hooks/apiResources/tables.ts @@ -18,6 +18,7 @@ */ import { useRef } from 'react'; import { useQuery, UseQueryOptions } from 'react-query'; +import rison from 'rison'; import { SupersetClient } from '@superset-ui/core'; export type FetchTablesQueryParams = { @@ -39,11 +40,16 @@ export interface Table { } type QueryData = { - json: { options: Table[]; tableLength: number }; + json: { + result: { + options: Table[]; + tableLength: number; + }; + }; response: Response; }; -export type Data = QueryData['json'] & { +export type Data = QueryData['json']['result'] & { hasMore: boolean; }; @@ -53,10 +59,15 @@ export function fetchTables({ forceRefresh, }: FetchTablesQueryParams) { const encodedSchema = schema ? encodeURIComponent(schema) : ''; + const params = rison.encode({ + force: forceRefresh, + schema_name: encodedSchema, + }); + // TODO: Would be nice to add pagination in a follow-up. Needs endpoint changes. - const endpoint = `/superset/tables/${ + const endpoint = `/api/v1/database/${ dbId ?? 'undefined' - }/${encodedSchema}/${forceRefresh}/`; + }/tables/?q=${params}`; return SupersetClient.get({ endpoint }) as Promise; } @@ -72,8 +83,8 @@ export function useTables(options: Params) { () => fetchTables({ ...params, forceRefresh: forceRefreshRef.current }), { select: ({ json }) => ({ - ...json, - hasMore: json.tableLength > json.options.length, + ...json.result, + hasMore: json.result.tableLength > json.result.options.length, }), enabled: Boolean(dbId && schema), onSuccess, diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx index 7457f0c25095..931c747a0554 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx @@ -24,7 +24,7 @@ import LeftPanel from 'src/views/CRUD/data/dataset/AddDataset/LeftPanel'; const databasesEndpoint = 'glob:*/api/v1/database/?q*'; const schemasEndpoint = 'glob:*/api/v1/database/*/schemas*'; -const tablesEndpoint = 'glob:*/superset/tables*'; +const tablesEndpoint = 'glob:*/api/v1/database/*/tables/?q*'; fetchMock.get(databasesEndpoint, { count: 2, diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx index 4f7dfca196f4..45e5e481ebeb 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx @@ -17,6 +17,7 @@ * under the License. */ import React, { useEffect, useState, SetStateAction, Dispatch } from 'react'; +import rison from 'rison'; import { SupersetClient, t, @@ -177,15 +178,17 @@ export default function LeftPanel({ const getTablesList = (url: string) => { SupersetClient.get({ url }) .then(({ json }) => { - const options: TableOption[] = json.options.map((table: Table) => { - const option: TableOption = { - value: table.value, - label: , - text: table.label, - }; + const options: TableOption[] = json.result.options.map( + (table: Table) => { + const option: TableOption = { + value: table.value, + label: , + text: table.label, + }; - return option; - }); + return option; + }, + ); setTableOptions(options); setLoadTables(false); @@ -213,9 +216,12 @@ export default function LeftPanel({ useEffect(() => { if (loadTables) { - const endpoint = encodeURI( - `/superset/tables/${dbId}/${encodedSchema}/${refresh}/`, - ); + const params = rison.encode({ + force: refresh, + schema_name: encodedSchema, + }); + + const endpoint = `/api/v1/database/${dbId}/tables/?q=${params}`; getTablesList(endpoint); } }, [loadTables]); diff --git a/superset/constants.py b/superset/constants.py index 7d759acf6741..154dab297e1a 100644 --- a/superset/constants.py +++ b/superset/constants.py @@ -115,6 +115,7 @@ class RouteMethod: # pylint: disable=too-few-public-methods "put": "write", "related": "read", "related_objects": "read", + "tables": "read", "schemas": "read", "select_star": "read", "table_metadata": "read", diff --git a/superset/databases/api.py b/superset/databases/api.py index 3f737ec4da1a..d1b22968c95f 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -44,11 +44,13 @@ DatabaseDeleteFailedError, DatabaseInvalidError, DatabaseNotFoundError, + DatabaseTablesUnexpectedError, DatabaseUpdateFailedError, InvalidParametersError, ) from superset.databases.commands.export import ExportDatabasesCommand from superset.databases.commands.importers.dispatcher import ImportDatabasesCommand +from superset.databases.commands.tables import TablesDatabaseCommand from superset.databases.commands.test_connection import TestConnectionDatabaseCommand from superset.databases.commands.update import UpdateDatabaseCommand from superset.databases.commands.validate import ValidateDatabaseParametersCommand @@ -58,10 +60,12 @@ from superset.databases.filters import DatabaseFilter, DatabaseUploadEnabledFilter from superset.databases.schemas import ( database_schemas_query_schema, + database_tables_query_schema, DatabaseFunctionNamesResponse, DatabasePostSchema, DatabasePutSchema, DatabaseRelatedObjectsResponse, + DatabaseTablesResponse, DatabaseTestConnectionSchema, DatabaseValidateParametersSchema, get_export_ids_schema, @@ -97,6 +101,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi): include_route_methods = RouteMethod.REST_MODEL_VIEW_CRUD_SET | { RouteMethod.EXPORT, RouteMethod.IMPORT, + "tables", "table_metadata", "table_extra_metadata", "select_star", @@ -202,6 +207,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi): apispec_parameter_schemas = { "database_schemas_query_schema": database_schemas_query_schema, + "database_tables_query_schema": database_tables_query_schema, "get_export_ids_schema": get_export_ids_schema, } @@ -209,6 +215,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi): openapi_spec_component_schemas = ( DatabaseFunctionNamesResponse, DatabaseRelatedObjectsResponse, + DatabaseTablesResponse, DatabaseTestConnectionSchema, DatabaseValidateParametersSchema, TableExtraMetadataResponseSchema, @@ -493,6 +500,76 @@ def schemas(self, pk: int, **kwargs: Any) -> FlaskResponse: except SupersetException as ex: return self.response(ex.status, message=ex.message) + @expose("//tables/", methods=["GET"]) + @protect() + @safe + @rison(database_tables_query_schema) + @statsd_metrics + @event_logger.log_this_with_context( + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}" f".tables", + log_to_statsd=False, + ) + def tables(self, pk: int, **kwargs: Any) -> FlaskResponse: + """Get a list of tables for given database + --- + get: + description: Get a list of tables for given database + parameters: + - in: path + schema: + type: integer + name: pk + description: The database id + - in: query + name: q + content: + application/json: + schema: + $ref: '#/components/schemas/database_tables_query_schema' + responses: + 200: + description: Tables list + content: + application/json: + schema: + type: object + properties: + result: + description: >- + A List of tables for given database + type: object + properties: + tableLength: + type: integer + options: + type: array + items: + $ref: '#/components/schemas/DatabaseTablesResponse' + 400: + $ref: '#/components/responses/400' + 401: + $ref: '#/components/responses/401' + 404: + $ref: '#/components/responses/404' + 500: + $ref: '#/components/responses/500' + """ + force = kwargs["rison"].get("force", False) + schema_name = kwargs["rison"].get("schema_name", "") + if not schema_name: + return self.response_422("Schema undefined") + + try: + command = TablesDatabaseCommand(pk, schema_name, force) + payload = command.run() + return self.response(200, result=payload) + except DatabaseNotFoundError: + return self.response_404() + except SupersetException as ex: + return self.response(ex.status, message=ex.message) + except DatabaseTablesUnexpectedError as ex: + return self.response_500(ex.message) + @expose("//table///", methods=["GET"]) @protect() @check_datasource_access diff --git a/superset/databases/commands/exceptions.py b/superset/databases/commands/exceptions.py index a49abd3449d0..8161e1047db6 100644 --- a/superset/databases/commands/exceptions.py +++ b/superset/databases/commands/exceptions.py @@ -137,6 +137,11 @@ class DatabaseTestConnectionUnexpectedError(SupersetErrorsException): message = _("Unexpected error occurred, please check your logs for details") +class DatabaseTablesUnexpectedError(Exception): + status = 422 + message = _("Unexpected error occurred, please check your logs for details") + + class NoValidatorConfigFoundError(SupersetErrorException): status = 422 message = _("no SQL validator is configured") diff --git a/superset/databases/commands/tables.py b/superset/databases/commands/tables.py new file mode 100644 index 000000000000..a947701608b6 --- /dev/null +++ b/superset/databases/commands/tables.py @@ -0,0 +1,116 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +import logging +from typing import Any, Dict, cast + +from flask_babel import gettext as _ + +from superset.commands.base import BaseCommand +from superset.connectors.sqla.models import SqlaTable +from superset.databases.commands.exceptions import ( + DatabaseNotFoundError, + DatabaseTablesUnexpectedError, +) +from superset.databases.dao import DatabaseDAO +from superset.exceptions import SupersetException +from superset.extensions import db, security_manager +from superset.models.core import Database +from superset.utils.core import DatasourceName + +logger = logging.getLogger(__name__) + + +class TablesDatabaseCommand(BaseCommand): + _model: Database + + def __init__(self, db_id: int, schema_name: str, force: bool): + self._db_id = db_id + self._schema_name = schema_name + self._force = force + + def run(self) -> Dict[str, Any]: + self.validate() + try: + tables = security_manager.get_datasources_accessible_by_user( + database=self._model, + schema=self._schema_name, + datasource_names=sorted( + DatasourceName(*datasource_name) + for datasource_name in self._model.get_all_table_names_in_schema( + schema=self._schema_name, + force=self._force, + cache=self._model.table_cache_enabled, + cache_timeout=self._model.table_cache_timeout, + ) + ), + ) + + views = security_manager.get_datasources_accessible_by_user( + database=self._model, + schema=self._schema_name, + datasource_names=sorted( + DatasourceName(*datasource_name) + for datasource_name in self._model.get_all_view_names_in_schema( + schema=self._schema_name, + force=self._force, + cache=self._model.table_cache_enabled, + cache_timeout=self._model.table_cache_timeout, + ) + ), + ) + + extra_dict_by_name = { + table.name: table.extra_dict + for table in ( + db.session.query(SqlaTable).filter( + SqlaTable.database_id == self._model.id, + SqlaTable.schema == self._schema_name, + ) + ).all() + } + + options = sorted( + [ + { + "value": table.table, + "type": "table", + "extra": extra_dict_by_name.get(table.table, None), + } + for table in tables + ] + + [ + { + "value": view.table, + "type": "view", + } + for view in views + ], + key=lambda item: item["value"], + ) + + payload = {"tableLength": len(tables) + len(views), "options": options} + return payload + except SupersetException as ex: + raise ex + except Exception as ex: + logger.error(ex) + raise DatabaseTablesUnexpectedError(ex) + + def validate(self) -> None: + self._model = cast(Database, DatabaseDAO.find_by_id(self._db_id)) + if not self._model: + raise DatabaseNotFoundError() diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py index ef22374ef8a8..837b7825aa94 100644 --- a/superset/databases/schemas.py +++ b/superset/databases/schemas.py @@ -39,6 +39,14 @@ from superset.utils.core import markdown, parse_ssl_cert database_schemas_query_schema = { + "type": "object", + "properties": { + "force": {"type": "boolean"}, + "schema_name": {"type": "string"}, + }, +} + +database_tables_query_schema = { "type": "object", "properties": {"force": {"type": "boolean"}}, } @@ -555,6 +563,12 @@ class SchemasResponseSchema(Schema): result = fields.List(fields.String(description="A database schema name")) +class DatabaseTablesResponse(Schema): + extra = (fields.Dict(),) + type = (fields.String(),) + value = (fields.String(),) + + class ValidateSQLRequest(Schema): sql = fields.String(required=True, description="SQL statement to validate") schema = fields.String(required=False, allow_none=True) diff --git a/superset/views/core.py b/superset/views/core.py index 534f8f667d70..e9b9482430fc 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -1139,6 +1139,7 @@ def save_or_overwrite_slice( @event_logger.log_this @expose("/tables///") @expose("/tables////") + @deprecated() def tables( # pylint: disable=no-self-use self, db_id: int, From ca2d94362fc9a204b9a6bc78353b39846fc47a4b Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 22 Dec 2022 00:21:02 -0300 Subject: [PATCH 2/8] cleanup --- .../SqlEditorLeftBar.test.jsx | 16 ++--- .../TableSelector/TableSelector.test.tsx | 14 +++-- .../src/hooks/apiResources/tables.test.ts | 60 ++++++++++--------- .../AddDataset/LeftPanel/LeftPanel.test.tsx | 14 +++-- superset/databases/api.py | 2 +- superset/databases/commands/tables.py | 5 +- superset/databases/schemas.py | 16 ++--- 7 files changed, 67 insertions(+), 60 deletions(-) diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx index 208a9d4de15a..375094c6c665 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx @@ -43,13 +43,15 @@ const store = mockStore(initialState); fetchMock.get('glob:*/api/v1/database/*/schemas/?*', { result: [] }); fetchMock.get('glob:*/api/v1/database/*/tables/*', { - options: [ - { - label: 'ab_user', - value: 'ab_user', - }, - ], - tableLength: 1, + result: { + options: [ + { + label: 'ab_user', + value: 'ab_user', + }, + ], + tableLength: 1, + } }); const renderAndWait = (props, store) => diff --git a/superset-frontend/src/components/TableSelector/TableSelector.test.tsx b/superset-frontend/src/components/TableSelector/TableSelector.test.tsx index 5b3fac1f94cf..388a2aa3c975 100644 --- a/superset-frontend/src/components/TableSelector/TableSelector.test.tsx +++ b/superset-frontend/src/components/TableSelector/TableSelector.test.tsx @@ -51,12 +51,14 @@ const getSchemaMockFunction = async () => const getTableMockFunction = async () => ({ json: { - options: [ - { label: 'table_a', value: 'table_a' }, - { label: 'table_b', value: 'table_b' }, - { label: 'table_c', value: 'table_c' }, - { label: 'table_d', value: 'table_d' }, - ], + result: { + options: [ + { label: 'table_a', value: 'table_a' }, + { label: 'table_b', value: 'table_b' }, + { label: 'table_c', value: 'table_c' }, + { label: 'table_d', value: 'table_d' }, + ], + }, }, } as any); diff --git a/superset-frontend/src/hooks/apiResources/tables.test.ts b/superset-frontend/src/hooks/apiResources/tables.test.ts index 02fb4de8d385..ebddbdbb76c7 100644 --- a/superset-frontend/src/hooks/apiResources/tables.test.ts +++ b/superset-frontend/src/hooks/apiResources/tables.test.ts @@ -23,47 +23,51 @@ import { useTables } from './tables'; const fakeApiResult = { json: { - options: [ - { - id: 1, - name: 'fake api result1', - label: 'fake api label1', - }, - { - id: 2, - name: 'fake api result2', - label: 'fake api label2', - }, - ], - tableLength: 2, + result: { + options: [ + { + id: 1, + name: 'fake api result1', + label: 'fake api label1', + }, + { + id: 2, + name: 'fake api result2', + label: 'fake api label2', + }, + ], + tableLength: 2, + }, }, }; const fakeHasMoreApiResult = { json: { - options: [ - { - id: 1, - name: 'fake api result1', - label: 'fake api label1', - }, - { - id: 2, - name: 'fake api result2', - label: 'fake api label2', - }, - ], - tableLength: 4, + result: { + options: [ + { + id: 1, + name: 'fake api result1', + label: 'fake api label1', + }, + { + id: 2, + name: 'fake api result2', + label: 'fake api label2', + }, + ], + tableLength: 4, + }, }, }; const expectedData = { - ...fakeApiResult.json, + ...fakeApiResult.json.result, hasMore: false, }; const expectedHasMoreData = { - ...fakeHasMoreApiResult.json, + ...fakeHasMoreApiResult.json.result, hasMore: true, }; diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx index 931c747a0554..25ab55b716e8 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx @@ -136,12 +136,14 @@ fetchMock.get(schemasEndpoint, { }); fetchMock.get(tablesEndpoint, { - tableLength: 3, - options: [ - { value: 'Sheet1', type: 'table', extra: null }, - { value: 'Sheet2', type: 'table', extra: null }, - { value: 'Sheet3', type: 'table', extra: null }, - ], + result: { + tableLength: 3, + options: [ + { value: 'Sheet1', type: 'table', extra: null }, + { value: 'Sheet2', type: 'table', extra: null }, + { value: 'Sheet3', type: 'table', extra: null }, + ], + }, }); const mockFun = jest.fn(); diff --git a/superset/databases/api.py b/superset/databases/api.py index d1b22968c95f..962c02ca7f0f 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -500,7 +500,7 @@ def schemas(self, pk: int, **kwargs: Any) -> FlaskResponse: except SupersetException as ex: return self.response(ex.status, message=ex.message) - @expose("//tables/", methods=["GET"]) + @expose("//tables/") @protect() @safe @rison(database_tables_query_schema) diff --git a/superset/databases/commands/tables.py b/superset/databases/commands/tables.py index a947701608b6..d2e833ba93aa 100644 --- a/superset/databases/commands/tables.py +++ b/superset/databases/commands/tables.py @@ -17,8 +17,6 @@ import logging from typing import Any, Dict, cast -from flask_babel import gettext as _ - from superset.commands.base import BaseCommand from superset.connectors.sqla.models import SqlaTable from superset.databases.commands.exceptions import ( @@ -107,8 +105,7 @@ def run(self) -> Dict[str, Any]: except SupersetException as ex: raise ex except Exception as ex: - logger.error(ex) - raise DatabaseTablesUnexpectedError(ex) + raise DatabaseTablesUnexpectedError(ex) from ex def validate(self) -> None: self._model = cast(Database, DatabaseDAO.find_by_id(self._db_id)) diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py index 837b7825aa94..780d02e28ebd 100644 --- a/superset/databases/schemas.py +++ b/superset/databases/schemas.py @@ -40,15 +40,15 @@ database_schemas_query_schema = { "type": "object", - "properties": { - "force": {"type": "boolean"}, - "schema_name": {"type": "string"}, - }, + "properties": {"force": {"type": "boolean"}}, } database_tables_query_schema = { "type": "object", - "properties": {"force": {"type": "boolean"}}, + "properties": { + "force": {"type": "boolean"}, + "schema_name": {"type": "str"}, + }, } database_name_description = "A database name to identify this connection." @@ -564,9 +564,9 @@ class SchemasResponseSchema(Schema): class DatabaseTablesResponse(Schema): - extra = (fields.Dict(),) - type = (fields.String(),) - value = (fields.String(),) + extra = fields.Dict(required=False) + type = fields.String() + value = fields.String() class ValidateSQLRequest(Schema): From e623499840a4a15b0e0e9aa976e7f560d544e1fe Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 22 Dec 2022 01:54:18 -0300 Subject: [PATCH 3/8] cleanup --- docs/static/resources/openapi.json | 11 +++ .../SqlEditorLeftBar.test.jsx | 2 +- superset/databases/api.py | 2 +- superset/databases/commands/tables.py | 2 +- .../databases/commands_tests.py | 71 +++++++++++++++++++ 5 files changed, 85 insertions(+), 3 deletions(-) diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json index 62153bac51cb..a5529dae44aa 100644 --- a/docs/static/resources/openapi.json +++ b/docs/static/resources/openapi.json @@ -8440,6 +8440,17 @@ }, "type": "object" }, + "database_tables_query_schema": { + "properties": { + "force": { + "type": "boolean" + }, + "schema_name": { + "type": "str" + } + }, + "type": "object" + }, "get_delete_ids_schema": { "items": { "type": "integer" diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx index 375094c6c665..d06aa5cff389 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx @@ -51,7 +51,7 @@ fetchMock.get('glob:*/api/v1/database/*/tables/*', { }, ], tableLength: 1, - } + }, }); const renderAndWait = (props, store) => diff --git a/superset/databases/api.py b/superset/databases/api.py index 962c02ca7f0f..fd27d977715c 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -526,7 +526,7 @@ def tables(self, pk: int, **kwargs: Any) -> FlaskResponse: application/json: schema: $ref: '#/components/schemas/database_tables_query_schema' - responses: + responses: 200: description: Tables list content: diff --git a/superset/databases/commands/tables.py b/superset/databases/commands/tables.py index d2e833ba93aa..1c16aa99fd8e 100644 --- a/superset/databases/commands/tables.py +++ b/superset/databases/commands/tables.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. import logging -from typing import Any, Dict, cast +from typing import Any, cast, Dict from superset.commands.base import BaseCommand from superset.connectors.sqla.models import SqlaTable diff --git a/tests/integration_tests/databases/commands_tests.py b/tests/integration_tests/databases/commands_tests.py index 64c9b260c4ab..7e2dd284689d 100644 --- a/tests/integration_tests/databases/commands_tests.py +++ b/tests/integration_tests/databases/commands_tests.py @@ -31,17 +31,20 @@ DatabaseInvalidError, DatabaseNotFoundError, DatabaseSecurityUnsafeError, + DatabaseTablesUnexpectedError, DatabaseTestConnectionDriverError, DatabaseTestConnectionUnexpectedError, ) from superset.databases.commands.export import ExportDatabasesCommand from superset.databases.commands.importers.v1 import ImportDatabasesCommand +from superset.databases.commands.tables import TablesDatabaseCommand from superset.databases.commands.test_connection import TestConnectionDatabaseCommand from superset.databases.commands.validate import ValidateDatabaseParametersCommand from superset.databases.schemas import DatabaseTestConnectionSchema from superset.errors import ErrorLevel, SupersetError, SupersetErrorType from superset.exceptions import ( SupersetErrorsException, + SupersetException, SupersetSecurityException, SupersetTimeoutException, ) @@ -882,3 +885,71 @@ def test_validate_partial_invalid_hostname(is_hostname_valid, app_context): }, ), ] + + +class TestTablesDatabaseCommand(SupersetTestCase): + @mock.patch("superset.databases.dao.DatabaseDAO.find_by_id") + def test_database_tables_list_with_unknown_database(self, mock_find_by_id): + mock_find_by_id.return_value = None + command = TablesDatabaseCommand(1, "test", False) + + with pytest.raises(DatabaseNotFoundError) as excinfo: + command.run() + assert str(excinfo.value) == ("Database not found.") + + @mock.patch("superset.databases.dao.DatabaseDAO.find_by_id") + @mock.patch("superset.security.manager.SupersetSecurityManager.can_access_database") + @mock.patch("superset.utils.core.g") + def test_database_tables_superset_exception( + self, mock_g, mock_can_access_database, mock_find_by_id + ): + database = get_example_database() + mock_find_by_id.return_value = database + mock_can_access_database.side_effect = SupersetException("Test Error") + mock_g.user = security_manager.find_user("admin") + + command = TablesDatabaseCommand(database.id, "main", False) + with pytest.raises(SupersetException) as excinfo: + command.run() + assert str(excinfo.value) == "Test Error" + + @mock.patch("superset.databases.dao.DatabaseDAO.find_by_id") + @mock.patch("superset.security.manager.SupersetSecurityManager.can_access_database") + @mock.patch("superset.utils.core.g") + def test_database_tables_exception( + self, mock_g, mock_can_access_database, mock_find_by_id + ): + database = get_example_database() + mock_find_by_id.return_value = database + mock_can_access_database.side_effect = Exception("Test Error") + mock_g.user = security_manager.find_user("admin") + + command = TablesDatabaseCommand(database.id, "main", False) + with pytest.raises(DatabaseTablesUnexpectedError) as excinfo: + command.run() + assert ( + str(excinfo.value) + == "Unexpected error occurred, please check your logs for details" + ) + + @mock.patch("superset.databases.dao.DatabaseDAO.find_by_id") + @mock.patch("superset.security.manager.SupersetSecurityManager.can_access_database") + @mock.patch("superset.utils.core.g") + def test_database_tables_list_tables( + self, mock_g, mock_can_access_database, mock_find_by_id + ): + database = get_example_database() + mock_find_by_id.return_value = database + mock_can_access_database.return_value = True + mock_g.user = security_manager.find_user("admin") + + command = TablesDatabaseCommand(database.id, "main", False) + result = command.run() + + assert result["tableLength"] == 66 + assert len(result["options"]) == result["tableLength"] + assert result["options"][0] == { + "extra": None, + "type": "table", + "value": "ab_permission", + } From 23511ae4386b19cdd7daff63e58a5aded9ec7c98 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Thu, 22 Dec 2022 06:37:20 -0300 Subject: [PATCH 4/8] ore tests --- docs/static/resources/openapi.json | 1226 +++++++++++------ superset/databases/schemas.py | 2 +- .../integration_tests/databases/api_tests.py | 50 + .../databases/commands_tests.py | 17 +- 4 files changed, 868 insertions(+), 427 deletions(-) diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json index a5529dae44aa..4f55259e64e9 100644 --- a/docs/static/resources/openapi.json +++ b/docs/static/resources/openapi.json @@ -547,6 +547,17 @@ }, "type": "object" }, + "AvailableDomainsSchema": { + "properties": { + "domains": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, "CacheInvalidationRequestSchema": { "properties": { "datasource_uids": { @@ -822,17 +833,12 @@ }, "ChartDataExtras": { "properties": { - "druid_time_origin": { - "description": "Starting point for time grain counting on legacy Druid datasources. Used to change e.g. Monday/Sunday first-day-of-week.", - "nullable": true, - "type": "string" - }, "having": { "description": "HAVING clause to be added to aggregate queries using AND operator.", "type": "string" }, "having_druid": { - "description": "HAVING filters to be added to legacy Druid datasource queries.", + "description": "HAVING filters to be added to legacy Druid datasource queries. This field is deprecated", "items": { "$ref": "#/components/schemas/ChartDataFilter" }, @@ -920,18 +926,20 @@ "NOT IN", "REGEX", "IS TRUE", - "IS FALSE" + "IS FALSE", + "TEMPORAL_RANGE" ], "example": "IN", "type": "string" }, "val": { - "description": "The value or values to compare against. Can be a string, integer, decimal or list, depending on the operator.", + "description": "The value or values to compare against. Can be a string, integer, decimal, None or list, depending on the operator.", "example": [ "China", "France", "Japan" - ] + ], + "nullable": true } }, "required": [ @@ -1060,13 +1068,13 @@ "operation": { "description": "Post processing operation type", "enum": [ - "_flatten_column_after_pivot", "aggregate", "boxplot", "compare", "contribution", "cum", "diff", + "escape_separator", "flatten", "geodetic_parse", "geohash_decode", @@ -1077,7 +1085,8 @@ "resample", "rolling", "select", - "sort" + "sort", + "unescape_separator" ], "example": "aggregate", "type": "string" @@ -1174,11 +1183,18 @@ }, "ChartDataQueryContextSchema": { "properties": { + "custom_cache_timeout": { + "description": "Override the default cache timeout", + "format": "int32", + "nullable": true, + "type": "integer" + }, "datasource": { "$ref": "#/components/schemas/ChartDataDatasource" }, "force": { "description": "Should the queries be forced to load from the source. Default: `false`", + "nullable": true, "type": "boolean" }, "form_data": { @@ -1436,7 +1452,7 @@ "type": "string" }, "cache_timeout": { - "description": "Cache timeout in following order: custom timeout, datasource timeout, default config timeout.", + "description": "Cache timeout in following order: custom timeout, datasource timeout, cache default timeout, config default cache timeout.", "format": "int32", "nullable": true, "type": "integer" @@ -1557,6 +1573,9 @@ "nullable": true, "type": "string" }, + "changed_on_delta_humanized": { + "readOnly": true + }, "dashboards": { "$ref": "#/components/schemas/ChartDataRestApi.get.Dashboard" }, @@ -1564,6 +1583,10 @@ "nullable": true, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "is_managed_externally": { "type": "boolean" }, @@ -1583,6 +1606,12 @@ "nullable": true, "type": "string" }, + "thumbnail_url": { + "readOnly": true + }, + "url": { + "readOnly": true + }, "viz_type": { "maxLength": 250, "nullable": true, @@ -1651,7 +1680,7 @@ "type": "string" }, "changed_by": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User1" }, "changed_by_name": { "readOnly": true @@ -1666,7 +1695,13 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User1" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User" + }, + "created_on_delta_humanized": { + "readOnly": true + }, + "dashboards": { + "$ref": "#/components/schemas/ChartDataRestApi.get_list.Dashboard" }, "datasource_id": { "format": "int32", @@ -1707,10 +1742,10 @@ "type": "string" }, "last_saved_by": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User2" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User3" }, "owners": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User1" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User2" }, "params": { "nullable": true, @@ -1738,6 +1773,20 @@ }, "type": "object" }, + "ChartDataRestApi.get_list.Dashboard": { + "properties": { + "dashboard_title": { + "maxLength": 500, + "nullable": true, + "type": "string" + }, + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, "ChartDataRestApi.get_list.SqlaTable": { "properties": { "default_endpoint": { @@ -1760,6 +1809,10 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -1777,23 +1830,14 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, "last_name": { "maxLength": 64, "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" } }, "required": [ "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, @@ -1810,11 +1854,16 @@ "last_name": { "maxLength": 64, "type": "string" + }, + "username": { + "maxLength": 64, + "type": "string" } }, "required": [ "first_name", - "last_name" + "last_name", + "username" ], "type": "object" }, @@ -2231,7 +2280,8 @@ "description": "Form data from the Explore controls used to form the chart's data query.", "type": "object" }, - "slice_id": { + "id": { + "description": "The id of the chart.", "format": "int32", "type": "integer" }, @@ -2315,6 +2365,9 @@ "nullable": true, "type": "string" }, + "changed_on_delta_humanized": { + "readOnly": true + }, "dashboards": { "$ref": "#/components/schemas/ChartRestApi.get.Dashboard" }, @@ -2322,6 +2375,10 @@ "nullable": true, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "is_managed_externally": { "type": "boolean" }, @@ -2341,6 +2398,12 @@ "nullable": true, "type": "string" }, + "thumbnail_url": { + "readOnly": true + }, + "url": { + "readOnly": true + }, "viz_type": { "maxLength": 250, "nullable": true, @@ -2409,7 +2472,7 @@ "type": "string" }, "changed_by": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User" + "$ref": "#/components/schemas/ChartRestApi.get_list.User1" }, "changed_by_name": { "readOnly": true @@ -2424,7 +2487,13 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User1" + "$ref": "#/components/schemas/ChartRestApi.get_list.User" + }, + "created_on_delta_humanized": { + "readOnly": true + }, + "dashboards": { + "$ref": "#/components/schemas/ChartRestApi.get_list.Dashboard" }, "datasource_id": { "format": "int32", @@ -2465,10 +2534,10 @@ "type": "string" }, "last_saved_by": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User2" + "$ref": "#/components/schemas/ChartRestApi.get_list.User3" }, "owners": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User1" + "$ref": "#/components/schemas/ChartRestApi.get_list.User2" }, "params": { "nullable": true, @@ -2496,6 +2565,20 @@ }, "type": "object" }, + "ChartRestApi.get_list.Dashboard": { + "properties": { + "dashboard_title": { + "maxLength": 500, + "nullable": true, + "type": "string" + }, + "id": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, "ChartRestApi.get_list.SqlaTable": { "properties": { "default_endpoint": { @@ -2518,6 +2601,10 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -2535,23 +2622,14 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, "last_name": { "maxLength": 64, "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" } }, "required": [ "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, @@ -2568,11 +2646,16 @@ "last_name": { "maxLength": 64, "type": "string" + }, + "username": { + "maxLength": 64, + "type": "string" } }, "required": [ "first_name", - "last_name" + "last_name", + "username" ], "type": "object" }, @@ -3044,8 +3127,7 @@ }, "owners": { "items": { - "format": "int32", - "type": "integer" + "type": "object" }, "type": "array" }, @@ -3180,16 +3262,24 @@ }, "DashboardPermalinkPostSchema": { "properties": { - "filterState": { - "description": "Native filter state", + "activeTabs": { + "description": "Current active dashboard tabs", + "items": { + "type": "string" + }, "nullable": true, - "type": "object" + "type": "array" }, - "hash": { - "description": "Optional anchor link", + "anchor": { + "description": "Optional anchor link added to url hash", "nullable": true, "type": "string" }, + "dataMask": { + "description": "Data mask used for native filter state", + "nullable": true, + "type": "object" + }, "urlParams": { "description": "URL Parameters", "items": { @@ -3222,7 +3312,7 @@ "type": "string" }, "changed_by": { - "$ref": "#/components/schemas/DashboardRestApi.get_list.User" + "$ref": "#/components/schemas/DashboardRestApi.get_list.User1" }, "changed_by_name": { "readOnly": true @@ -3237,10 +3327,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/DashboardRestApi.get_list.User2" - }, - "created_on_delta_humanized": { - "readOnly": true + "$ref": "#/components/schemas/DashboardRestApi.get_list.User" }, "created_on_delta_humanized": { "readOnly": true @@ -3325,25 +3412,16 @@ "last_name": { "maxLength": 64, "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" } }, "required": [ "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, "DashboardRestApi.get_list.User1": { "properties": { - "email": { - "maxLength": 64, - "type": "string" - }, "first_name": { "maxLength": 64, "type": "string" @@ -3362,7 +3440,6 @@ } }, "required": [ - "email", "first_name", "last_name", "username" @@ -3371,6 +3448,10 @@ }, "DashboardRestApi.get_list.User2": { "properties": { + "email": { + "maxLength": 64, + "type": "string" + }, "first_name": { "maxLength": 64, "type": "string" @@ -3382,11 +3463,17 @@ "last_name": { "maxLength": 64, "type": "string" + }, + "username": { + "maxLength": 64, + "type": "string" } }, "required": [ + "email", "first_name", - "last_name" + "last_name", + "username" ], "type": "object" }, @@ -3560,17 +3647,6 @@ }, "name": { "type": "string" - }, - "engine_information": { - "type": "object" - } - }, - "type": "object" - }, - "Database1": { - "properties": { - "database_name": { - "type": "string" } }, "type": "object" @@ -3711,9 +3787,11 @@ "maxLength": 250, "type": "string" }, - "encrypted_extra": { - "nullable": true, - "type": "string" + "driver": { + "readOnly": true + }, + "engine_information": { + "readOnly": true }, "expose_in_sqllab": { "nullable": true, @@ -3739,6 +3817,9 @@ "is_managed_externally": { "type": "boolean" }, + "masked_encrypted_extra": { + "readOnly": true + }, "parameters": { "readOnly": true }, @@ -3753,8 +3834,10 @@ "maxLength": 1024, "type": "string" }, - "engine_information": { - "readOnly": true + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" } }, "required": [ @@ -3815,6 +3898,9 @@ "disable_data_preview": { "readOnly": true }, + "engine_information": { + "readOnly": true + }, "explore_database_id": { "readOnly": true }, @@ -3835,8 +3921,10 @@ "format": "int32", "type": "integer" }, - "engine_information": { - "readOnly": true + "uuid": { + "format": "uuid", + "nullable": true, + "type": "string" } }, "required": [ @@ -3899,8 +3987,8 @@ "minLength": 1, "type": "string" }, - "encrypted_extra": { - "description": "

JSON string containing additional connection configuration.
This is used to provide connection information for systems like Hive, Presto, and BigQuery, which do not conform to the username:password syntax normally used by SQLAlchemy.

", + "driver": { + "description": "SQLAlchemy driver to use", "nullable": true, "type": "string" }, @@ -3936,6 +4024,11 @@ "nullable": true, "type": "boolean" }, + "masked_encrypted_extra": { + "description": "

JSON string containing additional connection configuration.
This is used to provide connection information for systems like Hive, Presto, and BigQuery, which do not conform to the username:password syntax normally used by SQLAlchemy.

", + "nullable": true, + "type": "string" + }, "parameters": { "additionalProperties": {}, "description": "DB-specific parameters for configuration", @@ -3951,6 +4044,9 @@ "maxLength": 1024, "minLength": 1, "type": "string" + }, + "uuid": { + "type": "string" } }, "required": [ @@ -3997,8 +4093,8 @@ "nullable": true, "type": "string" }, - "encrypted_extra": { - "description": "

JSON string containing additional connection configuration.
This is used to provide connection information for systems like Hive, Presto, and BigQuery, which do not conform to the username:password syntax normally used by SQLAlchemy.

", + "driver": { + "description": "SQLAlchemy driver to use", "nullable": true, "type": "string" }, @@ -4034,6 +4130,11 @@ "nullable": true, "type": "boolean" }, + "masked_encrypted_extra": { + "description": "

JSON string containing additional connection configuration.
This is used to provide connection information for systems like Hive, Presto, and BigQuery, which do not conform to the username:password syntax normally used by SQLAlchemy.

", + "nullable": true, + "type": "string" + }, "parameters": { "additionalProperties": {}, "description": "DB-specific parameters for configuration", @@ -4053,6 +4154,20 @@ }, "type": "object" }, + "DatabaseTablesResponse": { + "properties": { + "extra": { + "type": "object" + }, + "type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, "DatabaseTestConnectionSchema": { "properties": { "configuration_method": { @@ -4066,8 +4181,8 @@ "nullable": true, "type": "string" }, - "encrypted_extra": { - "description": "

JSON string containing additional connection configuration.
This is used to provide connection information for systems like Hive, Presto, and BigQuery, which do not conform to the username:password syntax normally used by SQLAlchemy.

", + "driver": { + "description": "SQLAlchemy driver to use", "nullable": true, "type": "string" }, @@ -4084,6 +4199,11 @@ "description": "If Presto, all the queries in SQL Lab are going to be executed as the currently logged on user who must have permission to run them.
If Hive and hive.server2.enable.doAs is enabled, will run the queries as service account, but impersonate the currently logged on user via hive.server2.proxy.user property.", "type": "boolean" }, + "masked_encrypted_extra": { + "description": "

JSON string containing additional connection configuration.
This is used to provide connection information for systems like Hive, Presto, and BigQuery, which do not conform to the username:password syntax normally used by SQLAlchemy.

", + "nullable": true, + "type": "string" + }, "parameters": { "additionalProperties": {}, "description": "DB-specific parameters for configuration", @@ -4105,6 +4225,13 @@ }, "DatabaseValidateParametersSchema": { "properties": { + "catalog": { + "additionalProperties": { + "nullable": true + }, + "description": "Gsheets specific column for managing label to sheet urls", + "type": "object" + }, "configuration_method": { "description": "Configuration_method is used on the frontend to inform the backend whether to explode parameters or to provide only a sqlalchemy_uri." }, @@ -4115,8 +4242,8 @@ "nullable": true, "type": "string" }, - "encrypted_extra": { - "description": "

JSON string containing additional connection configuration.
This is used to provide connection information for systems like Hive, Presto, and BigQuery, which do not conform to the username:password syntax normally used by SQLAlchemy.

", + "driver": { + "description": "SQLAlchemy driver to use", "nullable": true, "type": "string" }, @@ -4128,10 +4255,21 @@ "description": "

JSON string containing extra configuration elements.
1. The engine_params object gets unpacked into the sqlalchemy.create_engine call, while the metadata_params gets unpacked into the sqlalchemy.MetaData call.
2. The metadata_cache_timeout is a cache timeout setting in seconds for metadata fetch of this database. Specify it as \"metadata_cache_timeout\": {\"schema_cache_timeout\": 600, \"table_cache_timeout\": 600}. If unset, cache will not be enabled for the functionality. A timeout of 0 indicates that the cache never expires.
3. The schemas_allowed_for_file_upload is a comma separated list of schemas that CSVs are allowed to upload to. Specify it as \"schemas_allowed_for_file_upload\": [\"public\", \"csv_upload\"]. If database flavor does not support schema or any schema is allowed to be accessed, just leave the list empty
4. The version field is a string specifying the this db's version. This should be used with Presto DBs so that the syntax is correct
5. The allows_virtual_table_explore field is a boolean specifying whether or not the Explore button in SQL Lab results is shown.
6. The disable_data_preview field is a boolean specifying whether or not data preview queries will be run when fetching table metadata in SQL Lab.

", "type": "string" }, - "impersonate_user": { + "id": { + "description": "Database ID (for updates)", + "format": "int32", + "nullable": true, + "type": "integer" + }, + "impersonate_user": { "description": "If Presto, all the queries in SQL Lab are going to be executed as the currently logged on user who must have permission to run them.
If Hive and hive.server2.enable.doAs is enabled, will run the queries as service account, but impersonate the currently logged on user via hive.server2.proxy.user property.", "type": "boolean" }, + "masked_encrypted_extra": { + "description": "

JSON string containing additional connection configuration.
This is used to provide connection information for systems like Hive, Presto, and BigQuery, which do not conform to the username:password syntax normally used by SQLAlchemy.

", + "nullable": true, + "type": "string" + }, "parameters": { "additionalProperties": { "nullable": true @@ -4151,6 +4289,174 @@ ], "type": "object" }, + "Dataset": { + "properties": { + "cache_timeout": { + "description": "Duration (in seconds) of the caching timeout for this dataset.", + "format": "int32", + "type": "integer" + }, + "column_formats": { + "description": "Column formats.", + "type": "object" + }, + "columns": { + "description": "Columns metadata.", + "items": { + "type": "object" + }, + "type": "array" + }, + "database": { + "description": "Database associated with the dataset.", + "type": "object" + }, + "datasource_name": { + "description": "Dataset name.", + "type": "string" + }, + "default_endpoint": { + "description": "Default endpoint for the dataset.", + "type": "string" + }, + "description": { + "description": "Dataset description.", + "type": "string" + }, + "edit_url": { + "description": "The URL for editing the dataset.", + "type": "string" + }, + "extra": { + "description": "JSON string containing extra configuration elements.", + "type": "object" + }, + "fetch_values_predicate": { + "description": "Predicate used when fetching values from the dataset.", + "type": "string" + }, + "filter_select": { + "description": "SELECT filter applied to the dataset.", + "type": "boolean" + }, + "filter_select_enabled": { + "description": "If the SELECT filter is enabled.", + "type": "boolean" + }, + "granularity_sqla": { + "description": "Name of temporal column used for time filtering for SQL datasources. This field is deprecated, use `granularity` instead.", + "items": { + "items": { + "type": "object" + }, + "type": "array" + }, + "type": "array" + }, + "health_check_message": { + "description": "Health check message.", + "type": "string" + }, + "id": { + "description": "Dataset ID.", + "format": "int32", + "type": "integer" + }, + "is_sqllab_view": { + "description": "If the dataset is a SQL Lab view.", + "type": "boolean" + }, + "main_dttm_col": { + "description": "The main temporal column.", + "type": "string" + }, + "metrics": { + "description": "Dataset metrics.", + "items": { + "type": "object" + }, + "type": "array" + }, + "name": { + "description": "Dataset name.", + "type": "string" + }, + "offset": { + "description": "Dataset offset.", + "format": "int32", + "type": "integer" + }, + "order_by_choices": { + "description": "List of order by columns.", + "items": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + }, + "owners": { + "description": "List of owners identifiers", + "items": { + "format": "int32", + "type": "integer" + }, + "type": "array" + }, + "params": { + "description": "Extra params for the dataset.", + "type": "object" + }, + "perm": { + "description": "Permission expression.", + "type": "string" + }, + "schema": { + "description": "Dataset schema.", + "type": "string" + }, + "select_star": { + "description": "Select all clause.", + "type": "string" + }, + "sql": { + "description": "A SQL statement that defines the dataset.", + "type": "string" + }, + "table_name": { + "description": "The name of the table associated with the dataset.", + "type": "string" + }, + "template_params": { + "description": "Table template params.", + "type": "object" + }, + "time_grain_sqla": { + "description": "List of temporal granularities supported by the dataset.", + "items": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "array" + }, + "type": { + "description": "Dataset type.", + "type": "string" + }, + "uid": { + "description": "Dataset unique identifier.", + "type": "string" + }, + "verbose_map": { + "description": "Mapping from raw name to verbose name.", + "type": "object" + } + }, + "type": "object" + }, "DatasetColumnsPut": { "properties": { "advanced_data_type": { @@ -4446,9 +4752,31 @@ "nullable": true, "type": "integer" }, + "changed_by": { + "$ref": "#/components/schemas/DatasetRestApi.get.User1" + }, + "changed_on": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "changed_on_humanized": { + "readOnly": true + }, "columns": { "$ref": "#/components/schemas/DatasetRestApi.get.TableColumn" }, + "created_by": { + "$ref": "#/components/schemas/DatasetRestApi.get.User" + }, + "created_on": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "created_on_humanized": { + "readOnly": true + }, "database": { "$ref": "#/components/schemas/DatasetRestApi.get.Database" }, @@ -4503,13 +4831,16 @@ "type": "integer" }, "owners": { - "$ref": "#/components/schemas/DatasetRestApi.get.User" + "$ref": "#/components/schemas/DatasetRestApi.get.User2" }, "schema": { "maxLength": 255, "nullable": true, "type": "string" }, + "select_star": { + "readOnly": true + }, "sql": { "nullable": true, "type": "string" @@ -4594,11 +4925,6 @@ "nullable": true, "type": "string" }, - "uuid": { - "format": "uuid", - "nullable": true, - "type": "string" - }, "verbose_name": { "maxLength": 1024, "nullable": true, @@ -4697,6 +5023,40 @@ "type": "object" }, "DatasetRestApi.get.User": { + "properties": { + "first_name": { + "maxLength": 64, + "type": "string" + }, + "last_name": { + "maxLength": 64, + "type": "string" + } + }, + "required": [ + "first_name", + "last_name" + ], + "type": "object" + }, + "DatasetRestApi.get.User1": { + "properties": { + "first_name": { + "maxLength": 64, + "type": "string" + }, + "last_name": { + "maxLength": 64, + "type": "string" + } + }, + "required": [ + "first_name", + "last_name" + ], + "type": "object" + }, + "DatasetRestApi.get.User2": { "properties": { "first_name": { "maxLength": 64, @@ -4875,6 +5235,10 @@ "minLength": 0, "type": "string" }, + "sql": { + "nullable": true, + "type": "string" + }, "table_name": { "maxLength": 250, "minLength": 1, @@ -5020,101 +5384,27 @@ "count": { "description": "The total number of distinct values", "format": "int32", - "type": "integer" - }, - "result": { - "items": { - "$ref": "#/components/schemas/DistinctResultResponse" - }, - "type": "array" - } - }, - "type": "object" - }, - "DistinctResultResponse": { - "properties": { - "text": { - "description": "The distinct item", - "type": "string" - } - }, - "type": "object" - }, - "EmbeddedDashboardConfig": { - "properties": { - "allowed_domains": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "allowed_domains" - ], - "type": "object" - }, - "EmbeddedDashboardResponseSchema": { - "properties": { - "allowed_domains": { - "items": { - "type": "string" - }, - "type": "array" - }, - "changed_by": { - "$ref": "#/components/schemas/User" - }, - "changed_on": { - "format": "date-time", - "type": "string" - }, - "dashboard_id": { - "type": "string" - }, - "uuid": { - "type": "string" - } - }, - "type": "object" - }, - "EmbeddedDashboardRestApi.get": { - "properties": { - "uuid": { - "format": "uuid", - "type": "string" - } - }, - "type": "object" - }, - "EmbeddedDashboardRestApi.get_list": { - "properties": { - "uuid": { - "format": "uuid", - "type": "string" - } - }, - "type": "object" - }, - "EmbeddedDashboardRestApi.post": { - "properties": { - "uuid": { - "format": "uuid", - "type": "string" + "type": "integer" + }, + "result": { + "items": { + "$ref": "#/components/schemas/DistinctResultResponse" + }, + "type": "array" } }, "type": "object" }, - "EmbeddedDashboardRestApi.put": { + "DistinctResultResponse": { "properties": { - "uuid": { - "format": "uuid", + "text": { + "description": "The distinct item", "type": "string" } }, "type": "object" }, - "ExplorePermalinkPostSchema": { + "EmbeddedDashboardConfig": { "properties": { "allowed_domains": { "items": { @@ -5188,6 +5478,25 @@ }, "type": "object" }, + "ExploreContextSchema": { + "properties": { + "dataset": { + "$ref": "#/components/schemas/Dataset" + }, + "form_data": { + "description": "Form data from the Explore controls used to form the chart's data query.", + "type": "object" + }, + "message": { + "description": "Any message related to the processed request.", + "type": "string" + }, + "slice": { + "$ref": "#/components/schemas/Slice" + } + }, + "type": "object" + }, "ExplorePermalinkPostSchema": { "properties": { "formData": { @@ -5730,8 +6039,7 @@ "type": "string" }, "tracking_url": { - "nullable": true, - "type": "string" + "readOnly": true } }, "required": [ @@ -5840,6 +6148,10 @@ }, "RelatedResultResponse": { "properties": { + "extra": { + "description": "The extra metadata for related item", + "type": "object" + }, "text": { "description": "The related item string representation", "type": "string" @@ -6027,6 +6339,9 @@ "nullable": true, "type": "string" }, + "extra": { + "readOnly": true + }, "force_screenshot": { "nullable": true, "type": "boolean" @@ -6211,7 +6526,7 @@ "type": "boolean" }, "changed_by": { - "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User" + "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User1" }, "changed_on": { "format": "date-time", @@ -6227,7 +6542,7 @@ "type": "integer" }, "created_by": { - "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User2" + "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User" }, "created_on": { "format": "date-time", @@ -6255,6 +6570,9 @@ "nullable": true, "type": "string" }, + "extra": { + "readOnly": true + }, "id": { "format": "int32", "type": "integer" @@ -6274,7 +6592,7 @@ "type": "string" }, "owners": { - "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User1" + "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User2" }, "recipients": { "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.ReportRecipients" @@ -6335,10 +6653,6 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, "last_name": { "maxLength": 64, "type": "string" @@ -6356,6 +6670,10 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -7143,6 +7461,9 @@ "nullable": true, "type": "string" }, + "extra": { + "type": "object" + }, "force_screenshot": { "type": "boolean" }, @@ -7867,6 +8188,9 @@ }, "SavedQueryRestApi.get": { "properties": { + "changed_on_delta_humanized": { + "readOnly": true + }, "created_by": { "$ref": "#/components/schemas/SavedQueryRestApi.get.User" }, @@ -7897,6 +8221,10 @@ }, "sql_tables": { "readOnly": true + }, + "template_parameters": { + "nullable": true, + "type": "string" } }, "type": "object" @@ -8059,6 +8387,10 @@ "sql": { "nullable": true, "type": "string" + }, + "template_parameters": { + "nullable": true, + "type": "string" } }, "type": "object" @@ -8087,6 +8419,10 @@ "sql": { "nullable": true, "type": "string" + }, + "template_parameters": { + "nullable": true, + "type": "string" } }, "type": "object" @@ -8112,6 +8448,85 @@ }, "type": "object" }, + "Slice": { + "properties": { + "cache_timeout": { + "description": "Duration (in seconds) of the caching timeout for this chart.", + "format": "int32", + "type": "integer" + }, + "certification_details": { + "description": "Details of the certification.", + "type": "string" + }, + "certified_by": { + "description": "Person or group that has certified this dashboard.", + "type": "string" + }, + "changed_on": { + "description": "Timestamp of the last modification.", + "type": "string" + }, + "changed_on_humanized": { + "description": "Timestamp of the last modification in human readable form.", + "type": "string" + }, + "datasource": { + "description": "Datasource identifier.", + "type": "string" + }, + "description": { + "description": "Slice description.", + "type": "string" + }, + "description_markeddown": { + "description": "Sanitized HTML version of the chart description.", + "type": "string" + }, + "edit_url": { + "description": "The URL for editing the slice.", + "type": "string" + }, + "form_data": { + "description": "Form data associated with the slice.", + "type": "object" + }, + "is_managed_externally": { + "description": "If the chart is managed outside externally.", + "type": "boolean" + }, + "modified": { + "description": "Last modification in human readable form.", + "type": "string" + }, + "owners": { + "description": "Owners identifiers.", + "items": { + "format": "int32", + "type": "integer" + }, + "type": "array" + }, + "query_context": { + "description": "The context associated with the query.", + "type": "object" + }, + "slice_id": { + "description": "The slice ID.", + "format": "int32", + "type": "integer" + }, + "slice_name": { + "description": "The slice name.", + "type": "string" + }, + "slice_url": { + "description": "The slice URL.", + "type": "string" + } + }, + "type": "object" + }, "TableExtraMetadataResponseSchema": { "properties": { "clustering": { @@ -8446,7 +8861,7 @@ "type": "boolean" }, "schema_name": { - "type": "str" + "type": "string" } }, "type": "object" @@ -8635,152 +9050,59 @@ "type": "integer" } }, - "type": "object" - }, - "screenshot_query_schema": { - "properties": { - "force": { - "type": "boolean" - }, - "thumb_size": { - "items": { - "type": "integer" - }, - "type": "array" - }, - "window_size": { - "items": { - "type": "integer" - }, - "type": "array" - } - }, - "type": "object" - }, - "thumbnail_query_schema": { - "properties": { - "force": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "securitySchemes": { - "jwt": { - "bearerFormat": "JWT", - "scheme": "bearer", - "type": "http" - }, - "jwt_refresh": { - "bearerFormat": "JWT", - "scheme": "bearer", - "type": "http" - } - } - }, - "info": { - "description": "Superset", - "title": "Superset", - "version": "v1" - }, - "openapi": "3.0.2", - "paths": { - "/api/v1/advanced_data_type/convert": { - "get": { - "parameters": [ - { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/advanced_data_type_convert_schema" - } - } - }, - "in": "query", - "name": "q" - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AdvancedDataTypeSchema" - } - } - }, - "description": "AdvancedDataTypeResponse object has been returned." - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "404": { - "$ref": "#/components/responses/404" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [ - { - "jwt": [] - } - ], - "summary": "Returns a AdvancedDataTypeResponse object populated with the passed in args.", - "tags": [ - "Advanced Data Type" - ] - } - }, - "/api/v1/advanced_data_type/types": { - "get": { - "description": "Returns a list of available advanced data types.", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "result": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - } - } - }, - "description": "a successful return of the available advanced data types has taken place." - }, - "401": { - "$ref": "#/components/responses/401" + "type": "object" + }, + "screenshot_query_schema": { + "properties": { + "force": { + "type": "boolean" }, - "404": { - "$ref": "#/components/responses/404" + "thumb_size": { + "items": { + "type": "integer" + }, + "type": "array" }, - "500": { - "$ref": "#/components/responses/500" + "window_size": { + "items": { + "type": "integer" + }, + "type": "array" } }, - "security": [ - { - "jwt": [] + "type": "object" + }, + "thumbnail_query_schema": { + "properties": { + "force": { + "type": "boolean" } - ], - "tags": [ - "Advanced Data Type" - ] + }, + "type": "object" } }, - "/api/v1/annotation_layer/": { - "delete": { - "description": "Deletes multiple annotation layers in a bulk operation.", + "securitySchemes": { + "jwt": { + "bearerFormat": "JWT", + "scheme": "bearer", + "type": "http" + }, + "jwt_refresh": { + "bearerFormat": "JWT", + "scheme": "bearer", + "type": "http" + } + } + }, + "info": { + "description": "Superset", + "title": "Superset", + "version": "v1" + }, + "openapi": "3.0.2", + "paths": { + "/api/v1/advanced_data_type/convert": { + "get": { "parameters": [ { "content": { @@ -10034,6 +10356,42 @@ ] } }, + "/api/v1/available_domains/": { + "get": { + "description": "Get all available domains", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "result": { + "$ref": "#/components/schemas/AvailableDomainsSchema" + } + }, + "type": "object" + } + } + }, + "description": "a list of available domains" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Available Domains" + ] + } + }, "/api/v1/cachekey/invalidate": { "post": { "description": "Takes a list of datasources, finds the associated cache records and invalidates them and removes the database records", @@ -11015,6 +11373,14 @@ "schema": { "type": "string" } + }, + { + "description": "Should the queries be forced to load from the source", + "in": "query", + "name": "force", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -13817,6 +14183,16 @@ "description": "Name of the SQLAlchemy engine", "type": "string" }, + "engine_information": { + "description": "Dict with public properties form the DB Engine", + "properties": { + "supports_file_upload": { + "description": "Whether the engine supports file uploads", + "type": "boolean" + } + }, + "type": "object" + }, "name": { "description": "Name of the database", "type": "string" @@ -13832,10 +14208,6 @@ "sqlalchemy_uri_placeholder": { "description": "Example placeholder for the SQLAlchemy URI", "type": "string" - }, - "engine_information": { - "description": "Object with properties we want to expose from our DB engine", - "type": "object" } }, "type": "object" @@ -14587,9 +14959,9 @@ ] } }, - "/api/v1/database/{pk}/select_star/{table_name}/{schema_name}/": { + "/api/v1/database/{pk}/table/{table_name}/{schema_name}/": { "get": { - "description": "Get database select star for table", + "description": "Get database table metadata", "parameters": [ { "description": "The database id", @@ -14624,11 +14996,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SelectStarResponseSchema" + "$ref": "#/components/schemas/TableMetadataResponseSchema" } } }, - "description": "SQL statement for a select star for table" + "description": "Table metadata information" }, "400": { "$ref": "#/components/responses/400" @@ -14656,9 +15028,9 @@ ] } }, - "/api/v1/database/{pk}/table/{table_name}/{schema_name}/": { + "/api/v1/database/{pk}/table_extra/{table_name}/{schema_name}/": { "get": { - "description": "Get database table metadata", + "description": "Response depends on each DB engine spec normally focused on partitions", "parameters": [ { "description": "The database id", @@ -14693,11 +15065,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TableMetadataResponseSchema" + "$ref": "#/components/schemas/TableExtraMetadataResponseSchema" } } }, - "description": "Table metadata information" + "description": "Table extra metadata information" }, "400": { "$ref": "#/components/responses/400" @@ -14720,14 +15092,15 @@ "jwt": [] } ], + "summary": "Get table extra metadata", "tags": [ "Database" ] } }, - "/api/v1/database/{pk}/table_extra/{table_name}/{schema_name}/": { + "/api/v1/database/{pk}/tables/": { "get": { - "description": "Response depends on each DB engine spec normally focused on partitions", + "description": "Get a list of tables for given database", "parameters": [ { "description": "The database id", @@ -14739,22 +15112,15 @@ } }, { - "description": "Table name", - "in": "path", - "name": "table_name", - "required": true, - "schema": { - "type": "string" - } - }, - { - "description": "Table schema", - "in": "path", - "name": "schema_name", - "required": true, - "schema": { - "type": "string" - } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/database_tables_query_schema" + } + } + }, + "in": "query", + "name": "q" } ], "responses": { @@ -14762,11 +15128,28 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TableExtraMetadataResponseSchema" + "properties": { + "result": { + "description": "A List of tables for given database", + "properties": { + "options": { + "items": { + "$ref": "#/components/schemas/DatabaseTablesResponse" + }, + "type": "array" + }, + "tableLength": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" } } }, - "description": "Table extra metadata information" + "description": "Tables list" }, "400": { "$ref": "#/components/responses/400" @@ -14777,9 +15160,6 @@ "404": { "$ref": "#/components/responses/404" }, - "422": { - "$ref": "#/components/responses/422" - }, "500": { "$ref": "#/components/responses/500" } @@ -14789,13 +15169,12 @@ "jwt": [] } ], - "summary": "Get table extra metadata", "tags": [ "Database" ] } }, - "/api/v1/database/{pk}/validate_sql": { + "/api/v1/database/{pk}/validate_sql/": { "post": { "description": "Validates arbitrary SQL.", "parameters": [ @@ -15231,16 +15610,6 @@ "/api/v1/dataset/duplicate": { "post": { "description": "Duplicates a Dataset", - "parameters": [ - { - "in": "path", - "name": "pk", - "required": true, - "schema": { - "type": "integer" - } - } - ], "requestBody": { "content": { "application/json": { @@ -15253,20 +15622,23 @@ "required": true }, "responses": { - "200": { + "201": { "content": { "application/json": { "schema": { "properties": { - "message": { - "type": "string" + "id": { + "type": "number" + }, + "result": { + "$ref": "#/components/schemas/DatasetDuplicateSchema" } }, "type": "object" } } }, - "description": "Dataset duplicate" + "description": "Dataset duplicated" }, "400": { "$ref": "#/components/responses/400" @@ -15939,23 +16311,17 @@ ] } }, - "/api/v1/dataset/{pk}/samples": { + "/api/v1/embedded_dashboard/{uuid}": { "get": { - "description": "get samples from a Dataset", + "description": "Get a report schedule log", "parameters": [ { + "description": "The embedded configuration uuid", "in": "path", - "name": "pk", + "name": "uuid", "required": true, "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "force", - "schema": { - "type": "boolean" + "type": "string" } } ], @@ -15966,27 +16332,21 @@ "schema": { "properties": { "result": { - "$ref": "#/components/schemas/ChartDataResponseResult" + "$ref": "#/components/schemas/EmbeddedDashboardResponseSchema" } }, "type": "object" } } }, - "description": "Dataset samples" + "description": "Result contains the embedded dashboard configuration" }, "401": { "$ref": "#/components/responses/401" }, - "403": { - "$ref": "#/components/responses/403" - }, "404": { "$ref": "#/components/responses/404" }, - "422": { - "$ref": "#/components/responses/422" - }, "500": { "$ref": "#/components/responses/500" } @@ -15997,19 +16357,45 @@ } ], "tags": [ - "Datasets" + "Embedded Dashboard" ] } }, - "/api/v1/embedded_dashboard/{uuid}": { + "/api/v1/explore/": { "get": { - "description": "Get a report schedule log", + "description": "Assembles Explore related information (form_data, slice, dataset)\\n in a single endpoint.

\\nThe information can be assembled from:
- The cache using a form_data_key
- The metadata database using a permalink_key
- Build from scratch using dataset or slice identifiers.", "parameters": [ { - "description": "The embedded configuration uuid", - "in": "path", - "name": "uuid", - "required": true, + "in": "query", + "name": "form_data_key", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "permalink_key", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "slice_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "dataset_id", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "dataset_type", "schema": { "type": "string" } @@ -16020,16 +16406,14 @@ "content": { "application/json": { "schema": { - "properties": { - "result": { - "$ref": "#/components/schemas/EmbeddedDashboardResponseSchema" - } - }, - "type": "object" + "$ref": "#/components/schemas/ExploreContextSchema" } } }, - "description": "Result contains the embedded dashboard configuration" + "description": "Returns the initial context." + }, + "400": { + "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" @@ -16037,6 +16421,9 @@ "404": { "$ref": "#/components/responses/404" }, + "422": { + "$ref": "#/components/responses/422" + }, "500": { "$ref": "#/components/responses/500" } @@ -16046,8 +16433,9 @@ "jwt": [] } ], + "summary": "Assembles Explore related information (form_data, slice, dataset)\\n in a single endpoint.", "tags": [ - "Embedded Dashboard" + "Explore" ] } }, diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py index 780d02e28ebd..292367ebb2a9 100644 --- a/superset/databases/schemas.py +++ b/superset/databases/schemas.py @@ -47,7 +47,7 @@ "type": "object", "properties": { "force": {"type": "boolean"}, - "schema_name": {"type": "str"}, + "schema_name": {"type": "string"}, }, } diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py index 8a96184b8195..19f01e1df423 100644 --- a/tests/integration_tests/databases/api_tests.py +++ b/tests/integration_tests/databases/api_tests.py @@ -1381,6 +1381,56 @@ def test_database_schemas_invalid_query(self): ) self.assertEqual(rv.status_code, 400) + def test_database_tables(self): + """ + Database API: Test database tables + """ + self.login(username="admin") + database = db.session.query(Database).filter_by(database_name="examples").one() + + schema_name = self.default_schema_backend_map[database.backend] + rv = self.client.get( + f"api/v1/database/{database.id}/tables/?q={prison.dumps({'schema_name': schema_name})}" + ) + + self.assertEqual(rv.status_code, 200) + + def test_database_tables_not_found(self): + """ + Database API: Test database tables not found + """ + self.logout() + self.login(username="gamma") + example_db = get_example_database() + uri = f"api/v1/database/{example_db.id}/tables/?q={prison.dumps({'schema_name': 'non_existent'})}" + rv = self.client.get(uri) + self.assertEqual(rv.status_code, 404) + + def test_database_tables_invalid_query(self): + """ + Database API: Test database tables with invalid query + """ + self.login("admin") + database = db.session.query(Database).first() + rv = self.client.get( + f"api/v1/database/{database.id}/tables/?q={prison.dumps({'force': 'nop'})}" + ) + self.assertEqual(rv.status_code, 400) + + @mock.patch("superset.security.manager.SupersetSecurityManager.can_access_database") + def test_database_tables_unexpected_error(self, mock_can_access_database): + """ + Database API: Test database tables with unexpected error + """ + self.login(username="admin") + database = db.session.query(Database).filter_by(database_name="examples").one() + mock_can_access_database.side_effect = Exception("Test Error") + + rv = self.client.get( + f"api/v1/database/{database.id}/tables/?q={prison.dumps({'schema_name': 'main'})}" + ) + self.assertEqual(rv.status_code, 500) + def test_test_connection(self): """ Database API: Test test connection diff --git a/tests/integration_tests/databases/commands_tests.py b/tests/integration_tests/databases/commands_tests.py index 7e2dd284689d..a127aa522755 100644 --- a/tests/integration_tests/databases/commands_tests.py +++ b/tests/integration_tests/databases/commands_tests.py @@ -904,6 +904,9 @@ def test_database_tables_superset_exception( self, mock_g, mock_can_access_database, mock_find_by_id ): database = get_example_database() + if database.backend == "mysql": + return + mock_find_by_id.return_value = database mock_can_access_database.side_effect = SupersetException("Test Error") mock_g.user = security_manager.find_user("admin") @@ -943,13 +946,13 @@ def test_database_tables_list_tables( mock_can_access_database.return_value = True mock_g.user = security_manager.find_user("admin") - command = TablesDatabaseCommand(database.id, "main", False) + schema_name = self.default_schema_backend_map[database.backend] + if database.backend == "postgresql" or database.backend == "mysql": + return + + command = TablesDatabaseCommand(database.id, schema_name, False) result = command.run() - assert result["tableLength"] == 66 + assert result["tableLength"] > 0 + assert len(result["options"]) > 0 assert len(result["options"]) == result["tableLength"] - assert result["options"][0] == { - "extra": None, - "type": "table", - "value": "ab_permission", - } From 0e7fdacc98f8ca562380eee032f4a63bac52d3f4 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Tue, 3 Jan 2023 15:28:37 -0300 Subject: [PATCH 5/8] PR comments --- docs/static/resources/openapi.json | 8 +++++++- .../components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx | 2 +- superset-frontend/src/hooks/apiResources/tables.test.ts | 4 ++-- superset-frontend/src/hooks/apiResources/tables.ts | 4 ++-- .../data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx | 2 +- superset/databases/api.py | 6 ++---- superset/databases/commands/tables.py | 2 +- superset/databases/schemas.py | 7 ++++--- tests/integration_tests/databases/commands_tests.py | 4 ++-- 9 files changed, 22 insertions(+), 17 deletions(-) diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json index 4f55259e64e9..ec21a573621b 100644 --- a/docs/static/resources/openapi.json +++ b/docs/static/resources/openapi.json @@ -4157,12 +4157,15 @@ "DatabaseTablesResponse": { "properties": { "extra": { + "description": "Extra data used to specify column metadata", "type": "object" }, "type": { + "description": "table or view", "type": "string" }, "value": { + "description": "The table or view name", "type": "string" } }, @@ -8864,6 +8867,9 @@ "type": "string" } }, + "required": [ + "schema_name" + ], "type": "object" }, "get_delete_ids_schema": { @@ -15138,7 +15144,7 @@ }, "type": "array" }, - "tableLength": { + "count": { "type": "integer" } }, diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx index d06aa5cff389..f920c74abe20 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx @@ -50,7 +50,7 @@ fetchMock.get('glob:*/api/v1/database/*/tables/*', { value: 'ab_user', }, ], - tableLength: 1, + count: 1, }, }); diff --git a/superset-frontend/src/hooks/apiResources/tables.test.ts b/superset-frontend/src/hooks/apiResources/tables.test.ts index ebddbdbb76c7..6b3c987e4890 100644 --- a/superset-frontend/src/hooks/apiResources/tables.test.ts +++ b/superset-frontend/src/hooks/apiResources/tables.test.ts @@ -36,7 +36,7 @@ const fakeApiResult = { label: 'fake api label2', }, ], - tableLength: 2, + count: 2, }, }, }; @@ -56,7 +56,7 @@ const fakeHasMoreApiResult = { label: 'fake api label2', }, ], - tableLength: 4, + count: 4, }, }, }; diff --git a/superset-frontend/src/hooks/apiResources/tables.ts b/superset-frontend/src/hooks/apiResources/tables.ts index af2fa778f1de..0f000987100a 100644 --- a/superset-frontend/src/hooks/apiResources/tables.ts +++ b/superset-frontend/src/hooks/apiResources/tables.ts @@ -43,7 +43,7 @@ type QueryData = { json: { result: { options: Table[]; - tableLength: number; + count: number; }; }; response: Response; @@ -84,7 +84,7 @@ export function useTables(options: Params) { { select: ({ json }) => ({ ...json.result, - hasMore: json.result.tableLength > json.result.options.length, + hasMore: json.result.count > json.result.options.length, }), enabled: Boolean(dbId && schema), onSuccess, diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx index 25ab55b716e8..481c3d88cc85 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx @@ -137,7 +137,7 @@ fetchMock.get(schemasEndpoint, { fetchMock.get(tablesEndpoint, { result: { - tableLength: 3, + count: 3, options: [ { value: 'Sheet1', type: 'table', extra: null }, { value: 'Sheet2', type: 'table', extra: null }, diff --git a/superset/databases/api.py b/superset/databases/api.py index fd27d977715c..cff539c7bb15 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -513,7 +513,7 @@ def tables(self, pk: int, **kwargs: Any) -> FlaskResponse: """Get a list of tables for given database --- get: - description: Get a list of tables for given database + summary: Get a list of tables for given database parameters: - in: path schema: @@ -539,7 +539,7 @@ def tables(self, pk: int, **kwargs: Any) -> FlaskResponse: A List of tables for given database type: object properties: - tableLength: + count: type: integer options: type: array @@ -556,8 +556,6 @@ def tables(self, pk: int, **kwargs: Any) -> FlaskResponse: """ force = kwargs["rison"].get("force", False) schema_name = kwargs["rison"].get("schema_name", "") - if not schema_name: - return self.response_422("Schema undefined") try: command = TablesDatabaseCommand(pk, schema_name, force) diff --git a/superset/databases/commands/tables.py b/superset/databases/commands/tables.py index 1c16aa99fd8e..3e79f2f883a9 100644 --- a/superset/databases/commands/tables.py +++ b/superset/databases/commands/tables.py @@ -100,7 +100,7 @@ def run(self) -> Dict[str, Any]: key=lambda item: item["value"], ) - payload = {"tableLength": len(tables) + len(views), "options": options} + payload = {"count": len(tables) + len(views), "options": options} return payload except SupersetException as ex: raise ex diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py index 292367ebb2a9..2b514524bcdd 100644 --- a/superset/databases/schemas.py +++ b/superset/databases/schemas.py @@ -49,6 +49,7 @@ "force": {"type": "boolean"}, "schema_name": {"type": "string"}, }, + "required": ["schema_name"], } database_name_description = "A database name to identify this connection." @@ -564,9 +565,9 @@ class SchemasResponseSchema(Schema): class DatabaseTablesResponse(Schema): - extra = fields.Dict(required=False) - type = fields.String() - value = fields.String() + extra = fields.Dict(description="Extra data used to specify column metadata") + type = fields.String(description="table or view") + value = fields.String(description="The table or view name") class ValidateSQLRequest(Schema): diff --git a/tests/integration_tests/databases/commands_tests.py b/tests/integration_tests/databases/commands_tests.py index a127aa522755..94674146edfd 100644 --- a/tests/integration_tests/databases/commands_tests.py +++ b/tests/integration_tests/databases/commands_tests.py @@ -953,6 +953,6 @@ def test_database_tables_list_tables( command = TablesDatabaseCommand(database.id, schema_name, False) result = command.run() - assert result["tableLength"] > 0 + assert result["count"] > 0 assert len(result["options"]) > 0 - assert len(result["options"]) == result["tableLength"] + assert len(result["options"]) == result["count"] From af38335d4311d72faf48e09706a287a70cfc062c Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Fri, 6 Jan 2023 17:52:39 -0300 Subject: [PATCH 6/8] cleanup t --- docs/static/resources/openapi.json | 278 +++++++++++------- superset/databases/api.py | 4 +- .../integration_tests/databases/api_tests.py | 12 +- 3 files changed, 179 insertions(+), 115 deletions(-) diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json index ec21a573621b..b079e60dc59f 100644 --- a/docs/static/resources/openapi.json +++ b/docs/static/resources/openapi.json @@ -257,7 +257,7 @@ "AnnotationLayerRestApi.get_list": { "properties": { "changed_by": { - "$ref": "#/components/schemas/AnnotationLayerRestApi.get_list.User1" + "$ref": "#/components/schemas/AnnotationLayerRestApi.get_list.User" }, "changed_on": { "format": "date-time", @@ -268,7 +268,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/AnnotationLayerRestApi.get_list.User" + "$ref": "#/components/schemas/AnnotationLayerRestApi.get_list.User1" }, "created_on": { "format": "date-time", @@ -414,13 +414,13 @@ "AnnotationRestApi.get_list": { "properties": { "changed_by": { - "$ref": "#/components/schemas/AnnotationRestApi.get_list.User1" + "$ref": "#/components/schemas/AnnotationRestApi.get_list.User" }, "changed_on_delta_humanized": { "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/AnnotationRestApi.get_list.User" + "$ref": "#/components/schemas/AnnotationRestApi.get_list.User1" }, "end_dttm": { "format": "date-time", @@ -1695,7 +1695,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User2" }, "created_on_delta_humanized": { "readOnly": true @@ -1742,10 +1742,10 @@ "type": "string" }, "last_saved_by": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User3" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User" }, "owners": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User2" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User3" }, "params": { "nullable": true, @@ -1854,16 +1854,11 @@ "last_name": { "maxLength": 64, "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" } }, "required": [ "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, @@ -1880,11 +1875,16 @@ "last_name": { "maxLength": 64, "type": "string" + }, + "username": { + "maxLength": 64, + "type": "string" } }, "required": [ "first_name", - "last_name" + "last_name", + "username" ], "type": "object" }, @@ -2487,7 +2487,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User" + "$ref": "#/components/schemas/ChartRestApi.get_list.User2" }, "created_on_delta_humanized": { "readOnly": true @@ -2534,10 +2534,10 @@ "type": "string" }, "last_saved_by": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User3" + "$ref": "#/components/schemas/ChartRestApi.get_list.User" }, "owners": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User2" + "$ref": "#/components/schemas/ChartRestApi.get_list.User3" }, "params": { "nullable": true, @@ -2646,16 +2646,11 @@ "last_name": { "maxLength": 64, "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" } }, "required": [ "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, @@ -2672,11 +2667,16 @@ "last_name": { "maxLength": 64, "type": "string" + }, + "username": { + "maxLength": 64, + "type": "string" } }, "required": [ "first_name", - "last_name" + "last_name", + "username" ], "type": "object" }, @@ -2939,13 +2939,13 @@ "CssTemplateRestApi.get_list": { "properties": { "changed_by": { - "$ref": "#/components/schemas/CssTemplateRestApi.get_list.User1" + "$ref": "#/components/schemas/CssTemplateRestApi.get_list.User" }, "changed_on_delta_humanized": { "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/CssTemplateRestApi.get_list.User" + "$ref": "#/components/schemas/CssTemplateRestApi.get_list.User1" }, "created_on": { "format": "date-time", @@ -3312,7 +3312,7 @@ "type": "string" }, "changed_by": { - "$ref": "#/components/schemas/DashboardRestApi.get_list.User1" + "$ref": "#/components/schemas/DashboardRestApi.get_list.User" }, "changed_by_name": { "readOnly": true @@ -3327,7 +3327,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/DashboardRestApi.get_list.User" + "$ref": "#/components/schemas/DashboardRestApi.get_list.User1" }, "created_on_delta_humanized": { "readOnly": true @@ -3412,11 +3412,16 @@ "last_name": { "maxLength": 64, "type": "string" + }, + "username": { + "maxLength": 64, + "type": "string" } }, "required": [ "first_name", - "last_name" + "last_name", + "username" ], "type": "object" }, @@ -3433,16 +3438,11 @@ "last_name": { "maxLength": 64, "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" } }, "required": [ "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, @@ -4045,6 +4045,14 @@ "minLength": 1, "type": "string" }, + "ssh_tunnel": { + "allOf": [ + { + "$ref": "#/components/schemas/DatabaseSSHTunnel" + } + ], + "nullable": true + }, "uuid": { "type": "string" } @@ -4150,6 +4158,38 @@ "maxLength": 1024, "minLength": 0, "type": "string" + }, + "ssh_tunnel": { + "allOf": [ + { + "$ref": "#/components/schemas/DatabaseSSHTunnel" + } + ], + "nullable": true + } + }, + "type": "object" + }, + "DatabaseSSHTunnel": { + "properties": { + "password": { + "type": "string" + }, + "private_key": { + "type": "string" + }, + "private_key_password": { + "type": "string" + }, + "server_address": { + "type": "string" + }, + "server_port": { + "format": "int32", + "type": "integer" + }, + "username": { + "type": "string" } }, "type": "object" @@ -4222,6 +4262,14 @@ "maxLength": 1024, "minLength": 1, "type": "string" + }, + "ssh_tunnel": { + "allOf": [ + { + "$ref": "#/components/schemas/DatabaseSSHTunnel" + } + ], + "nullable": true } }, "type": "object" @@ -4756,7 +4804,7 @@ "type": "integer" }, "changed_by": { - "$ref": "#/components/schemas/DatasetRestApi.get.User1" + "$ref": "#/components/schemas/DatasetRestApi.get.User" }, "changed_on": { "format": "date-time", @@ -4770,7 +4818,7 @@ "$ref": "#/components/schemas/DatasetRestApi.get.TableColumn" }, "created_by": { - "$ref": "#/components/schemas/DatasetRestApi.get.User" + "$ref": "#/components/schemas/DatasetRestApi.get.User1" }, "created_on": { "format": "date-time", @@ -5088,7 +5136,7 @@ "DatasetRestApi.get_list": { "properties": { "changed_by": { - "$ref": "#/components/schemas/DatasetRestApi.get_list.User1" + "$ref": "#/components/schemas/DatasetRestApi.get_list.User" }, "changed_by_name": { "readOnly": true @@ -5131,7 +5179,7 @@ "readOnly": true }, "owners": { - "$ref": "#/components/schemas/DatasetRestApi.get_list.User" + "$ref": "#/components/schemas/DatasetRestApi.get_list.User1" }, "schema": { "maxLength": 255, @@ -5175,14 +5223,6 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, - "last_name": { - "maxLength": 64, - "type": "string" - }, "username": { "maxLength": 64, "type": "string" @@ -5190,7 +5230,6 @@ }, "required": [ "first_name", - "last_name", "username" ], "type": "object" @@ -5201,6 +5240,14 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, + "last_name": { + "maxLength": 64, + "type": "string" + }, "username": { "maxLength": 64, "type": "string" @@ -5208,6 +5255,7 @@ }, "required": [ "first_name", + "last_name", "username" ], "type": "object" @@ -6529,7 +6577,7 @@ "type": "boolean" }, "changed_by": { - "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User1" + "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User" }, "changed_on": { "format": "date-time", @@ -6545,7 +6593,7 @@ "type": "integer" }, "created_by": { - "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User" + "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User1" }, "created_on": { "format": "date-time", @@ -14510,26 +14558,16 @@ ] }, "get": { - "description": "Get an item model", + "description": "Get a database", "parameters": [ { + "description": "The database id", "in": "path", "name": "pk", "required": true, "schema": { "type": "integer" } - }, - { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/get_item_schema" - } - } - }, - "in": "query", - "name": "q" } ], "responses": { @@ -14537,52 +14575,11 @@ "content": { "application/json": { "schema": { - "properties": { - "description_columns": { - "properties": { - "column_name": { - "description": "The description for the column name. Will be translated by babel", - "example": "A Nice description for the column", - "type": "string" - } - }, - "type": "object" - }, - "id": { - "description": "The item id", - "type": "string" - }, - "label_columns": { - "properties": { - "column_name": { - "description": "The label for the column name. Will be translated by babel", - "example": "A Nice label for the column", - "type": "string" - } - }, - "type": "object" - }, - "result": { - "$ref": "#/components/schemas/DatabaseRestApi.get" - }, - "show_columns": { - "description": "A list of columns", - "items": { - "type": "string" - }, - "type": "array" - }, - "show_title": { - "description": "A title to render. Will be translated by babel", - "example": "Show Item Details", - "type": "string" - } - }, "type": "object" } } }, - "description": "Item from Model" + "description": "Database" }, "400": { "$ref": "#/components/responses/400" @@ -14590,9 +14587,6 @@ "401": { "$ref": "#/components/responses/401" }, - "404": { - "$ref": "#/components/responses/404" - }, "422": { "$ref": "#/components/responses/422" }, @@ -14965,6 +14959,61 @@ ] } }, + "/api/v1/database/{pk}/ssh_tunnel/": { + "delete": { + "description": "Deletes a SSH Tunnel.", + "parameters": [ + { + "in": "path", + "name": "pk", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "SSH Tunnel deleted" + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + }, + "500": { + "$ref": "#/components/responses/500" + } + }, + "security": [ + { + "jwt": [] + } + ], + "tags": [ + "Database" + ] + } + }, "/api/v1/database/{pk}/table/{table_name}/{schema_name}/": { "get": { "description": "Get database table metadata", @@ -15106,7 +15155,6 @@ }, "/api/v1/database/{pk}/tables/": { "get": { - "description": "Get a list of tables for given database", "parameters": [ { "description": "The database id", @@ -15138,14 +15186,14 @@ "result": { "description": "A List of tables for given database", "properties": { + "count": { + "type": "integer" + }, "options": { "items": { "$ref": "#/components/schemas/DatabaseTablesResponse" }, "type": "array" - }, - "count": { - "type": "integer" } }, "type": "object" @@ -15166,6 +15214,9 @@ "404": { "$ref": "#/components/responses/404" }, + "422": { + "$ref": "#/components/responses/422" + }, "500": { "$ref": "#/components/responses/500" } @@ -15175,6 +15226,7 @@ "jwt": [] } ], + "summary": "Get a list of tables for given database", "tags": [ "Database" ] @@ -16394,14 +16446,14 @@ }, { "in": "query", - "name": "dataset_id", + "name": "datasource_id", "schema": { "type": "integer" } }, { "in": "query", - "name": "dataset_type", + "name": "datasource_type", "schema": { "type": "string" } diff --git a/superset/databases/api.py b/superset/databases/api.py index 55369387249a..3bec215bc0cc 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -608,6 +608,8 @@ def tables(self, pk: int, **kwargs: Any) -> FlaskResponse: $ref: '#/components/responses/401' 404: $ref: '#/components/responses/404' + 422: + $ref: '#/components/responses/422' 500: $ref: '#/components/responses/500' """ @@ -623,7 +625,7 @@ def tables(self, pk: int, **kwargs: Any) -> FlaskResponse: except SupersetException as ex: return self.response(ex.status, message=ex.message) except DatabaseTablesUnexpectedError as ex: - return self.response_500(ex.message) + return self.response_422(ex.message) @expose("//table///", methods=["GET"]) @protect() diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py index 7cb187f6b079..48978b5a6591 100644 --- a/tests/integration_tests/databases/api_tests.py +++ b/tests/integration_tests/databases/api_tests.py @@ -1704,6 +1704,16 @@ def test_database_tables(self): ) self.assertEqual(rv.status_code, 200) + if database.backend == "postgresql": + response = json.loads(rv.data.decode("utf-8")) + schemas = [ + s[0] for s in database.get_all_table_names_in_schema(schema_name) + ] + self.assertEquals(response["result"]["count"], len(schemas)) + for option in response["result"]["options"]: + self.assertEquals(option["extra"], None) + self.assertEquals(option["type"], "table") + self.assertTrue(option["value"] in schemas) def test_database_tables_not_found(self): """ @@ -1739,7 +1749,7 @@ def test_database_tables_unexpected_error(self, mock_can_access_database): rv = self.client.get( f"api/v1/database/{database.id}/tables/?q={prison.dumps({'schema_name': 'main'})}" ) - self.assertEqual(rv.status_code, 500) + self.assertEqual(rv.status_code, 422) def test_test_connection(self): """ From 4780234bab18c4386280ef056389845a28814655 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Mon, 9 Jan 2023 11:56:37 -0300 Subject: [PATCH 7/8] PR comments --- docs/static/resources/openapi.json | 163 +++++++++--------- .../SqlEditorLeftBar.test.jsx | 16 +- .../TableSelector/TableSelector.test.tsx | 15 +- .../src/hooks/apiResources/tables.test.ts | 57 +++--- .../src/hooks/apiResources/tables.ts | 13 +- .../AddDataset/LeftPanel/LeftPanel.test.tsx | 14 +- .../dataset/AddDataset/LeftPanel/index.tsx | 18 +- superset/databases/api.py | 15 +- superset/databases/commands/tables.py | 2 +- .../integration_tests/databases/api_tests.py | 4 +- 10 files changed, 149 insertions(+), 168 deletions(-) diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json index b079e60dc59f..feb852969808 100644 --- a/docs/static/resources/openapi.json +++ b/docs/static/resources/openapi.json @@ -257,7 +257,7 @@ "AnnotationLayerRestApi.get_list": { "properties": { "changed_by": { - "$ref": "#/components/schemas/AnnotationLayerRestApi.get_list.User" + "$ref": "#/components/schemas/AnnotationLayerRestApi.get_list.User1" }, "changed_on": { "format": "date-time", @@ -268,7 +268,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/AnnotationLayerRestApi.get_list.User1" + "$ref": "#/components/schemas/AnnotationLayerRestApi.get_list.User" }, "created_on": { "format": "date-time", @@ -414,13 +414,13 @@ "AnnotationRestApi.get_list": { "properties": { "changed_by": { - "$ref": "#/components/schemas/AnnotationRestApi.get_list.User" + "$ref": "#/components/schemas/AnnotationRestApi.get_list.User1" }, "changed_on_delta_humanized": { "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/AnnotationRestApi.get_list.User1" + "$ref": "#/components/schemas/AnnotationRestApi.get_list.User" }, "end_dttm": { "format": "date-time", @@ -1680,7 +1680,7 @@ "type": "string" }, "changed_by": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User1" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User2" }, "changed_by_name": { "readOnly": true @@ -1695,7 +1695,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User2" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User1" }, "created_on_delta_humanized": { "readOnly": true @@ -1742,10 +1742,10 @@ "type": "string" }, "last_saved_by": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User3" }, "owners": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User3" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User" }, "params": { "nullable": true, @@ -1816,11 +1816,16 @@ "last_name": { "maxLength": 64, "type": "string" + }, + "username": { + "maxLength": 64, + "type": "string" } }, "required": [ "first_name", - "last_name" + "last_name", + "username" ], "type": "object" }, @@ -1830,6 +1835,10 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -1847,10 +1856,6 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, "last_name": { "maxLength": 64, "type": "string" @@ -1875,16 +1880,11 @@ "last_name": { "maxLength": 64, "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" } }, "required": [ "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, @@ -2472,7 +2472,7 @@ "type": "string" }, "changed_by": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User1" + "$ref": "#/components/schemas/ChartRestApi.get_list.User2" }, "changed_by_name": { "readOnly": true @@ -2487,7 +2487,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User2" + "$ref": "#/components/schemas/ChartRestApi.get_list.User1" }, "created_on_delta_humanized": { "readOnly": true @@ -2534,10 +2534,10 @@ "type": "string" }, "last_saved_by": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User" + "$ref": "#/components/schemas/ChartRestApi.get_list.User3" }, "owners": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User3" + "$ref": "#/components/schemas/ChartRestApi.get_list.User" }, "params": { "nullable": true, @@ -2608,11 +2608,16 @@ "last_name": { "maxLength": 64, "type": "string" + }, + "username": { + "maxLength": 64, + "type": "string" } }, "required": [ "first_name", - "last_name" + "last_name", + "username" ], "type": "object" }, @@ -2622,6 +2627,10 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -2639,10 +2648,6 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, "last_name": { "maxLength": 64, "type": "string" @@ -2667,16 +2672,11 @@ "last_name": { "maxLength": 64, "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" } }, "required": [ "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, @@ -2939,13 +2939,13 @@ "CssTemplateRestApi.get_list": { "properties": { "changed_by": { - "$ref": "#/components/schemas/CssTemplateRestApi.get_list.User" + "$ref": "#/components/schemas/CssTemplateRestApi.get_list.User1" }, "changed_on_delta_humanized": { "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/CssTemplateRestApi.get_list.User1" + "$ref": "#/components/schemas/CssTemplateRestApi.get_list.User" }, "created_on": { "format": "date-time", @@ -3312,7 +3312,7 @@ "type": "string" }, "changed_by": { - "$ref": "#/components/schemas/DashboardRestApi.get_list.User" + "$ref": "#/components/schemas/DashboardRestApi.get_list.User2" }, "changed_by_name": { "readOnly": true @@ -3353,7 +3353,7 @@ "type": "string" }, "owners": { - "$ref": "#/components/schemas/DashboardRestApi.get_list.User2" + "$ref": "#/components/schemas/DashboardRestApi.get_list.User" }, "position_json": { "nullable": true, @@ -3401,6 +3401,10 @@ }, "DashboardRestApi.get_list.User": { "properties": { + "email": { + "maxLength": 64, + "type": "string" + }, "first_name": { "maxLength": 64, "type": "string" @@ -3419,6 +3423,7 @@ } }, "required": [ + "email", "first_name", "last_name", "username" @@ -3448,10 +3453,6 @@ }, "DashboardRestApi.get_list.User2": { "properties": { - "email": { - "maxLength": 64, - "type": "string" - }, "first_name": { "maxLength": 64, "type": "string" @@ -3470,7 +3471,6 @@ } }, "required": [ - "email", "first_name", "last_name", "username" @@ -4804,7 +4804,7 @@ "type": "integer" }, "changed_by": { - "$ref": "#/components/schemas/DatasetRestApi.get.User" + "$ref": "#/components/schemas/DatasetRestApi.get.User2" }, "changed_on": { "format": "date-time", @@ -4882,7 +4882,7 @@ "type": "integer" }, "owners": { - "$ref": "#/components/schemas/DatasetRestApi.get.User2" + "$ref": "#/components/schemas/DatasetRestApi.get.User" }, "schema": { "maxLength": 255, @@ -5079,14 +5079,23 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" + }, + "username": { + "maxLength": 64, + "type": "string" } }, "required": [ "first_name", - "last_name" + "last_name", + "username" ], "type": "object" }, @@ -5113,30 +5122,21 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, "last_name": { "maxLength": 64, "type": "string" - }, - "username": { - "maxLength": 64, - "type": "string" } }, "required": [ "first_name", - "last_name", - "username" + "last_name" ], "type": "object" }, "DatasetRestApi.get_list": { "properties": { "changed_by": { - "$ref": "#/components/schemas/DatasetRestApi.get_list.User" + "$ref": "#/components/schemas/DatasetRestApi.get_list.User1" }, "changed_by_name": { "readOnly": true @@ -5179,7 +5179,7 @@ "readOnly": true }, "owners": { - "$ref": "#/components/schemas/DatasetRestApi.get_list.User1" + "$ref": "#/components/schemas/DatasetRestApi.get_list.User" }, "schema": { "maxLength": 255, @@ -5223,6 +5223,14 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, + "last_name": { + "maxLength": 64, + "type": "string" + }, "username": { "maxLength": 64, "type": "string" @@ -5230,6 +5238,7 @@ }, "required": [ "first_name", + "last_name", "username" ], "type": "object" @@ -5240,14 +5249,6 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, - "last_name": { - "maxLength": 64, - "type": "string" - }, "username": { "maxLength": 64, "type": "string" @@ -5255,7 +5256,6 @@ }, "required": [ "first_name", - "last_name", "username" ], "type": "object" @@ -6577,7 +6577,7 @@ "type": "boolean" }, "changed_by": { - "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User" + "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User2" }, "changed_on": { "format": "date-time", @@ -6643,7 +6643,7 @@ "type": "string" }, "owners": { - "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User2" + "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User" }, "recipients": { "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.ReportRecipients" @@ -6687,6 +6687,10 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -6721,10 +6725,6 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, "last_name": { "maxLength": 64, "type": "string" @@ -15183,20 +15183,15 @@ "application/json": { "schema": { "properties": { + "count": { + "type": "integer" + }, "result": { "description": "A List of tables for given database", - "properties": { - "count": { - "type": "integer" - }, - "options": { - "items": { - "$ref": "#/components/schemas/DatabaseTablesResponse" - }, - "type": "array" - } + "items": { + "$ref": "#/components/schemas/DatabaseTablesResponse" }, - "type": "object" + "type": "array" } }, "type": "object" diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx index f920c74abe20..a4774cd390af 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/SqlEditorLeftBar.test.jsx @@ -43,15 +43,13 @@ const store = mockStore(initialState); fetchMock.get('glob:*/api/v1/database/*/schemas/?*', { result: [] }); fetchMock.get('glob:*/api/v1/database/*/tables/*', { - result: { - options: [ - { - label: 'ab_user', - value: 'ab_user', - }, - ], - count: 1, - }, + count: 1, + result: [ + { + label: 'ab_user', + value: 'ab_user', + }, + ], }); const renderAndWait = (props, store) => diff --git a/superset-frontend/src/components/TableSelector/TableSelector.test.tsx b/superset-frontend/src/components/TableSelector/TableSelector.test.tsx index 388a2aa3c975..09cb29a3857f 100644 --- a/superset-frontend/src/components/TableSelector/TableSelector.test.tsx +++ b/superset-frontend/src/components/TableSelector/TableSelector.test.tsx @@ -51,14 +51,13 @@ const getSchemaMockFunction = async () => const getTableMockFunction = async () => ({ json: { - result: { - options: [ - { label: 'table_a', value: 'table_a' }, - { label: 'table_b', value: 'table_b' }, - { label: 'table_c', value: 'table_c' }, - { label: 'table_d', value: 'table_d' }, - ], - }, + count: 4, + result: [ + { label: 'table_a', value: 'table_a' }, + { label: 'table_b', value: 'table_b' }, + { label: 'table_c', value: 'table_c' }, + { label: 'table_d', value: 'table_d' }, + ], }, } as any); diff --git a/superset-frontend/src/hooks/apiResources/tables.test.ts b/superset-frontend/src/hooks/apiResources/tables.test.ts index 6b3c987e4890..87a5bed4fb47 100644 --- a/superset-frontend/src/hooks/apiResources/tables.test.ts +++ b/superset-frontend/src/hooks/apiResources/tables.test.ts @@ -23,41 +23,38 @@ import { useTables } from './tables'; const fakeApiResult = { json: { - result: { - options: [ - { - id: 1, - name: 'fake api result1', - label: 'fake api label1', - }, - { - id: 2, - name: 'fake api result2', - label: 'fake api label2', - }, - ], - count: 2, - }, + count: 2, + + result: [ + { + id: 1, + name: 'fake api result1', + label: 'fake api label1', + }, + { + id: 2, + name: 'fake api result2', + label: 'fake api label2', + }, + ], }, }; const fakeHasMoreApiResult = { json: { - result: { - options: [ - { - id: 1, - name: 'fake api result1', - label: 'fake api label1', - }, - { - id: 2, - name: 'fake api result2', - label: 'fake api label2', - }, - ], - count: 4, - }, + count: 2, + result: [ + { + id: 1, + name: 'fake api result1', + label: 'fake api label1', + }, + { + id: 2, + name: 'fake api result2', + label: 'fake api label2', + }, + ], }, }; diff --git a/superset-frontend/src/hooks/apiResources/tables.ts b/superset-frontend/src/hooks/apiResources/tables.ts index 0f000987100a..f65cdf375ad4 100644 --- a/superset-frontend/src/hooks/apiResources/tables.ts +++ b/superset-frontend/src/hooks/apiResources/tables.ts @@ -41,15 +41,14 @@ export interface Table { type QueryData = { json: { - result: { - options: Table[]; - count: number; - }; + count: number; + result: Table[]; }; response: Response; }; -export type Data = QueryData['json']['result'] & { +export type Data = { + options: Table[]; hasMore: boolean; }; @@ -83,8 +82,8 @@ export function useTables(options: Params) { () => fetchTables({ ...params, forceRefresh: forceRefreshRef.current }), { select: ({ json }) => ({ - ...json.result, - hasMore: json.result.count > json.result.options.length, + options: json.result, + hasMore: json.count > json.result.length, }), enabled: Boolean(dbId && schema), onSuccess, diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx index 481c3d88cc85..7b5f03bc11f8 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/LeftPanel.test.tsx @@ -136,14 +136,12 @@ fetchMock.get(schemasEndpoint, { }); fetchMock.get(tablesEndpoint, { - result: { - count: 3, - options: [ - { value: 'Sheet1', type: 'table', extra: null }, - { value: 'Sheet2', type: 'table', extra: null }, - { value: 'Sheet3', type: 'table', extra: null }, - ], - }, + count: 3, + result: [ + { value: 'Sheet1', type: 'table', extra: null }, + { value: 'Sheet2', type: 'table', extra: null }, + { value: 'Sheet3', type: 'table', extra: null }, + ], }); const mockFun = jest.fn(); diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx index 45e5e481ebeb..4f42ada7f024 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/LeftPanel/index.tsx @@ -178,17 +178,15 @@ export default function LeftPanel({ const getTablesList = (url: string) => { SupersetClient.get({ url }) .then(({ json }) => { - const options: TableOption[] = json.result.options.map( - (table: Table) => { - const option: TableOption = { - value: table.value, - label: , - text: table.label, - }; + const options: TableOption[] = json.result.map((table: Table) => { + const option: TableOption = { + value: table.value, + label: , + text: table.label, + }; - return option; - }, - ); + return option; + }); setTableOptions(options); setLoadTables(false); diff --git a/superset/databases/api.py b/superset/databases/api.py index 3bec215bc0cc..9c19c03c0647 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -591,17 +591,14 @@ def tables(self, pk: int, **kwargs: Any) -> FlaskResponse: schema: type: object properties: + count: + type: integer result: description: >- A List of tables for given database - type: object - properties: - count: - type: integer - options: - type: array - items: - $ref: '#/components/schemas/DatabaseTablesResponse' + type: array + items: + $ref: '#/components/schemas/DatabaseTablesResponse' 400: $ref: '#/components/responses/400' 401: @@ -619,7 +616,7 @@ def tables(self, pk: int, **kwargs: Any) -> FlaskResponse: try: command = TablesDatabaseCommand(pk, schema_name, force) payload = command.run() - return self.response(200, result=payload) + return self.response(200, **payload) except DatabaseNotFoundError: return self.response_404() except SupersetException as ex: diff --git a/superset/databases/commands/tables.py b/superset/databases/commands/tables.py index 3e79f2f883a9..48e9227dea75 100644 --- a/superset/databases/commands/tables.py +++ b/superset/databases/commands/tables.py @@ -100,7 +100,7 @@ def run(self) -> Dict[str, Any]: key=lambda item: item["value"], ) - payload = {"count": len(tables) + len(views), "options": options} + payload = {"count": len(tables) + len(views), "result": options} return payload except SupersetException as ex: raise ex diff --git a/tests/integration_tests/databases/api_tests.py b/tests/integration_tests/databases/api_tests.py index 48978b5a6591..26479d843b41 100644 --- a/tests/integration_tests/databases/api_tests.py +++ b/tests/integration_tests/databases/api_tests.py @@ -1709,8 +1709,8 @@ def test_database_tables(self): schemas = [ s[0] for s in database.get_all_table_names_in_schema(schema_name) ] - self.assertEquals(response["result"]["count"], len(schemas)) - for option in response["result"]["options"]: + self.assertEquals(response["count"], len(schemas)) + for option in response["result"]: self.assertEquals(option["extra"], None) self.assertEquals(option["type"], "table") self.assertTrue(option["value"] in schemas) From 5d941c4dc3a3b1530b153f6a7f2d6328b0480b21 Mon Sep 17 00:00:00 2001 From: Diego Medina Date: Mon, 9 Jan 2023 14:51:24 -0300 Subject: [PATCH 8/8] test fixes --- superset-frontend/src/hooks/apiResources/tables.test.ts | 7 +++---- tests/integration_tests/databases/commands_tests.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/superset-frontend/src/hooks/apiResources/tables.test.ts b/superset-frontend/src/hooks/apiResources/tables.test.ts index 87a5bed4fb47..49305f5f62eb 100644 --- a/superset-frontend/src/hooks/apiResources/tables.test.ts +++ b/superset-frontend/src/hooks/apiResources/tables.test.ts @@ -24,7 +24,6 @@ import { useTables } from './tables'; const fakeApiResult = { json: { count: 2, - result: [ { id: 1, @@ -42,7 +41,7 @@ const fakeApiResult = { const fakeHasMoreApiResult = { json: { - count: 2, + count: 4, result: [ { id: 1, @@ -59,12 +58,12 @@ const fakeHasMoreApiResult = { }; const expectedData = { - ...fakeApiResult.json.result, + options: [...fakeApiResult.json.result], hasMore: false, }; const expectedHasMoreData = { - ...fakeHasMoreApiResult.json.result, + options: [...fakeHasMoreApiResult.json.result], hasMore: true, }; diff --git a/tests/integration_tests/databases/commands_tests.py b/tests/integration_tests/databases/commands_tests.py index 94674146edfd..07c2d2040da9 100644 --- a/tests/integration_tests/databases/commands_tests.py +++ b/tests/integration_tests/databases/commands_tests.py @@ -954,5 +954,5 @@ def test_database_tables_list_tables( result = command.run() assert result["count"] > 0 - assert len(result["options"]) > 0 - assert len(result["options"]) == result["count"] + assert len(result["result"]) > 0 + assert len(result["result"]) == result["count"]