You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2021/03/05 01:19:06 UTC

[superset] branch master updated: fix: API to allow importing old exports (JSON/YAML) (#13444)

This is an automated email from the ASF dual-hosted git repository.

beto pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 9fc03f0  fix: API to allow importing old exports (JSON/YAML) (#13444)
9fc03f0 is described below

commit 9fc03f042402d56f27b9e3ef26ed54139e725749
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu Mar 4 17:18:27 2021 -0800

    fix: API to allow importing old exports (JSON/YAML) (#13444)
    
    * fix: fix API to allow importing old exports (JSON/YAML)
    
    * Fix test
    
    * Fix lint
    
    * Add description to API schema
---
 superset/charts/api.py                       |  3 +++
 superset/dashboards/api.py                   | 13 +++++++++---
 superset/dashboards/commands/importers/v0.py |  5 ++++-
 superset/databases/api.py                    |  3 +++
 superset/datasets/api.py                     | 13 +++++++++---
 tests/dashboards/api_tests.py                | 31 ++++++++++++++++++++++++++++
 tests/datasets/api_tests.py                  | 26 +++++++++++++++++++++++
 7 files changed, 87 insertions(+), 7 deletions(-)

diff --git a/superset/charts/api.py b/superset/charts/api.py
index c84a843..7c756f1 100644
--- a/superset/charts/api.py
+++ b/superset/charts/api.py
@@ -993,11 +993,14 @@ class ChartRestApi(BaseSupersetModelRestApi):
                   type: object
                   properties:
                     formData:
+                      description: upload file (ZIP)
                       type: string
                       format: binary
                     passwords:
+                      description: JSON map of passwords for each file
                       type: string
                     overwrite:
+                      description: overwrite existing databases?
                       type: bool
           responses:
             200:
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index 7678711..79dbc97 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -19,7 +19,7 @@ import logging
 from datetime import datetime
 from io import BytesIO
 from typing import Any, Dict
-from zipfile import ZipFile
+from zipfile import is_zipfile, ZipFile
 
 from flask import g, make_response, redirect, request, Response, send_file, url_for
 from flask_appbuilder.api import expose, protect, rison, safe
@@ -759,11 +759,14 @@ class DashboardRestApi(BaseSupersetModelRestApi):
                   type: object
                   properties:
                     formData:
+                      description: upload file (ZIP or JSON)
                       type: string
                       format: binary
                     passwords:
+                      description: JSON map of passwords for each file
                       type: string
                     overwrite:
+                      description: overwrite existing databases?
                       type: bool
           responses:
             200:
@@ -787,8 +790,12 @@ class DashboardRestApi(BaseSupersetModelRestApi):
         upload = request.files.get("formData")
         if not upload:
             return self.response_400()
-        with ZipFile(upload) as bundle:
-            contents = get_contents_from_bundle(bundle)
+        if is_zipfile(upload):
+            with ZipFile(upload) as bundle:
+                contents = get_contents_from_bundle(bundle)
+        else:
+            upload.seek(0)
+            contents = {upload.filename: upload.read()}
 
         passwords = (
             json.loads(request.form["passwords"])
diff --git a/superset/dashboards/commands/importers/v0.py b/superset/dashboards/commands/importers/v0.py
index 851ecab..3b164fe 100644
--- a/superset/dashboards/commands/importers/v0.py
+++ b/superset/dashboards/commands/importers/v0.py
@@ -317,7 +317,10 @@ class ImportDashboardsCommand(BaseCommand):
     in Superset.
     """
 
-    def __init__(self, contents: Dict[str, str], database_id: Optional[int] = None):
+    # pylint: disable=unused-argument
+    def __init__(
+        self, contents: Dict[str, str], database_id: Optional[int] = None, **kwargs: Any
+    ):
         self.contents = contents
         self.database_id = database_id
 
diff --git a/superset/databases/api.py b/superset/databases/api.py
index ba665c1..1c92ab7 100644
--- a/superset/databases/api.py
+++ b/superset/databases/api.py
@@ -751,11 +751,14 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
                   type: object
                   properties:
                     formData:
+                      description: upload file (ZIP)
                       type: string
                       format: binary
                     passwords:
+                      description: JSON map of passwords for each file
                       type: string
                     overwrite:
+                      description: overwrite existing databases?
                       type: bool
           responses:
             200:
diff --git a/superset/datasets/api.py b/superset/datasets/api.py
index 96f3532..85adae5 100644
--- a/superset/datasets/api.py
+++ b/superset/datasets/api.py
@@ -20,7 +20,7 @@ from datetime import datetime
 from distutils.util import strtobool
 from io import BytesIO
 from typing import Any
-from zipfile import ZipFile
+from zipfile import is_zipfile, ZipFile
 
 import yaml
 from flask import g, request, Response, send_file
@@ -659,11 +659,14 @@ class DatasetRestApi(BaseSupersetModelRestApi):
                   type: object
                   properties:
                     formData:
+                      description: upload file (ZIP or YAML)
                       type: string
                       format: binary
                     passwords:
+                      description: JSON map of passwords for each file
                       type: string
                     overwrite:
+                      description: overwrite existing databases?
                       type: bool
           responses:
             200:
@@ -687,8 +690,12 @@ class DatasetRestApi(BaseSupersetModelRestApi):
         upload = request.files.get("formData")
         if not upload:
             return self.response_400()
-        with ZipFile(upload) as bundle:
-            contents = get_contents_from_bundle(bundle)
+        if is_zipfile(upload):
+            with ZipFile(upload) as bundle:
+                contents = get_contents_from_bundle(bundle)
+        else:
+            upload.seek(0)
+            contents = {upload.filename: upload.read()}
 
         passwords = (
             json.loads(request.form["passwords"])
diff --git a/tests/dashboards/api_tests.py b/tests/dashboards/api_tests.py
index a9b0ca4..7fdb7b0 100644
--- a/tests/dashboards/api_tests.py
+++ b/tests/dashboards/api_tests.py
@@ -45,6 +45,7 @@ from tests.fixtures.importexport import (
     chart_config,
     database_config,
     dashboard_config,
+    dashboard_export,
     dashboard_metadata_config,
     dataset_config,
     dataset_metadata_config,
@@ -1316,6 +1317,36 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
         db.session.delete(database)
         db.session.commit()
 
+    def test_import_dashboard_v0_export(self):
+        num_dashboards = db.session.query(Dashboard).count()
+
+        self.login(username="admin")
+        uri = "api/v1/dashboard/import/"
+
+        buf = BytesIO()
+        buf.write(json.dumps(dashboard_export).encode())
+        buf.seek(0)
+        form_data = {
+            "formData": (buf, "20201119_181105.json"),
+        }
+        rv = self.client.post(uri, data=form_data, content_type="multipart/form-data")
+        response = json.loads(rv.data.decode("utf-8"))
+
+        assert rv.status_code == 200
+        assert response == {"message": "OK"}
+        assert db.session.query(Dashboard).count() == num_dashboards + 1
+
+        dashboard = (
+            db.session.query(Dashboard).filter_by(dashboard_title="Births 2").one()
+        )
+        chart = dashboard.slices[0]
+        dataset = chart.table
+
+        db.session.delete(dashboard)
+        db.session.delete(chart)
+        db.session.delete(dataset)
+        db.session.commit()
+
     def test_import_dashboard_overwrite(self):
         """
         Dashboard API: Test import existing dashboard
diff --git a/tests/datasets/api_tests.py b/tests/datasets/api_tests.py
index 6b628d7..00be830 100644
--- a/tests/datasets/api_tests.py
+++ b/tests/datasets/api_tests.py
@@ -47,6 +47,7 @@ from tests.fixtures.importexport import (
     database_metadata_config,
     dataset_config,
     dataset_metadata_config,
+    dataset_ui_export,
 )
 
 
@@ -1275,6 +1276,31 @@ class TestDatasetApi(SupersetTestCase):
         db.session.delete(database)
         db.session.commit()
 
+    def test_import_dataset_v0_export(self):
+        num_datasets = db.session.query(SqlaTable).count()
+
+        self.login(username="admin")
+        uri = "api/v1/dataset/import/"
+
+        buf = BytesIO()
+        buf.write(json.dumps(dataset_ui_export).encode())
+        buf.seek(0)
+        form_data = {
+            "formData": (buf, "dataset_export.zip"),
+        }
+        rv = self.client.post(uri, data=form_data, content_type="multipart/form-data")
+        response = json.loads(rv.data.decode("utf-8"))
+
+        assert rv.status_code == 200
+        assert response == {"message": "OK"}
+        assert db.session.query(SqlaTable).count() == num_datasets + 1
+
+        dataset = (
+            db.session.query(SqlaTable).filter_by(table_name="birth_names_2").one()
+        )
+        db.session.delete(dataset)
+        db.session.commit()
+
     def test_import_dataset_overwrite(self):
         """
         Dataset API: Test import existing dataset