You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by dp...@apache.org on 2023/01/16 14:08:05 UTC
[superset] branch master updated: chore: Migrate /superset/stop_query/ to API v1 (#22624)
This is an automated email from the ASF dual-hosted git repository.
dpgaspar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 3ed288d4ee chore: Migrate /superset/stop_query/ to API v1 (#22624)
3ed288d4ee is described below
commit 3ed288d4ee96fd0a8a40dc1f7804de6a07c34447
Author: Diego Medina <di...@gmail.com>
AuthorDate: Mon Jan 16 11:07:52 2023 -0300
chore: Migrate /superset/stop_query/ to API v1 (#22624)
---
docs/static/resources/openapi.json | 1258 +++++++++++++-------
superset-frontend/src/SqlLab/actions/sqlLab.js | 6 +-
.../src/SqlLab/actions/sqlLab.test.js | 11 +-
superset/constants.py | 1 +
superset/exceptions.py | 4 +
superset/queries/api.py | 78 +-
superset/queries/dao.py | 27 +
superset/queries/schemas.py | 8 +
superset/views/core.py | 1 +
tests/integration_tests/queries/api_tests.py | 52 +
tests/unit_tests/dao/queries_test.py | 165 ++-
11 files changed, 1177 insertions(+), 434 deletions(-)
diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json
index 62153bac51..8279811b53 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",
@@ -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.User3"
+ },
+ "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.User"
},
"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.User3"
+ },
+ "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.User"
},
"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"
},
@@ -2856,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",
@@ -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": {
@@ -3242,9 +3332,6 @@
"created_on_delta_humanized": {
"readOnly": true
},
- "created_on_delta_humanized": {
- "readOnly": true
- },
"css": {
"nullable": true,
"type": "string"
@@ -3266,7 +3353,7 @@
"type": "string"
},
"owners": {
- "$ref": "#/components/schemas/DashboardRestApi.get_list.User2"
+ "$ref": "#/components/schemas/DashboardRestApi.get_list.User1"
},
"position_json": {
"nullable": true,
@@ -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": "<p>JSON string containing additional connection configuration.<br>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.</p>",
+ "driver": {
+ "description": "SQLAlchemy driver to use",
"nullable": true,
"type": "string"
},
@@ -3936,6 +4024,11 @@
"nullable": true,
"type": "boolean"
},
+ "masked_encrypted_extra": {
+ "description": "<p>JSON string containing additional connection configuration.<br>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.</p>",
+ "nullable": true,
+ "type": "string"
+ },
"parameters": {
"additionalProperties": {},
"description": "DB-specific parameters for configuration",
@@ -3951,6 +4044,17 @@
"maxLength": 1024,
"minLength": 1,
"type": "string"
+ },
+ "ssh_tunnel": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/DatabaseSSHTunnel"
+ }
+ ],
+ "nullable": true
+ },
+ "uuid": {
+ "type": "string"
}
},
"required": [
@@ -3997,8 +4101,8 @@
"nullable": true,
"type": "string"
},
- "encrypted_extra": {
- "description": "<p>JSON string containing additional connection configuration.<br>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.</p>",
+ "driver": {
+ "description": "SQLAlchemy driver to use",
"nullable": true,
"type": "string"
},
@@ -4034,6 +4138,11 @@
"nullable": true,
"type": "boolean"
},
+ "masked_encrypted_extra": {
+ "description": "<p>JSON string containing additional connection configuration.<br>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.</p>",
+ "nullable": true,
+ "type": "string"
+ },
"parameters": {
"additionalProperties": {},
"description": "DB-specific parameters for configuration",
@@ -4049,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"
@@ -4066,8 +4207,8 @@
"nullable": true,
"type": "string"
},
- "encrypted_extra": {
- "description": "<p>JSON string containing additional connection configuration.<br>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.</p>",
+ "driver": {
+ "description": "SQLAlchemy driver to use",
"nullable": true,
"type": "string"
},
@@ -4084,6 +4225,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.<br/>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": "<p>JSON string containing additional connection configuration.<br>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.</p>",
+ "nullable": true,
+ "type": "string"
+ },
"parameters": {
"additionalProperties": {},
"description": "DB-specific parameters for configuration",
@@ -4099,12 +4245,27 @@
"maxLength": 1024,
"minLength": 1,
"type": "string"
- }
- },
- "type": "object"
- },
+ },
+ "ssh_tunnel": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/DatabaseSSHTunnel"
+ }
+ ],
+ "nullable": true
+ }
+ },
+ "type": "object"
+ },
"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 +4276,8 @@
"nullable": true,
"type": "string"
},
- "encrypted_extra": {
- "description": "<p>JSON string containing additional connection configuration.<br>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.</p>",
+ "driver": {
+ "description": "SQLAlchemy driver to use",
"nullable": true,
"type": "string"
},
@@ -4128,10 +4289,21 @@
"description": "<p>JSON string containing extra configuration elements.<br>1. The <code>engine_params</code> object gets unpacked into the <a href=\"https://docs.sqlalchemy.org/en/latest/core/engines.html#sqlalchemy.create_engine\">sqlalchemy.create_engine</a> call, while the <code>metadata_params</code> gets unpacked into the <a href=\"https://docs.sqlalchemy.org/en/rel_1_0/core/metadata.html#sqlalchemy.schema.MetaData\">sqlalchemy.MetaData</a> call.<br>2. The <code>metadata [...]
"type": "string"
},
+ "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.<br/>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": "<p>JSON string containing additional connection configuration.<br>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.</p>",
+ "nullable": true,
+ "type": "string"
+ },
"parameters": {
"additionalProperties": {
"nullable": true
@@ -4151,6 +4323,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 +4786,31 @@
"nullable": true,
"type": "integer"
},
+ "changed_by": {
+ "$ref": "#/components/schemas/DatasetRestApi.get.User"
+ },
+ "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.User2"
+ },
+ "created_on": {
+ "format": "date-time",
+ "nullable": true,
+ "type": "string"
+ },
+ "created_on_humanized": {
+ "readOnly": true
+ },
"database": {
"$ref": "#/components/schemas/DatasetRestApi.get.Database"
},
@@ -4503,13 +4865,16 @@
"type": "integer"
},
"owners": {
- "$ref": "#/components/schemas/DatasetRestApi.get.User"
+ "$ref": "#/components/schemas/DatasetRestApi.get.User1"
},
"schema": {
"maxLength": 255,
"nullable": true,
"type": "string"
},
+ "select_star": {
+ "readOnly": true
+ },
"sql": {
"nullable": true,
"type": "string"
@@ -4594,11 +4959,6 @@
"nullable": true,
"type": "string"
},
- "uuid": {
- "format": "uuid",
- "nullable": true,
- "type": "string"
- },
"verbose_name": {
"maxLength": 1024,
"nullable": true,
@@ -4697,6 +5057,23 @@
"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,
@@ -4722,6 +5099,23 @@
],
"type": "object"
},
+ "DatasetRestApi.get.User2": {
+ "properties": {
+ "first_name": {
+ "maxLength": 64,
+ "type": "string"
+ },
+ "last_name": {
+ "maxLength": 64,
+ "type": "string"
+ }
+ },
+ "required": [
+ "first_name",
+ "last_name"
+ ],
+ "type": "object"
+ },
"DatasetRestApi.get_list": {
"properties": {
"changed_by": {
@@ -4875,6 +5269,10 @@
"minLength": 0,
"type": "string"
},
+ "sql": {
+ "nullable": true,
+ "type": "string"
+ },
"table_name": {
"maxLength": 250,
"minLength": 1,
@@ -5006,115 +5404,41 @@
"type": "string"
},
"schema": {
- "description": "Datasource schema",
- "type": "string"
- }
- },
- "required": [
- "datasource_type"
- ],
- "type": "object"
- },
- "DistincResponseSchema": {
- "properties": {
- "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",
+ "description": "Datasource schema",
"type": "string"
}
},
+ "required": [
+ "datasource_type"
+ ],
"type": "object"
},
- "EmbeddedDashboardRestApi.post": {
+ "DistincResponseSchema": {
"properties": {
- "uuid": {
- "format": "uuid",
- "type": "string"
+ "count": {
+ "description": "The total number of distinct values",
+ "format": "int32",
+ "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 +5512,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 +6073,7 @@
"type": "string"
},
"tracking_url": {
- "nullable": true,
- "type": "string"
+ "readOnly": true
}
},
"required": [
@@ -5840,6 +6182,10 @@
},
"RelatedResultResponse": {
"properties": {
+ "extra": {
+ "description": "The extra metadata for related item",
+ "type": "object"
+ },
"text": {
"description": "The related item string representation",
"type": "string"
@@ -6027,6 +6373,9 @@
"nullable": true,
"type": "string"
},
+ "extra": {
+ "readOnly": true
+ },
"force_screenshot": {
"nullable": true,
"type": "boolean"
@@ -6255,6 +6604,9 @@
"nullable": true,
"type": "string"
},
+ "extra": {
+ "readOnly": true
+ },
"id": {
"format": "int32",
"type": "integer"
@@ -7143,6 +7495,9 @@
"nullable": true,
"type": "string"
},
+ "extra": {
+ "type": "object"
+ },
"force_screenshot": {
"type": "boolean"
},
@@ -7867,6 +8222,9 @@
},
"SavedQueryRestApi.get": {
"properties": {
+ "changed_on_delta_humanized": {
+ "readOnly": true
+ },
"created_by": {
"$ref": "#/components/schemas/SavedQueryRestApi.get.User"
},
@@ -7897,6 +8255,10 @@
},
"sql_tables": {
"readOnly": true
+ },
+ "template_parameters": {
+ "nullable": true,
+ "type": "string"
}
},
"type": "object"
@@ -8059,6 +8421,10 @@
"sql": {
"nullable": true,
"type": "string"
+ },
+ "template_parameters": {
+ "nullable": true,
+ "type": "string"
}
},
"type": "object"
@@ -8087,6 +8453,10 @@
"sql": {
"nullable": true,
"type": "string"
+ },
+ "template_parameters": {
+ "nullable": true,
+ "type": "string"
}
},
"type": "object"
@@ -8112,6 +8482,93 @@
},
"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"
+ },
+ "StopQuerySchema": {
+ "properties": {
+ "client_id": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
"TableExtraMetadataResponseSchema": {
"properties": {
"clustering": {
@@ -8767,99 +9224,6 @@
]
}
},
- "/api/v1/annotation_layer/": {
- "delete": {
- "description": "Deletes multiple annotation layers in a bulk operation.",
- "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"
- },
- "404": {
- "$ref": "#/components/responses/404"
- },
- "500": {
- "$ref": "#/components/responses/500"
- }
- },
- "security": [
- {
- "jwt": []
- }
- ],
- "tags": [
- "Advanced Data Type"
- ]
- }
- },
"/api/v1/annotation_layer/": {
"delete": {
"description": "Deletes multiple annotation layers in a bulk operation.",
@@ -10004,13 +10368,49 @@
}
}
},
- "description": "Async event results"
+ "description": "Async event results"
+ },
+ "401": {
+ "$ref": "#/components/responses/401"
+ },
+ "500": {
+ "$ref": "#/components/responses/500"
+ }
+ },
+ "security": [
+ {
+ "jwt": []
+ }
+ ],
+ "tags": [
+ "AsyncEventsRestApi"
+ ]
+ }
+ },
+ "/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"
},
- "500": {
- "$ref": "#/components/responses/500"
+ "403": {
+ "$ref": "#/components/responses/403"
}
},
"security": [
@@ -10019,7 +10419,7 @@
}
],
"tags": [
- "AsyncEventsRestApi"
+ "Available Domains"
]
}
},
@@ -11004,6 +11404,14 @@
"schema": {
"type": "string"
}
+ },
+ {
+ "description": "Should the queries be forced to load from the source",
+ "in": "query",
+ "name": "force",
+ "schema": {
+ "type": "boolean"
+ }
}
],
"responses": {
@@ -13806,6 +14214,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"
@@ -13821,10 +14239,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"
@@ -14121,26 +14535,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": {
@@ -14148,52 +14552,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"
@@ -14201,9 +14564,6 @@
"401": {
"$ref": "#/components/responses/401"
},
- "404": {
- "$ref": "#/components/responses/404"
- },
"422": {
"$ref": "#/components/responses/422"
},
@@ -14576,36 +14936,17 @@
]
}
},
- "/api/v1/database/{pk}/select_star/{table_name}/{schema_name}/": {
- "get": {
- "description": "Get database select star for table",
+ "/api/v1/database/{pk}/ssh_tunnel/": {
+ "delete": {
+ "description": "Deletes a SSH Tunnel.",
"parameters": [
{
- "description": "The database id",
"in": "path",
"name": "pk",
"required": true,
"schema": {
"type": "integer"
}
- },
- {
- "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"
- }
}
],
"responses": {
@@ -14613,18 +14954,23 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/SelectStarResponseSchema"
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ },
+ "type": "object"
}
}
},
- "description": "SQL statement for a select star for table"
- },
- "400": {
- "$ref": "#/components/responses/400"
+ "description": "SSH Tunnel deleted"
},
"401": {
"$ref": "#/components/responses/401"
},
+ "403": {
+ "$ref": "#/components/responses/403"
+ },
"404": {
"$ref": "#/components/responses/404"
},
@@ -14784,7 +15130,7 @@
]
}
},
- "/api/v1/database/{pk}/validate_sql": {
+ "/api/v1/database/{pk}/validate_sql/": {
"post": {
"description": "Validates arbitrary SQL.",
"parameters": [
@@ -15220,16 +15566,6 @@
"/api/v1/dataset/duplicate": {
"post": {
"description": "Duplicates a Dataset",
- "parameters": [
- {
- "in": "path",
- "name": "pk",
- "required": true,
- "schema": {
- "type": "integer"
- }
- }
- ],
"requestBody": {
"content": {
"application/json": {
@@ -15242,20 +15578,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"
@@ -15928,23 +16267,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"
}
}
],
@@ -15955,27 +16288,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"
}
@@ -15986,19 +16313,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.<br/><br/>\\nThe information can be assembled from:<br/> - The cache using a form_data_key<br/> - The metadata database using a permalink_key<br/> - 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": "datasource_id",
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "in": "query",
+ "name": "datasource_type",
"schema": {
"type": "string"
}
@@ -16009,16 +16362,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"
@@ -16026,6 +16377,9 @@
"404": {
"$ref": "#/components/responses/404"
},
+ "422": {
+ "$ref": "#/components/responses/422"
+ },
"500": {
"$ref": "#/components/responses/500"
}
@@ -16035,8 +16389,9 @@
"jwt": []
}
],
+ "summary": "Assembles Explore related information (form_data, slice, dataset)\\n in a single endpoint.",
"tags": [
- "Embedded Dashboard"
+ "Explore"
]
}
},
@@ -17001,6 +17356,59 @@
]
}
},
+ "/api/v1/query/stop": {
+ "post": {
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StopQuerySchema"
+ }
+ }
+ },
+ "description": "Stop query schema",
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "properties": {
+ "result": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ }
+ }
+ },
+ "description": "Query stopped"
+ },
+ "400": {
+ "$ref": "#/components/responses/400"
+ },
+ "401": {
+ "$ref": "#/components/responses/401"
+ },
+ "404": {
+ "$ref": "#/components/responses/404"
+ },
+ "500": {
+ "$ref": "#/components/responses/500"
+ }
+ },
+ "security": [
+ {
+ "jwt": []
+ }
+ ],
+ "summary": "Manually stop a query with client_id",
+ "tags": [
+ "Queries"
+ ]
+ }
+ },
"/api/v1/query/{pk}": {
"get": {
"description": "Get query detail information.",
diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js
index 12487d1a94..d6447e808c 100644
--- a/superset-frontend/src/SqlLab/actions/sqlLab.js
+++ b/superset-frontend/src/SqlLab/actions/sqlLab.js
@@ -450,9 +450,9 @@ export function validateQuery(queryEditor, sql) {
export function postStopQuery(query) {
return function (dispatch) {
return SupersetClient.post({
- endpoint: '/superset/stop_query/',
- postPayload: { client_id: query.id },
- stringify: false,
+ endpoint: '/api/v1/query/stop',
+ body: JSON.stringify({ client_id: query.id }),
+ headers: { 'Content-Type': 'application/json' },
})
.then(() => dispatch(stopQuery(query)))
.then(() => dispatch(addSuccessToast(t('Query was stopped.'))))
diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.test.js b/superset-frontend/src/SqlLab/actions/sqlLab.test.js
index acc79031ed..fb6ff470b4 100644
--- a/superset-frontend/src/SqlLab/actions/sqlLab.test.js
+++ b/superset-frontend/src/SqlLab/actions/sqlLab.test.js
@@ -317,11 +317,15 @@ describe('async actions', () => {
});
describe('postStopQuery', () => {
- const stopQueryEndpoint = 'glob:*/superset/stop_query/*';
+ const stopQueryEndpoint = 'glob:*/api/v1/query/stop';
fetchMock.post(stopQueryEndpoint, {});
+ const baseQuery = {
+ ...query,
+ id: 'test_foo',
+ };
const makeRequest = () => {
- const request = actions.postStopQuery(query);
+ const request = actions.postStopQuery(baseQuery);
return request(dispatch);
};
@@ -346,7 +350,8 @@ describe('async actions', () => {
return makeRequest().then(() => {
const call = fetchMock.calls(stopQueryEndpoint)[0];
- expect(call[1].body.get('client_id')).toBe(query.id);
+ const body = JSON.parse(call[1].body);
+ expect(body.client_id).toBe(baseQuery.id);
});
});
});
diff --git a/superset/constants.py b/superset/constants.py
index ea7920ff2f..10de5c52f0 100644
--- a/superset/constants.py
+++ b/superset/constants.py
@@ -140,6 +140,7 @@ MODEL_API_RW_METHOD_PERMISSION_MAP = {
"get_data": "read",
"samples": "read",
"delete_ssh_tunnel": "write",
+ "stop_query": "read",
}
EXTRA_FORM_DATA_APPEND_KEYS = {
diff --git a/superset/exceptions.py b/superset/exceptions.py
index 153d7439eb..963bf96682 100644
--- a/superset/exceptions.py
+++ b/superset/exceptions.py
@@ -266,3 +266,7 @@ class InvalidPayloadSchemaError(SupersetErrorException):
class SupersetCancelQueryException(SupersetException):
status = 422
+
+
+class QueryNotFoundException(SupersetException):
+ status = 404
diff --git a/superset/queries/api.py b/superset/queries/api.py
index 1fb342f067..51ba148603 100644
--- a/superset/queries/api.py
+++ b/superset/queries/api.py
@@ -16,14 +16,29 @@
# under the License.
import logging
+import backoff
+from flask_appbuilder.api import expose, protect, request, safe
from flask_appbuilder.models.sqla.interface import SQLAInterface
+from superset import db, event_logger
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod
from superset.databases.filters import DatabaseFilter
+from superset.exceptions import SupersetException
from superset.models.sql_lab import Query
+from superset.queries.dao import QueryDAO
from superset.queries.filters import QueryFilter
-from superset.queries.schemas import openapi_spec_methods_override, QuerySchema
-from superset.views.base_api import BaseSupersetModelRestApi, RelatedFieldFilter
+from superset.queries.schemas import (
+ openapi_spec_methods_override,
+ QuerySchema,
+ StopQuerySchema,
+)
+from superset.superset_typing import FlaskResponse
+from superset.views.base_api import (
+ BaseSupersetModelRestApi,
+ RelatedFieldFilter,
+ requires_json,
+ statsd_metrics,
+)
from superset.views.filters import BaseFilterRelatedUsers, FilterRelatedOwners
logger = logging.getLogger(__name__)
@@ -43,6 +58,7 @@ class QueryRestApi(BaseSupersetModelRestApi):
RouteMethod.GET_LIST,
RouteMethod.RELATED,
RouteMethod.DISTINCT,
+ "stop_query",
}
list_columns = [
@@ -95,9 +111,11 @@ class QueryRestApi(BaseSupersetModelRestApi):
base_filters = [["id", QueryFilter, lambda: []]]
base_order = ("changed_on", "desc")
list_model_schema = QuerySchema()
+ stop_query_schema = StopQuerySchema()
openapi_spec_tag = "Queries"
openapi_spec_methods = openapi_spec_methods_override
+ openapi_spec_component_schemas = (StopQuerySchema,)
order_columns = [
"changed_on",
@@ -123,3 +141,59 @@ class QueryRestApi(BaseSupersetModelRestApi):
base_related_field_filters = {"database": [["id", DatabaseFilter, lambda: []]]}
allowed_rel_fields = {"database", "user"}
allowed_distinct_fields = {"status"}
+
+ @expose("/stop", methods=["POST"])
+ @protect()
+ @safe
+ @statsd_metrics
+ @event_logger.log_this_with_context(
+ action=lambda self, *args, **kwargs: f"{self.__class__.__name__}"
+ f".stop_query",
+ log_to_statsd=False,
+ )
+ @backoff.on_exception(
+ backoff.constant,
+ Exception,
+ interval=1,
+ on_backoff=lambda details: db.session.rollback(),
+ on_giveup=lambda details: db.session.rollback(),
+ max_tries=5,
+ )
+ @requires_json
+ def stop_query(self) -> FlaskResponse:
+ """Manually stop a query with client_id
+ ---
+ post:
+ summary: Manually stop a query with client_id
+ requestBody:
+ description: Stop query schema
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/StopQuerySchema'
+ responses:
+ 200:
+ description: Query stopped
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ result:
+ type: string
+ 400:
+ $ref: '#/components/responses/400'
+ 401:
+ $ref: '#/components/responses/401'
+ 404:
+ $ref: '#/components/responses/404'
+ 500:
+ $ref: '#/components/responses/500'
+ """
+ try:
+ body = self.stop_query_schema.load(request.json)
+ QueryDAO.stop_query(body["client_id"])
+ return self.response(200, result="OK")
+ except SupersetException as ex:
+ return self.response(ex.status, message=ex.message)
diff --git a/superset/queries/dao.py b/superset/queries/dao.py
index c7d59343e8..5867b2917d 100644
--- a/superset/queries/dao.py
+++ b/superset/queries/dao.py
@@ -18,10 +18,14 @@ import logging
from datetime import datetime
from typing import Any, Dict
+from superset import sql_lab
+from superset.common.db_query_status import QueryStatus
from superset.dao.base import BaseDAO
+from superset.exceptions import QueryNotFoundException, SupersetCancelQueryException
from superset.extensions import db
from superset.models.sql_lab import Query, SavedQuery
from superset.queries.filters import QueryFilter
+from superset.utils.dates import now_as_float
logger = logging.getLogger(__name__)
@@ -56,3 +60,26 @@ class QueryDAO(BaseDAO):
columns = payload.get("columns", {})
db.session.add(query)
query.set_extra_json_key("columns", columns)
+
+ @staticmethod
+ def stop_query(client_id: str) -> None:
+ query = db.session.query(Query).filter_by(client_id=client_id).one_or_none()
+ if not query:
+ raise QueryNotFoundException(f"Query with client_id {client_id} not found")
+
+ if query.status in [
+ QueryStatus.FAILED,
+ QueryStatus.SUCCESS,
+ QueryStatus.TIMED_OUT,
+ ]:
+ logger.warning(
+ "Query with client_id could not be stopped: query already complete",
+ )
+ return
+
+ if not sql_lab.cancel_query(query):
+ raise SupersetCancelQueryException("Could not cancel query")
+
+ query.status = QueryStatus.STOPPED
+ query.end_time = now_as_float()
+ db.session.commit()
diff --git a/superset/queries/schemas.py b/superset/queries/schemas.py
index f11cf37127..a8c1e2bbcb 100644
--- a/superset/queries/schemas.py
+++ b/superset/queries/schemas.py
@@ -67,3 +67,11 @@ class QuerySchema(Schema):
# pylint: disable=no-self-use
def get_sql_tables(self, obj: Query) -> List[Table]:
return obj.sql_tables
+
+
+class StopQuerySchema(Schema):
+ """
+ Schema for the stop_query API call.
+ """
+
+ client_id = fields.String()
diff --git a/superset/views/core.py b/superset/views/core.py
index 62a7c5b963..b9e1981028 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -2298,6 +2298,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
on_giveup=lambda details: db.session.rollback(),
max_tries=5,
)
+ @deprecated()
def stop_query(self) -> FlaskResponse:
client_id = request.form.get("client_id")
query = db.session.query(Query).filter_by(client_id=client_id).one()
diff --git a/tests/integration_tests/queries/api_tests.py b/tests/integration_tests/queries/api_tests.py
index eaf4e00576..8c193662a1 100644
--- a/tests/integration_tests/queries/api_tests.py
+++ b/tests/integration_tests/queries/api_tests.py
@@ -17,6 +17,7 @@
# isort:skip_file
"""Unit tests for Superset"""
from datetime import datetime, timedelta
+from unittest import mock
import json
import random
import string
@@ -392,3 +393,54 @@ class TestQueryApi(SupersetTestCase):
# rollback changes
db.session.delete(query)
db.session.commit()
+
+ @mock.patch("superset.sql_lab.cancel_query")
+ @mock.patch("superset.views.core.db.session")
+ def test_stop_query_not_found(
+ self, mock_superset_db_session, mock_sql_lab_cancel_query
+ ):
+ """
+ Handles stop query when the DB engine spec does not
+ have a cancel query method (with invalid client_id).
+ """
+ form_data = {"client_id": "foo2"}
+ query_mock = mock.Mock()
+ query_mock.return_value = None
+ self.login(username="admin")
+ mock_superset_db_session.query().filter_by().one_or_none = query_mock
+ mock_sql_lab_cancel_query.return_value = True
+ rv = self.client.post(
+ "/api/v1/query/stop",
+ data=json.dumps(form_data),
+ content_type="application/json",
+ )
+
+ assert rv.status_code == 404
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["message"] == "Query with client_id foo2 not found"
+
+ @mock.patch("superset.sql_lab.cancel_query")
+ @mock.patch("superset.views.core.db.session")
+ def test_stop_query(self, mock_superset_db_session, mock_sql_lab_cancel_query):
+ """
+ Handles stop query when the DB engine spec does not
+ have a cancel query method.
+ """
+ form_data = {"client_id": "foo"}
+ query_mock = mock.Mock()
+ query_mock.client_id = "foo"
+ query_mock.status = QueryStatus.RUNNING
+ self.login(username="admin")
+ mock_superset_db_session.query().filter_by().one_or_none().return_value = (
+ query_mock
+ )
+ mock_sql_lab_cancel_query.return_value = True
+ rv = self.client.post(
+ "/api/v1/query/stop",
+ data=json.dumps(form_data),
+ content_type="application/json",
+ )
+
+ assert rv.status_code == 200
+ data = json.loads(rv.data.decode("utf-8"))
+ assert data["result"] == "OK"
diff --git a/tests/unit_tests/dao/queries_test.py b/tests/unit_tests/dao/queries_test.py
index 8e2a458434..590ba92d48 100644
--- a/tests/unit_tests/dao/queries_test.py
+++ b/tests/unit_tests/dao/queries_test.py
@@ -15,11 +15,14 @@
# specific language governing permissions and limitations
# under the License.
import json
-from typing import Iterator
+from typing import Any, Iterator
import pytest
+from pytest_mock import MockFixture
from sqlalchemy.orm.session import Session
+from superset.exceptions import QueryNotFoundException, SupersetCancelQueryException
+
def test_query_dao_save_metadata(session: Session) -> None:
from superset.models.core import Database
@@ -53,3 +56,163 @@ def test_query_dao_save_metadata(session: Session) -> None:
query = session.query(Query).one()
QueryDAO.save_metadata(query=query, payload={"columns": []})
assert query.extra.get("columns", None) == []
+
+
+def test_query_dao_stop_query_not_found(
+ mocker: MockFixture, app: Any, session: Session
+) -> None:
+ from superset.common.db_query_status import QueryStatus
+ from superset.models.core import Database
+ from superset.models.sql_lab import Query
+
+ engine = session.get_bind()
+ Query.metadata.create_all(engine) # pylint: disable=no-member
+
+ db = Database(database_name="my_database", sqlalchemy_uri="sqlite://")
+
+ query_obj = Query(
+ client_id="foo",
+ database=db,
+ tab_name="test_tab",
+ sql_editor_id="test_editor_id",
+ sql="select * from bar",
+ select_sql="select * from bar",
+ executed_sql="select * from bar",
+ limit=100,
+ select_as_cta=False,
+ rows=100,
+ error_message="none",
+ results_key="abc",
+ status=QueryStatus.RUNNING,
+ )
+
+ session.add(db)
+ session.add(query_obj)
+
+ mocker.patch("superset.sql_lab.cancel_query", return_value=False)
+
+ from superset.queries.dao import QueryDAO
+
+ with pytest.raises(QueryNotFoundException):
+ QueryDAO.stop_query("foo2")
+
+ query = session.query(Query).one()
+ assert query.status == QueryStatus.RUNNING
+
+
+def test_query_dao_stop_query_not_running(
+ mocker: MockFixture, app: Any, session: Session
+) -> None:
+ from superset.common.db_query_status import QueryStatus
+ from superset.models.core import Database
+ from superset.models.sql_lab import Query
+
+ engine = session.get_bind()
+ Query.metadata.create_all(engine) # pylint: disable=no-member
+
+ db = Database(database_name="my_database", sqlalchemy_uri="sqlite://")
+
+ query_obj = Query(
+ client_id="foo",
+ database=db,
+ tab_name="test_tab",
+ sql_editor_id="test_editor_id",
+ sql="select * from bar",
+ select_sql="select * from bar",
+ executed_sql="select * from bar",
+ limit=100,
+ select_as_cta=False,
+ rows=100,
+ error_message="none",
+ results_key="abc",
+ status=QueryStatus.FAILED,
+ )
+
+ session.add(db)
+ session.add(query_obj)
+
+ from superset.queries.dao import QueryDAO
+
+ QueryDAO.stop_query(query_obj.client_id)
+ query = session.query(Query).one()
+ assert query.status == QueryStatus.FAILED
+
+
+def test_query_dao_stop_query_failed(
+ mocker: MockFixture, app: Any, session: Session
+) -> None:
+ from superset.common.db_query_status import QueryStatus
+ from superset.models.core import Database
+ from superset.models.sql_lab import Query
+
+ engine = session.get_bind()
+ Query.metadata.create_all(engine) # pylint: disable=no-member
+
+ db = Database(database_name="my_database", sqlalchemy_uri="sqlite://")
+
+ query_obj = Query(
+ client_id="foo",
+ database=db,
+ tab_name="test_tab",
+ sql_editor_id="test_editor_id",
+ sql="select * from bar",
+ select_sql="select * from bar",
+ executed_sql="select * from bar",
+ limit=100,
+ select_as_cta=False,
+ rows=100,
+ error_message="none",
+ results_key="abc",
+ status=QueryStatus.RUNNING,
+ )
+
+ session.add(db)
+ session.add(query_obj)
+
+ mocker.patch("superset.sql_lab.cancel_query", return_value=False)
+
+ from superset.queries.dao import QueryDAO
+
+ with pytest.raises(SupersetCancelQueryException):
+ QueryDAO.stop_query(query_obj.client_id)
+
+ query = session.query(Query).one()
+ assert query.status == QueryStatus.RUNNING
+
+
+def test_query_dao_stop_query(mocker: MockFixture, app: Any, session: Session) -> None:
+ from superset.common.db_query_status import QueryStatus
+ from superset.models.core import Database
+ from superset.models.sql_lab import Query
+
+ engine = session.get_bind()
+ Query.metadata.create_all(engine) # pylint: disable=no-member
+
+ db = Database(database_name="my_database", sqlalchemy_uri="sqlite://")
+
+ query_obj = Query(
+ client_id="foo",
+ database=db,
+ tab_name="test_tab",
+ sql_editor_id="test_editor_id",
+ sql="select * from bar",
+ select_sql="select * from bar",
+ executed_sql="select * from bar",
+ limit=100,
+ select_as_cta=False,
+ rows=100,
+ error_message="none",
+ results_key="abc",
+ status=QueryStatus.RUNNING,
+ )
+
+ session.add(db)
+ session.add(query_obj)
+
+ mocker.patch("superset.sql_lab.cancel_query", return_value=True)
+
+ from superset.queries.dao import QueryDAO
+
+ QueryDAO.stop_query(query_obj.client_id)
+ query = session.query(Query).one()
+ assert query.status == QueryStatus.STOPPED