diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json index 29d9d7e8a9543..ce84f368c8fc4 100644 --- a/docs/static/resources/openapi.json +++ b/docs/static/resources/openapi.json @@ -93,31 +93,6 @@ } }, "schemas": { - "AdvancedDataTypeSchema": { - "properties": { - "display_value": { - "description": "The string representation of the parsed values", - "type": "string" - }, - "error_message": { - "type": "string" - }, - "valid_filter_operators": { - "items": { - "type": "string" - }, - "type": "array" - }, - "values": { - "items": { - "description": "parsed value (can be any value)", - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, "AnnotationLayer": { "properties": { "annotationType": { @@ -257,7 +232,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 +243,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 +389,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", @@ -805,12 +780,8 @@ "type": { "description": "Datasource type", "enum": [ - "sl_table", - "table", - "dataset", - "query", - "saved_query", - "view" + "druid", + "table" ], "type": "string" } @@ -1060,24 +1031,22 @@ "operation": { "description": "Post processing operation type", "enum": [ - "_flatten_column_after_pivot", "aggregate", "boxplot", - "compare", "contribution", "cum", - "diff", - "flatten", "geodetic_parse", "geohash_decode", "geohash_encode", "pivot", "prophet", - "rename", - "resample", "rolling", "select", - "sort" + "sort", + "diff", + "compare", + "resample", + "flatten" ], "example": "aggregate", "type": "string" @@ -1564,9 +1533,6 @@ "nullable": true, "type": "string" }, - "is_managed_externally": { - "type": "boolean" - }, "owners": { "$ref": "#/components/schemas/ChartDataRestApi.get.User" }, @@ -1651,7 +1617,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 +1632,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/ChartDataRestApi.get_list.User1" + "$ref": "#/components/schemas/ChartDataRestApi.get_list.User2" }, "datasource_id": { "format": "int32", @@ -1698,16 +1664,13 @@ "format": "int32", "type": "integer" }, - "is_managed_externally": { - "type": "boolean" - }, "last_saved_at": { "format": "date-time", "nullable": true, "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.User3" @@ -1760,6 +1723,10 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -1777,10 +1744,6 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, "last_name": { "maxLength": 64, "type": "string" @@ -1878,11 +1841,8 @@ "datasource_type": { "description": "The type of dataset/datasource identified on `datasource_id`.", "enum": [ - "sl_table", + "druid", "table", - "dataset", - "query", - "saved_query", "view" ], "type": "string" @@ -1984,11 +1944,8 @@ "datasource_type": { "description": "The type of dataset/datasource identified on `datasource_id`.", "enum": [ - "sl_table", + "druid", "table", - "dataset", - "query", - "saved_query", "view" ], "nullable": true, @@ -2231,6 +2188,9 @@ "description": "Form data from the Explore controls used to form the chart's data query.", "type": "object" }, + "modified": { + "type": "string" + }, "slice_id": { "format": "int32", "type": "integer" @@ -2322,9 +2282,6 @@ "nullable": true, "type": "string" }, - "is_managed_externally": { - "type": "boolean" - }, "owners": { "$ref": "#/components/schemas/ChartRestApi.get.User" }, @@ -2409,7 +2366,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 +2381,7 @@ "readOnly": true }, "created_by": { - "$ref": "#/components/schemas/ChartRestApi.get_list.User1" + "$ref": "#/components/schemas/ChartRestApi.get_list.User2" }, "datasource_id": { "format": "int32", @@ -2456,16 +2413,13 @@ "format": "int32", "type": "integer" }, - "is_managed_externally": { - "type": "boolean" - }, "last_saved_at": { "format": "date-time", "nullable": true, "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.User3" @@ -2518,6 +2472,10 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, "last_name": { "maxLength": 64, "type": "string" @@ -2535,10 +2493,6 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, "last_name": { "maxLength": 64, "type": "string" @@ -2636,11 +2590,8 @@ "datasource_type": { "description": "The type of dataset/datasource identified on `datasource_id`.", "enum": [ - "sl_table", + "druid", "table", - "dataset", - "query", - "saved_query", "view" ], "type": "string" @@ -2742,11 +2693,8 @@ "datasource_type": { "description": "The type of dataset/datasource identified on `datasource_id`.", "enum": [ - "sl_table", + "druid", "table", - "dataset", - "query", - "saved_query", "view" ], "nullable": true, @@ -2856,13 +2804,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", @@ -3139,10 +3087,6 @@ "format": "int32", "type": "integer" }, - "is_managed_externally": { - "nullable": true, - "type": "boolean" - }, "json_metadata": { "description": "This JSON object is generated dynamically when clicking the save or overwrite button in the dashboard view. It is exposed here for reference and for power users who may want to alter specific parameters.", "type": "string" @@ -3182,7 +3126,6 @@ "properties": { "filterState": { "description": "Native filter state", - "nullable": true, "type": "object" }, "hash": { @@ -3200,6 +3143,9 @@ "type": "array" } }, + "required": [ + "filterState" + ], "type": "object" }, "DashboardRestApi.get": { @@ -3239,9 +3185,6 @@ "created_by": { "$ref": "#/components/schemas/DashboardRestApi.get_list.User1" }, - "created_on_delta_humanized": { - "readOnly": true - }, "css": { "nullable": true, "type": "string" @@ -3255,9 +3198,6 @@ "format": "int32", "type": "integer" }, - "is_managed_externally": { - "type": "boolean" - }, "json_metadata": { "nullable": true, "type": "string" @@ -3265,6 +3205,12 @@ "owners": { "$ref": "#/components/schemas/DashboardRestApi.get_list.User2" }, + "advanced_data_type": { + "maxLength": 255, + "minLength": 1, + "nullable": true, + "type": "string" + }, "position_json": { "nullable": true, "type": "string" @@ -3561,14 +3507,6 @@ }, "type": "object" }, - "Database1": { - "properties": { - "database_name": { - "type": "string" - } - }, - "type": "object" - }, "DatabaseFunctionNamesResponse": { "properties": { "function_names": { @@ -3722,9 +3660,6 @@ "nullable": true, "type": "boolean" }, - "is_managed_externally": { - "type": "boolean" - }, "parameters": { "readOnly": true }, @@ -4133,12 +4068,6 @@ }, "DatasetColumnsPut": { "properties": { - "advanced_data_type": { - "maxLength": 255, - "minLength": 1, - "nullable": true, - "type": "string" - }, "column_name": { "maxLength": 255, "minLength": 1, @@ -4233,24 +4162,6 @@ }, "type": "object" }, - "DatasetDuplicateSchema": { - "properties": { - "base_model_id": { - "format": "int32", - "type": "integer" - }, - "table_name": { - "maxLength": 250, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "base_model_id", - "table_name" - ], - "type": "object" - }, "DatasetMetricRestApi.get": { "properties": { "id": { @@ -4321,6 +4232,12 @@ "nullable": true, "type": "string" }, + "advanced_data_type": { + "maxLength": 255, + "minLength": 1, + "nullable": true, + "type": "string" + }, "uuid": { "format": "uuid", "nullable": true, @@ -4459,9 +4376,6 @@ "format": "int32", "type": "integer" }, - "is_managed_externally": { - "type": "boolean" - }, "is_sqllab_view": { "nullable": true, "type": "boolean" @@ -4597,11 +4511,6 @@ }, "DatasetRestApi.get.TableColumn": { "properties": { - "advanced_data_type": { - "maxLength": 255, - "nullable": true, - "type": "string" - }, "changed_on": { "format": "date-time", "nullable": true, @@ -4705,7 +4614,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 @@ -4748,7 +4657,7 @@ "readOnly": true }, "owners": { - "$ref": "#/components/schemas/DatasetRestApi.get_list.User" + "$ref": "#/components/schemas/DatasetRestApi.get_list.User1" }, "schema": { "maxLength": 255, @@ -4792,14 +4701,6 @@ "maxLength": 64, "type": "string" }, - "id": { - "format": "int32", - "type": "integer" - }, - "last_name": { - "maxLength": 64, - "type": "string" - }, "username": { "maxLength": 64, "type": "string" @@ -4807,7 +4708,6 @@ }, "required": [ "first_name", - "last_name", "username" ], "type": "object" @@ -4818,6 +4718,14 @@ "maxLength": 64, "type": "string" }, + "id": { + "format": "int32", + "type": "integer" + }, + "last_name": { + "maxLength": 64, + "type": "string" + }, "username": { "maxLength": 64, "type": "string" @@ -4825,6 +4733,7 @@ }, "required": [ "first_name", + "last_name", "username" ], "type": "object" @@ -4976,11 +4885,8 @@ "datasource_type": { "description": "The type of dataset/datasource identified on `datasource_id`.", "enum": [ - "sl_table", + "druid", "table", - "dataset", - "query", - "saved_query", "view" ], "type": "string" @@ -5020,80 +4926,6 @@ }, "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": "object" - }, - "EmbeddedDashboardRestApi.put": { - "properties": { - "uuid": { - "format": "uuid", - "type": "string" - } - }, - "type": "object" - }, "ExplorePermalinkPostSchema": { "properties": { "formData": { @@ -5277,31 +5109,18 @@ "format": "int32", "type": "integer" }, - "datasource_id": { - "description": "The datasource ID", + "dataset_id": { + "description": "The dataset ID", "format": "int32", "type": "integer" }, - "datasource_type": { - "description": "The datasource type", - "enum": [ - "sl_table", - "table", - "dataset", - "query", - "saved_query", - "view" - ], - "type": "string" - }, "form_data": { "description": "Any type of JSON supported text.", "type": "string" } }, "required": [ - "datasource_id", - "datasource_type", + "dataset_id", "form_data" ], "type": "object" @@ -5313,31 +5132,18 @@ "format": "int32", "type": "integer" }, - "datasource_id": { - "description": "The datasource ID", + "dataset_id": { + "description": "The dataset ID", "format": "int32", "type": "integer" }, - "datasource_type": { - "description": "The datasource type", - "enum": [ - "sl_table", - "table", - "dataset", - "query", - "saved_query", - "view" - ], - "type": "string" - }, "form_data": { "description": "Any type of JSON supported text.", "type": "string" } }, "required": [ - "datasource_id", - "datasource_type", + "dataset_id", "form_data" ], "type": "object" @@ -5659,16 +5465,18 @@ "properties": { "changed_on": { "format": "date-time", + "nullable": true, "type": "string" }, "database": { - "$ref": "#/components/schemas/Database1" + "$ref": "#/components/schemas/QueryRestApi.get_list.Database" }, "end_time": { - "format": "float", + "nullable": true, "type": "number" }, "executed_sql": { + "nullable": true, "type": "string" }, "id": { @@ -5677,37 +5485,89 @@ }, "rows": { "format": "int32", + "nullable": true, "type": "integer" }, "schema": { + "maxLength": 256, + "nullable": true, "type": "string" }, "sql": { + "nullable": true, "type": "string" }, "sql_tables": { "readOnly": true }, "start_time": { - "format": "float", + "nullable": true, "type": "number" }, "status": { + "maxLength": 16, + "nullable": true, "type": "string" }, "tab_name": { + "maxLength": 256, + "nullable": true, "type": "string" }, "tmp_table_name": { + "maxLength": 256, + "nullable": true, "type": "string" }, "tracking_url": { + "nullable": true, "type": "string" }, "user": { - "$ref": "#/components/schemas/User" + "$ref": "#/components/schemas/QueryRestApi.get_list.User" + } + }, + "required": [ + "database" + ], + "type": "object" + }, + "QueryRestApi.get_list.Database": { + "properties": { + "database_name": { + "maxLength": 250, + "type": "string" + } + }, + "required": [ + "database_name" + ], + "type": "object" + }, + "QueryRestApi.get_list.User": { + "properties": { + "first_name": { + "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" + ], "type": "object" }, "QueryRestApi.post": { @@ -6127,11 +5987,6 @@ "changed_on_delta_humanized": { "readOnly": true }, - "chart_id": { - "format": "int32", - "nullable": true, - "type": "integer" - }, "created_by": { "$ref": "#/components/schemas/ReportScheduleRestApi.get_list.User1" }, @@ -6152,11 +6007,6 @@ "crontab_humanized": { "readOnly": true }, - "dashboard_id": { - "format": "int32", - "nullable": true, - "type": "integer" - }, "description": { "nullable": true, "type": "string" @@ -6921,7 +6771,6 @@ "Pacific/Guam", "Pacific/Honolulu", "Pacific/Johnston", - "Pacific/Kanton", "Pacific/Kiritimati", "Pacific/Kosrae", "Pacific/Kwajalein", @@ -7646,7 +7495,6 @@ "Pacific/Guam", "Pacific/Honolulu", "Pacific/Johnston", - "Pacific/Kanton", "Pacific/Kiritimati", "Pacific/Kosrae", "Pacific/Kwajalein", @@ -8018,20 +7866,6 @@ }, "type": "object" }, - "TableExtraMetadataResponseSchema": { - "properties": { - "clustering": { - "type": "object" - }, - "metadata": { - "type": "object" - }, - "partitions": { - "type": "object" - } - }, - "type": "object" - }, "TableMetadataColumnsResponse": { "properties": { "duplicates_constraint": { @@ -8257,46 +8091,6 @@ }, "type": "object" }, - "ValidateSQLRequest": { - "properties": { - "schema": { - "nullable": true, - "type": "string" - }, - "sql": { - "description": "SQL statement to validate", - "type": "string" - }, - "template_params": { - "nullable": true, - "type": "object" - } - }, - "required": [ - "sql" - ], - "type": "object" - }, - "ValidateSQLResponse": { - "properties": { - "end_column": { - "format": "int32", - "type": "integer" - }, - "line_number": { - "format": "int32", - "type": "integer" - }, - "message": { - "type": "string" - }, - "start_column": { - "format": "int32", - "type": "integer" - } - }, - "type": "object" - }, "ValidatorConfigJSON": { "properties": { "op": { @@ -8318,26 +8112,6 @@ }, "type": "object" }, - "advanced_data_type_convert_schema": { - "properties": { - "type": { - "default": "port", - "type": "string" - }, - "values": { - "items": { - "default": "http" - }, - "minItems": 1, - "type": "array" - } - }, - "required": [ - "type", - "values" - ], - "type": "object" - }, "database_schemas_query_schema": { "properties": { "force": { @@ -8581,98 +8355,6 @@ }, "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" - }, - "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.", @@ -9679,6 +9361,9 @@ }, "description": "ZIP file" }, + "400": { + "$ref": "#/components/responses/400" + }, "401": { "$ref": "#/components/responses/401" }, @@ -9736,7 +9421,7 @@ } } }, - "description": "Assets import result" + "description": "Dashboard import result" }, "400": { "$ref": "#/components/responses/400" @@ -10756,7 +10441,7 @@ } ], "responses": { - "202": { + "200": { "content": { "application/json": { "schema": { @@ -10766,6 +10451,9 @@ }, "description": "Chart async result" }, + "302": { + "description": "Redirects to the current digest" + }, "400": { "$ref": "#/components/responses/400" }, @@ -10893,6 +10581,9 @@ }, "description": "Chart thumbnail image" }, + "302": { + "description": "Redirects to the current digest" + }, "400": { "$ref": "#/components/responses/400" }, @@ -11743,6 +11434,9 @@ }, "description": "Dashboard added" }, + "302": { + "description": "Redirects to the current digest" + }, "400": { "$ref": "#/components/responses/400" }, @@ -12450,6 +12144,9 @@ }, "description": "Dashboard" }, + "302": { + "description": "Redirects to the current digest" + }, "400": { "$ref": "#/components/responses/400" }, @@ -12505,6 +12202,9 @@ }, "description": "Dashboard chart definitions" }, + "302": { + "description": "Redirects to the current digest" + }, "400": { "$ref": "#/components/responses/400" }, @@ -12561,6 +12261,9 @@ }, "description": "Dashboard dataset definitions" }, + "302": { + "description": "Redirects to the current digest" + }, "400": { "$ref": "#/components/responses/400" }, @@ -12584,17 +12287,16 @@ ] } }, - "/api/v1/dashboard/{id_or_slug}/embedded": { + "/api/v1/dashboard/{pk}": { "delete": { - "description": "Removes a dashboard's embedded configuration.", + "description": "Deletes a Dashboard.", "parameters": [ { - "description": "The dashboard id or slug", "in": "path", - "name": "id_or_slug", + "name": "pk", "required": true, "schema": { - "type": "string" + "type": "integer" } } ], @@ -12612,11 +12314,20 @@ } } }, - "description": "Successfully removed the configuration" + "description": "Dashboard 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" } @@ -12630,227 +12341,15 @@ "Dashboards" ] }, - "get": { - "description": "Returns the dashboard's embedded configuration", + "put": { + "description": "Changes a Dashboard.", "parameters": [ { - "description": "The dashboard id or slug", "in": "path", - "name": "id_or_slug", + "name": "pk", "required": true, "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "result": { - "$ref": "#/components/schemas/EmbeddedDashboardResponseSchema" - } - }, - "type": "object" - } - } - }, - "description": "Result contains the embedded dashboard config" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [ - { - "jwt": [] - } - ], - "tags": [ - "Dashboards" - ] - }, - "post": { - "description": "Sets a dashboard's embedded configuration.", - "parameters": [ - { - "description": "The dashboard id or slug", - "in": "path", - "name": "id_or_slug", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmbeddedDashboardConfig" - } - } - }, - "description": "The embedded configuration to set", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "result": { - "$ref": "#/components/schemas/EmbeddedDashboardResponseSchema" - } - }, - "type": "object" - } - } - }, - "description": "Successfully set the configuration" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [ - { - "jwt": [] - } - ], - "tags": [ - "Dashboards" - ] - }, - "put": { - "description": "Sets a dashboard's embedded configuration.", - "parameters": [ - { - "description": "The dashboard id or slug", - "in": "path", - "name": "id_or_slug", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmbeddedDashboardConfig" - } - } - }, - "description": "The embedded configuration to set", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "result": { - "$ref": "#/components/schemas/EmbeddedDashboardResponseSchema" - } - }, - "type": "object" - } - } - }, - "description": "Successfully set the configuration" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [ - { - "jwt": [] - } - ], - "tags": [ - "Dashboards" - ] - } - }, - "/api/v1/dashboard/{pk}": { - "delete": { - "description": "Deletes a Dashboard.", - "parameters": [ - { - "in": "path", - "name": "pk", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "message": { - "type": "string" - } - }, - "type": "object" - } - } - }, - "description": "Dashboard 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": [ - "Dashboards" - ] - }, - "put": { - "description": "Changes a Dashboard.", - "parameters": [ - { - "in": "path", - "name": "pk", - "required": true, - "schema": { - "type": "integer" + "type": "integer" } } ], @@ -13319,9 +12818,6 @@ }, "description": "Thumbnail does not exist on cache, fired async to compute" }, - "302": { - "description": "Redirects to the current digest" - }, "401": { "$ref": "#/components/responses/401" }, @@ -13485,6 +12981,9 @@ }, "description": "Database added" }, + "302": { + "description": "Redirects to the current digest" + }, "400": { "$ref": "#/components/responses/400" }, @@ -14249,145 +13748,7 @@ }, "/api/v1/database/{pk}/select_star/{table_name}/": { "get": { - "description": "Get database select star for table", - "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": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SelectStarResponseSchema" - } - } - }, - "description": "SQL statement for a select star for table" - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "404": { - "$ref": "#/components/responses/404" - }, - "422": { - "$ref": "#/components/responses/422" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [ - { - "jwt": [] - } - ], - "tags": [ - "Database" - ] - } - }, - "/api/v1/database/{pk}/select_star/{table_name}/{schema_name}/": { - "get": { - "description": "Get database select star for table", - "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": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SelectStarResponseSchema" - } - } - }, - "description": "SQL statement for a select star for table" - }, - "400": { - "$ref": "#/components/responses/400" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "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", + "description": "Get database select star for table", "parameters": [ { "description": "The database id", @@ -14422,11 +13783,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TableMetadataResponseSchema" + "$ref": "#/components/schemas/SelectStarResponseSchema" } } }, - "description": "Table metadata information" + "description": "SQL statement for a select star for table" }, "400": { "$ref": "#/components/responses/400" @@ -14454,9 +13815,9 @@ ] } }, - "/api/v1/database/{pk}/table_extra/{table_name}/{schema_name}/": { + "/api/v1/database/{pk}/select_star/{table_name}/{schema_name}/": { "get": { - "description": "Response depends on each DB engine spec normally focused on partitions", + "description": "Get database select star for table", "parameters": [ { "description": "The database id", @@ -14491,11 +13852,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/TableExtraMetadataResponseSchema" + "$ref": "#/components/schemas/SelectStarResponseSchema" } } }, - "description": "Table extra metadata information" + "description": "SQL statement for a select star for table" }, "400": { "$ref": "#/components/responses/400" @@ -14518,55 +13879,53 @@ "jwt": [] } ], - "summary": "Get table extra metadata", "tags": [ "Database" ] } }, - "/api/v1/database/{pk}/validate_sql": { - "post": { - "description": "Validates arbitrary SQL.", + "/api/v1/database/{pk}/table/{table_name}/{schema_name}/": { + "get": { + "description": "Get database table metadata", "parameters": [ { + "description": "The database id", "in": "path", "name": "pk", "required": true, "schema": { "type": "integer" } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidateSQLRequest" - } + }, + { + "description": "Table name", + "in": "path", + "name": "table_name", + "required": true, + "schema": { + "type": "string" } }, - "description": "Validate SQL request", - "required": true - }, + { + "description": "Table schema", + "in": "path", + "name": "schema_name", + "required": true, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { "application/json": { "schema": { - "properties": { - "result": { - "description": "A List of SQL errors found on the statement", - "items": { - "$ref": "#/components/schemas/ValidateSQLResponse" - }, - "type": "array" - } - }, - "type": "object" + "$ref": "#/components/schemas/TableMetadataResponseSchema" } } }, - "description": "Validation result" + "description": "Table metadata information" }, "400": { "$ref": "#/components/responses/400" @@ -14577,6 +13936,9 @@ "404": { "$ref": "#/components/responses/404" }, + "422": { + "$ref": "#/components/responses/422" + }, "500": { "$ref": "#/components/responses/500" } @@ -14586,7 +13948,6 @@ "jwt": [] } ], - "summary": "Validates that arbitrary sql is acceptable for the given database", "tags": [ "Database" ] @@ -14957,75 +14318,6 @@ ] } }, - "/api/v1/dataset/duplicate": { - "post": { - "description": "Duplicates a Dataset", - "parameters": [ - { - "in": "path", - "name": "pk", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DatasetDuplicateSchema" - } - } - }, - "description": "Dataset schema", - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "message": { - "type": "string" - } - }, - "type": "object" - } - } - }, - "description": "Dataset duplicate" - }, - "400": { - "$ref": "#/components/responses/400" - }, - "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": [ - "Datasets" - ] - } - }, "/api/v1/dataset/export/": { "get": { "description": "Exports multiple datasets and downloads them as YAML files", @@ -15660,118 +14952,6 @@ ] } }, - "/api/v1/dataset/{pk}/samples": { - "get": { - "description": "get samples from a Dataset", - "parameters": [ - { - "in": "path", - "name": "pk", - "required": true, - "schema": { - "type": "integer" - } - }, - { - "in": "query", - "name": "force", - "schema": { - "type": "boolean" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "result": { - "$ref": "#/components/schemas/ChartDataResponseResult" - } - }, - "type": "object" - } - } - }, - "description": "Dataset samples" - }, - "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": [ - "Datasets" - ] - } - }, - "/api/v1/embedded_dashboard/{uuid}": { - "get": { - "description": "Get a report schedule log", - "parameters": [ - { - "description": "The embedded configuration uuid", - "in": "path", - "name": "uuid", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "result": { - "$ref": "#/components/schemas/EmbeddedDashboardResponseSchema" - } - }, - "type": "object" - } - } - }, - "description": "Result contains the embedded dashboard configuration" - }, - "401": { - "$ref": "#/components/responses/401" - }, - "404": { - "$ref": "#/components/responses/404" - }, - "500": { - "$ref": "#/components/responses/500" - } - }, - "security": [ - { - "jwt": [] - } - ], - "tags": [ - "Embedded Dashboard" - ] - } - }, "/api/v1/explore/form_data": { "post": { "description": "Stores a new form_data.", @@ -16421,34 +15601,6 @@ ] } }, - "/api/v1/me/roles/": { - "get": { - "description": "Returns the user roles corresponding to the agent making the request, or returns a 401 error if the user is unauthenticated.", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "result": { - "$ref": "#/components/schemas/UserResponseSchema" - } - }, - "type": "object" - } - } - }, - "description": "The current user" - }, - "401": { - "$ref": "#/components/responses/401" - } - }, - "tags": [ - "Current User" - ] - } - }, "/api/v1/menu/": { "get": { "description": "Get the menu data structure. Returns a forest like structure with the menu the user has access to", @@ -17040,9 +16192,6 @@ "404": { "$ref": "#/components/responses/404" }, - "422": { - "$ref": "#/components/responses/422" - }, "500": { "$ref": "#/components/responses/500" } @@ -17407,9 +16556,6 @@ "404": { "$ref": "#/components/responses/404" }, - "422": { - "$ref": "#/components/responses/422" - }, "500": { "$ref": "#/components/responses/500" } @@ -18395,9 +17541,6 @@ }, "description": "Result contains the guest token" }, - "400": { - "$ref": "#/components/responses/400" - }, "401": { "$ref": "#/components/responses/401" }, diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.test.jsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.test.jsx index a2c2ab6954778..2f23f45573311 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.test.jsx +++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.test.jsx @@ -41,7 +41,6 @@ const store = mockStore({}); const datasetsInfoEndpoint = 'glob:*/api/v1/dataset/_info*'; const datasetsOwnersEndpoint = 'glob:*/api/v1/dataset/related/owners*'; const datasetsSchemaEndpoint = 'glob:*/api/v1/dataset/distinct/schema*'; -const datasetsDuplicateEndpoint = 'glob:*/api/v1/dataset/duplicate*'; const databaseEndpoint = 'glob:*/api/v1/dataset/related/database*'; const datasetsEndpoint = 'glob:*/api/v1/dataset/?*'; @@ -64,7 +63,7 @@ const mockUser = { }; fetchMock.get(datasetsInfoEndpoint, { - permissions: ['can_read', 'can_write', 'can_duplicate'], + permissions: ['can_read', 'can_write'], }); fetchMock.get(datasetsOwnersEndpoint, { result: [], @@ -72,9 +71,6 @@ fetchMock.get(datasetsOwnersEndpoint, { fetchMock.get(datasetsSchemaEndpoint, { result: [], }); -fetchMock.post(datasetsDuplicateEndpoint, { - result: [], -}); fetchMock.get(datasetsEndpoint, { result: mockdatasets, dataset_count: 3, @@ -185,44 +181,6 @@ describe('DatasetList', () => { wrapper.find('[data-test="bulk-select-copy"]').text(), ).toMatchInlineSnapshot(`"3 Selected (2 Physical, 1 Virtual)"`); }); - - it('shows duplicate modal when duplicate action is clicked', async () => { - await waitForComponentToPaint(wrapper); - expect( - wrapper.find('[data-test="duplicate-modal-input"]').exists(), - ).toBeFalsy(); - act(() => { - wrapper - .find('#duplicate-action-tooltop') - .at(0) - .find('.action-button') - .props() - .onClick(); - }); - await waitForComponentToPaint(wrapper); - expect( - wrapper.find('[data-test="duplicate-modal-input"]').exists(), - ).toBeTruthy(); - }); - - it('calls the duplicate endpoint', async () => { - await waitForComponentToPaint(wrapper); - await act(async () => { - wrapper - .find('#duplicate-action-tooltop') - .at(0) - .find('.action-button') - .props() - .onClick(); - await waitForComponentToPaint(wrapper); - wrapper - .find('[data-test="duplicate-modal-input"]') - .at(0) - .props() - .onPressEnter(); - }); - expect(fetchMock.calls(/dataset\/duplicate/)).toHaveLength(1); - }); }); jest.mock('react-router-dom', () => ({ diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx index 265a692bb6ed1..107b072f25781 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx @@ -69,7 +69,6 @@ import { PASSWORDS_NEEDED_MESSAGE, CONFIRM_OVERWRITE_MESSAGE, } from './constants'; -import DuplicateDatasetModal from './DuplicateDatasetModal'; const FlexRowContainer = styled.div` align-items: center; @@ -120,11 +119,6 @@ type Dataset = { table_name: string; }; -interface VirtualDataset extends Dataset { - extra: Record; - sql: string; -} - interface DatasetListProps { addDangerToast: (msg: string) => void; addSuccessToast: (msg: string) => void; @@ -163,9 +157,6 @@ const DatasetList: FunctionComponent = ({ const [datasetCurrentlyEditing, setDatasetCurrentlyEditing] = useState(null); - const [datasetCurrentlyDuplicating, setDatasetCurrentlyDuplicating] = - useState(null); - const [importingDataset, showImportModal] = useState(false); const [passwordFields, setPasswordFields] = useState([]); const [preparingExport, setPreparingExport] = useState(false); @@ -187,7 +178,6 @@ const DatasetList: FunctionComponent = ({ const canEdit = hasPerm('can_write'); const canDelete = hasPerm('can_write'); const canCreate = hasPerm('can_write'); - const canDuplicate = hasPerm('can_duplicate'); const canExport = hasPerm('can_export') && isFeatureEnabled(FeatureFlag.VERSIONED_EXPORT); @@ -251,10 +241,6 @@ const DatasetList: FunctionComponent = ({ ), ); - const openDatasetDuplicateModal = (dataset: VirtualDataset) => { - setDatasetCurrentlyDuplicating(dataset); - }; - const handleBulkDatasetExport = (datasetsToExport: Dataset[]) => { const ids = datasetsToExport.map(({ id }) => id); handleResourceExport('dataset', ids, () => { @@ -411,8 +397,7 @@ const DatasetList: FunctionComponent = ({ const handleEdit = () => openDatasetEditModal(original); const handleDelete = () => openDatasetDeleteModal(original); const handleExport = () => handleBulkDatasetExport([original]); - const handleDuplicate = () => openDatasetDuplicateModal(original); - if (!canEdit && !canDelete && !canExport && !canDuplicate) { + if (!canEdit && !canDelete && !canExport) { return null; } return ( @@ -471,32 +456,16 @@ const DatasetList: FunctionComponent = ({ )} - {canDuplicate && original.kind === 'virtual' && ( - - - - - - )} ); }, Header: t('Actions'), id: 'actions', - hidden: !canEdit && !canDelete && !canDuplicate, + hidden: !canEdit && !canDelete, disableSortBy: true, }, ], - [canEdit, canDelete, canExport, openDatasetEditModal, canDuplicate], + [canEdit, canDelete, canExport, openDatasetEditModal], ); const filterTypes: Filters = useMemo( @@ -656,10 +625,6 @@ const DatasetList: FunctionComponent = ({ setDatasetCurrentlyEditing(null); }; - const closeDatasetDuplicateModal = () => { - setDatasetCurrentlyDuplicating(null); - }; - const handleDatasetDelete = ({ id, table_name: tableName }: Dataset) => { SupersetClient.delete({ endpoint: `/api/v1/dataset/${id}`, @@ -695,30 +660,6 @@ const DatasetList: FunctionComponent = ({ ); }; - const handleDatasetDuplicate = (newDatasetName: string) => { - if (datasetCurrentlyDuplicating === null) { - addDangerToast(t('There was an issue duplicating the dataset.')); - } - - SupersetClient.post({ - endpoint: `/api/v1/dataset/duplicate`, - postPayload: { - base_model_id: datasetCurrentlyDuplicating?.id, - table_name: newDatasetName, - }, - }).then( - () => { - setDatasetCurrentlyDuplicating(null); - refreshData(); - }, - createErrorHandler(errMsg => - addDangerToast( - t('There was an issue duplicating the selected datasets: %s', errMsg), - ), - ), - ); - }; - return ( <> @@ -753,11 +694,6 @@ const DatasetList: FunctionComponent = ({ show /> )} - void; - onDuplicate: (newDatasetName: string) => void; -} - -const DuplicateDatasetModal: FunctionComponent = ({ - dataset, - onHide, - onDuplicate, -}) => { - const [show, setShow] = useState(false); - const [disableSave, setDisableSave] = useState(false); - const [newDuplicateDatasetName, setNewDuplicateDatasetName] = - useState(''); - - const onChange = (event: React.ChangeEvent) => { - const targetValue = event.target.value ?? ''; - setNewDuplicateDatasetName(targetValue); - setDisableSave(targetValue === ''); - }; - - const duplicateDataset = () => { - onDuplicate(newDuplicateDatasetName); - }; - - useEffect(() => { - setNewDuplicateDatasetName(''); - setShow(dataset !== null); - }, [dataset]); - - return ( - - {t('New dataset name')} - - - ); -}; - -export default DuplicateDatasetModal; diff --git a/superset/datasets/api.py b/superset/datasets/api.py index 86faa8bb9df1c..6f2db1b631ba2 100644 --- a/superset/datasets/api.py +++ b/superset/datasets/api.py @@ -22,7 +22,7 @@ from zipfile import is_zipfile, ZipFile import yaml -from flask import g, request, Response, send_file +from flask import request, Response, send_file from flask_appbuilder.api import expose, protect, rison, safe from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_babel import ngettext @@ -37,7 +37,6 @@ from superset.datasets.commands.bulk_delete import BulkDeleteDatasetCommand from superset.datasets.commands.create import CreateDatasetCommand from superset.datasets.commands.delete import DeleteDatasetCommand -from superset.datasets.commands.duplicate import DuplicateDatasetCommand from superset.datasets.commands.exceptions import ( DatasetBulkDeleteFailedError, DatasetCreateFailedError, @@ -55,7 +54,6 @@ from superset.datasets.dao import DatasetDAO from superset.datasets.filters import DatasetCertifiedFilter, DatasetIsNullOrEmptyFilter from superset.datasets.schemas import ( - DatasetDuplicateSchema, DatasetPostSchema, DatasetPutSchema, DatasetRelatedObjectsResponse, @@ -92,7 +90,6 @@ class DatasetRestApi(BaseSupersetModelRestApi): "bulk_delete", "refresh", "related_objects", - "duplicate", } list_columns = [ "id", @@ -193,7 +190,6 @@ class DatasetRestApi(BaseSupersetModelRestApi): ] add_model_schema = DatasetPostSchema() edit_model_schema = DatasetPutSchema() - duplicate_model_schema = DatasetDuplicateSchema() add_columns = ["database", "schema", "table_name", "owners"] edit_columns = [ "table_name", @@ -230,10 +226,7 @@ class DatasetRestApi(BaseSupersetModelRestApi): apispec_parameter_schemas = { "get_export_ids_schema": get_export_ids_schema, } - openapi_spec_component_schemas = ( - DatasetRelatedObjectsResponse, - DatasetDuplicateSchema, - ) + openapi_spec_component_schemas = (DatasetRelatedObjectsResponse,) @expose("/", methods=["POST"]) @protect() @@ -525,77 +518,6 @@ def export(self, **kwargs: Any) -> Response: mimetype="application/text", ) - @expose("/duplicate", methods=["POST"]) - @protect() - @safe - @statsd_metrics - @event_logger.log_this_with_context( - action=lambda self, *args, **kwargs: f"{self.__class__.__name__}" f".duplicate", - log_to_statsd=False, - ) - @requires_json - def duplicate(self) -> Response: - """Duplicates a Dataset - --- - post: - description: >- - Duplicates a Dataset - requestBody: - description: Dataset schema - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/DatasetDuplicateSchema' - responses: - 201: - description: Dataset duplicated - content: - application/json: - schema: - type: object - properties: - id: - type: number - result: - $ref: '#/components/schemas/DatasetDuplicateSchema' - 400: - $ref: '#/components/responses/400' - 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' - """ - try: - item = self.duplicate_model_schema.load(request.json) - # This validates custom Schema with custom validations - except ValidationError as error: - return self.response_400(message=error.messages) - - try: - new_model = DuplicateDatasetCommand([g.user.id], item).run() - return self.response(201, id=new_model.id, result=item) - except DatasetInvalidError as ex: - return self.response_422( - message=ex.normalized_messages() - if isinstance(ex, ValidationError) - else str(ex) - ) - except DatasetCreateFailedError as ex: - logger.error( - "Error creating model %s: %s", - self.__class__.__name__, - str(ex), - exc_info=True, - ) - return self.response_422(message=str(ex)) - @expose("//refresh", methods=["PUT"]) @protect() @safe diff --git a/superset/datasets/commands/duplicate.py b/superset/datasets/commands/duplicate.py deleted file mode 100644 index 3ee538230b68c..0000000000000 --- a/superset/datasets/commands/duplicate.py +++ /dev/null @@ -1,133 +0,0 @@ -# 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, List - -from flask_appbuilder.models.sqla import Model -from flask_appbuilder.security.sqla.models import User -from flask_babel import gettext as __ -from marshmallow import ValidationError -from sqlalchemy.exc import SQLAlchemyError - -from superset.commands.base import BaseCommand, CreateMixin -from superset.commands.exceptions import DatasourceTypeInvalidError -from superset.connectors.sqla.models import SqlaTable, SqlMetric, TableColumn -from superset.dao.exceptions import DAOCreateFailedError -from superset.datasets.commands.exceptions import ( - DatasetDuplicateFailedError, - DatasetExistsValidationError, - DatasetInvalidError, - DatasetNotFoundError, -) -from superset.datasets.dao import DatasetDAO -from superset.errors import ErrorLevel, SupersetError, SupersetErrorType -from superset.exceptions import SupersetErrorException -from superset.extensions import db -from superset.models.core import Database -from superset.sql_parse import ParsedQuery - -logger = logging.getLogger(__name__) - - -class DuplicateDatasetCommand(CreateMixin, BaseCommand): - def __init__(self, user: User, data: Dict[str, Any]): - self._actor = user - self._base_model: SqlaTable = SqlaTable() - self._properties = data.copy() - - def run(self) -> Model: - self.validate() - try: - database_id = self._base_model.database_id - table_name = self._properties["table_name"] - owners = self._properties["owners"] - database = db.session.query(Database).get(database_id) - if not database: - raise SupersetErrorException( - SupersetError( - message=__("The database was not found."), - error_type=SupersetErrorType.DATABASE_NOT_FOUND_ERROR, - level=ErrorLevel.ERROR, - ), - status=404, - ) - table = SqlaTable(table_name=table_name, owners=owners) - table.database = database - table.schema = self._base_model.schema - table.template_params = self._base_model.template_params - table.is_sqllab_view = True - table.sql = ParsedQuery(self._base_model.sql).stripped() - db.session.add(table) - cols = [] - for config_ in self._base_model.columns: - column_name = config_.column_name - col = TableColumn( - column_name=column_name, - verbose_name=config_.verbose_name, - filterable=True, - groupby=True, - is_dttm=config_.is_dttm, - type=config_.type, - ) - cols.append(col) - table.columns = cols - mets = [] - for config_ in self._base_model.metrics: - metric_name = config_.metric_name - met = SqlMetric( - metric_name=metric_name, - verbose_name=config_.verbose_name, - expression=config_.expression, - metric_type=config_.metric_type, - description=config_.description, - ) - mets.append(met) - table.metrics = mets - db.session.commit() - except (SQLAlchemyError, DAOCreateFailedError) as ex: - logger.warning(ex, exc_info=True) - db.session.rollback() - raise DatasetDuplicateFailedError() from ex - return table - - def validate(self) -> None: - exceptions: List[ValidationError] = [] - base_model_id = self._properties["base_model_id"] - duplicate_name = self._properties["table_name"] - - base_model = DatasetDAO.find_by_id(base_model_id) - if not base_model: - exceptions.append(DatasetNotFoundError()) - else: - self._base_model = base_model - - if self._base_model and self._base_model.kind != "virtual": - exceptions.append(DatasourceTypeInvalidError()) - - if DatasetDAO.find_one_or_none(table_name=duplicate_name): - exceptions.append(DatasetExistsValidationError(table_name=duplicate_name)) - - try: - owners = self.populate_owners(self._actor) - self._properties["owners"] = owners - except ValidationError as ex: - exceptions.append(ex) - - if exceptions: - exception = DatasetInvalidError() - exception.add_list(exceptions) - raise exception diff --git a/superset/datasets/commands/exceptions.py b/superset/datasets/commands/exceptions.py index c76b7b3ad53dc..b743a4355ea06 100644 --- a/superset/datasets/commands/exceptions.py +++ b/superset/datasets/commands/exceptions.py @@ -187,7 +187,3 @@ class DatasetImportError(ImportFailedError): class DatasetAccessDeniedError(ForbiddenError): message = _("You don't have access to this dataset.") - - -class DatasetDuplicateFailedError(CreateFailedError): - message = _("Dataset could not be duplicated.") diff --git a/superset/datasets/schemas.py b/superset/datasets/schemas.py index 9d2b474894b02..38646471d03c3 100644 --- a/superset/datasets/schemas.py +++ b/superset/datasets/schemas.py @@ -107,11 +107,6 @@ class DatasetPutSchema(Schema): external_url = fields.String(allow_none=True) -class DatasetDuplicateSchema(Schema): - base_model_id = fields.Integer(required=True) - table_name = fields.String(required=True, allow_none=False, validate=Length(1, 250)) - - class DatasetRelatedChart(Schema): id = fields.Integer() slice_name = fields.String() diff --git a/tests/integration_tests/datasets/api_tests.py b/tests/integration_tests/datasets/api_tests.py index fa6ad266929f1..019f07027fc53 100644 --- a/tests/integration_tests/datasets/api_tests.py +++ b/tests/integration_tests/datasets/api_tests.py @@ -99,13 +99,6 @@ def get_fixture_datasets(self) -> List[SqlaTable]: .all() ) - def get_fixture_virtual_datasets(self) -> List[SqlaTable]: - return ( - db.session.query(SqlaTable) - .filter(SqlaTable.table_name.in_(self.fixture_virtual_table_names)) - .all() - ) - @pytest.fixture() def create_virtual_datasets(self): with self.create_app().app_context(): @@ -463,12 +456,7 @@ def test_info_security_dataset(self): rv = self.get_assert_metric(uri, "info") data = json.loads(rv.data.decode("utf-8")) assert rv.status_code == 200 - assert set(data["permissions"]) == { - "can_read", - "can_write", - "can_export", - "can_duplicate", - } + assert set(data["permissions"]) == {"can_read", "can_write", "can_export"} def test_create_dataset_item(self): """ @@ -2165,78 +2153,3 @@ def test_get_datasets_is_certified_filter(self): db.session.delete(table_w_certification) db.session.commit() - - @pytest.mark.usefixtures("create_virtual_datasets") - def test_duplicate_virtual_dataset(self): - """ - Dataset API: Test duplicate virtual dataset - """ - if backend() == "sqlite": - return - - dataset = self.get_fixture_virtual_datasets()[0] - - self.login(username="admin") - uri = f"api/v1/dataset/duplicate" - table_data = {"base_model_id": dataset.id, "table_name": "Dupe1"} - rv = self.post_assert_metric(uri, table_data, "duplicate") - assert rv.status_code == 201 - rv_data = json.loads(rv.data) - new_dataset: SqlaTable = ( - db.session.query(SqlaTable).filter_by(id=rv_data["id"]).one_or_none() - ) - assert new_dataset is not None - assert new_dataset.id != dataset.id - assert new_dataset.table_name == "Dupe1" - assert len(new_dataset.columns) == 2 - assert new_dataset.columns[0].column_name == "id" - assert new_dataset.columns[1].column_name == "name" - - @pytest.mark.usefixtures("create_datasets") - def test_duplicate_physical_dataset(self): - """ - Dataset API: Test duplicate physical dataset - """ - if backend() == "sqlite": - return - - dataset = self.get_fixture_datasets()[0] - - self.login(username="admin") - uri = f"api/v1/dataset/duplicate" - table_data = {"base_model_id": dataset.id, "table_name": "Dupe2"} - rv = self.post_assert_metric(uri, table_data, "duplicate") - assert rv.status_code == 422 - - @pytest.mark.usefixtures("create_virtual_datasets") - def test_duplicate_existing_dataset(self): - """ - Dataset API: Test duplicate dataset with existing name - """ - if backend() == "sqlite": - return - - dataset = self.get_fixture_virtual_datasets()[0] - - self.login(username="admin") - uri = f"api/v1/dataset/duplicate" - table_data = { - "base_model_id": dataset.id, - "table_name": "sql_virtual_dataset_2", - } - rv = self.post_assert_metric(uri, table_data, "duplicate") - assert rv.status_code == 422 - - def test_duplicate_invalid_dataset(self): - """ - Dataset API: Test duplicate invalid dataset - """ - - self.login(username="admin") - uri = f"api/v1/dataset/duplicate" - table_data = { - "base_model_id": -1, - "table_name": "Dupe3", - } - rv = self.post_assert_metric(uri, table_data, "duplicate") - assert rv.status_code == 422