Import dashboard in Apache Superset through REST API
Asked Answered
L

3

8

I'm trying to import the Superset dashboard through API but currently not successful yet. I'm following Superset API docs to import with endpoint:

/api/v1/dashboard/import

My import payload as below:

POST /api/v1/dashboard/import/ HTTP/1.1
Host: localhost:8088
Authorization: Bearer <access token>
Content-Length: 289
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="formData"; filename="20210615_065115.json"
Content-Type: application/json

(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="overwrite"

True
----WebKitFormBoundary7MA4YWxkTrZu0gW

I got a response with status 200 but the dashboard was not imported, and in the preview response on Postman I got this output: enter image description here

Anybody can help with this issue?

Lues answered 15/6, 2021 at 9:59 Comment(0)
D
8

Superset documentation isn't very clear about this but I finally managed to solve this problem.

As you can see your response is redirecting you to a login page. What you need to do is to first make a GET request to /api/v1/security/csrf_token/

And add header in your request to /api/v1/dashboard/import

'X-CSRFToken': csrf_token

Another incorrect thing in the documentation is that Content-type is not multipart/form-data; but it is text/html; charset=utf-8

So basically in your call you don't need to pass Content-type in headers

Python example:

import requests

headers = {
    'accept': 'application/json', 
    'Authorization': f'Bearer {jwt_token}', 
    'X-CSRFToken': csrf_token
}
files = { 
    'formData': (
        dashboard_path, 
        open(dashboard_path, 'rb'), 
        'application/json'
    )
}

response = self.session.post(url, files=files, headers=headers)

EDIT 30.08.2021

I've noticed that for some reason when I was running Superset with AUTH_TYPE = AUTH_OAUTH on production the solution above stopped working.

It requires additionally header Referer to be included in headers, so more safe option would be


import requests

headers = {
    'accept': 'application/json', 
    'Authorization': f'Bearer {jwt_token}', 
    'X-CSRFToken': csrf_token,
    'Referer': url
}
files = { 
    'formData': (
        dashboard_path, 
        open(dashboard_path, 'rb'), 
        'application/json'
    )
}

response = self.session.post(url, files=files, headers=headers)
Downcast answered 1/7, 2021 at 13:53 Comment(4)
Shouldn't you use POST instead of GET ?Callup
Hi, 2023 here. This does not seem to work anymore - if i'm doing it via bash scripts.Wulfila
Hey @Wulfila what version of Superset are you using? I guess that if you're doing it through bash scripts, you're using curl. could you write the command that you're using?Downcast
curl -X POST -H "Content-Type: application/json" -d "$payload" superset-dashboard:8088/api/v1/security/login to get the Bearer token. Then to get the CRSRF token: curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $access_token" superset-dashboard:8088/api/v1/security/csrf_token/ And finally to update assests curl -s -X POST http://superset-dashboard:8088/api/v1/assets/import/ -H "Authorization: Bearer $access_token" -H "X-CSRFToken: $csrf_token" -F "formData=@$FILE_PATH" URLS are like this bc of Docker setupWulfila
G
2

In my case, I had to provide a password for the database connection for the import to work. Here's the way to do that via Python code. Source

IMPORT_API_ENDPOINT = "/api/v1/dashboard/import/"

def get_superset_access_token():
    # Authenticate and get access token
    response = requests.post(
        "%s/api/v1/security/login" % SUPERSET_HOST,
        json={"username": SUPERSET_USERNAME, "password": SUPERSET_PASSWORD, "provider": "db", "refresh": True},
    )
    access_token = response.json()["access_token"]
    print("Received access token.")
    return access_token

# Function to import dashboard from a zip file
def import_dashboard(zip_file_path):
    with open(zip_file_path, 'rb') as file:
        files = {'formData': ('dashboard.zip', file, 'application/zip')}
        payload={'passwords': '{"databases/{{DatabaseYAMLFile}}": "{{DatabasePassword}}"}','overwrite': 'true'}
        headers = {"Authorization": f"Bearer {get_superset_access_token()}", 'Accept': 'application/json'}
        response = requests.post(SUPERSET_HOST + IMPORT_API_ENDPOINT, headers=headers, files=files, data=payload)

        if response.status_code != 200:
            raise Exception(f"Failed to import dashboard: {response.text}")
        print("Dashboard imported successfully!")

overwrite is an optional param. I had 2 yaml files in my zip in the databases folder, the import failed when I added both the files and passwords in the API call. It worked only when I specified just 1 file.

Geotectonic answered 13/9, 2023 at 12:51 Comment(0)
S
0

Using Superset 4.0.2 (2024-07-18), I found importing using the API to be very challenging. CSRF tokens may now be required and there is practically no documentation on this. However, building on Harshad's code, the following obtains a JWT then a CSRF token, it then calls the import API and more thoroughly checks for success.

import requests

SUPERSET_PROTOCOL = "http"
SUPERSET_HOST = "localhost:8088"
SUPERSET_USERNAME = "admin"
SUPERSET_PASSWORD = "admin"
EXPORTED_ZIP_FILE="example.zip"
DATABASE_NAME = "MySQL.yaml"  # View the contents of EXPORTED_ZIP_FILE to get this name
DATABASE_PASSWORD = "password"

class Importer:
    def __init__(self):
        self.session = requests.Session()
        self.get_superset_access_token()
        self.get_csrf_token()

    def get_superset_access_token(self):
        # Authenticate and get access token
        endpoint = "/api/v1/security/login"
        url = f"{SUPERSET_PROTOCOL}://{SUPERSET_HOST}{endpoint}"
        response = self.session.post(
            url,
            json={
                "username": SUPERSET_USERNAME,
                "password": SUPERSET_PASSWORD,
                "provider": "db",
                "refresh": True
            },
        )
        if response.status_code != 200:
            raise Exception(f"Got HTTP code of {response.status_code} from {url}; expected 200")
        access_token = response.json()["access_token"]
        print("Received access token")
        self.session.headers.update({
            "Authorization": f"Bearer {access_token}"
        })

    def get_csrf_token(self):
        endpoint = "/api/v1/security/csrf_token/"  # Trailing slash required to avoid redirect"
        url = f"{SUPERSET_PROTOCOL}://{SUPERSET_HOST}{endpoint}"
        response = self.session.get(url)
        if response.status_code != 200:
            raise Exception(f"Got HTTP code of {response.status_code} from {url}; expected 200")
        token = response.json()["result"]
        print("Received CSRF token")
        self.session.headers.update({
            "X-CSRFToken": token
        })

    def import_dashboard(self, zip_file_path, database_name, database_password):
        endpoint = "/api/v1/dashboard/import/"
        url = f"{SUPERSET_PROTOCOL}://{SUPERSET_HOST}{endpoint}"
        with open(zip_file_path, 'rb') as infile:
            files = {'formData': ('dashboard.zip', infile.read(), 'application/zip')}
        payload={
            'passwords': '{"databases/'+database_name+'": "'+database_password+'"}',
            'overwrite': 'true'
        }
        response = self.session.post(
            url,
            files=files,
            data=payload
        )
        output_filename = "out.html"
        with open(output_filename, "wt", encoding="utf-8") as outfile:
            outfile.write(response.text)

        # Ensure we got the expected 200 status code
        if response.status_code != 200:
            raise Exception(
                f"Got HTTP code of {response.status_code} from {url}; expected 200.  See {output_filename} or server logs for possible hints"
            )

        # Ensure we can parse the response as JSON
        try:
            response_json = response.json()
        except Exception as exception:
            raise Exception(f"Could not parse response from {url} as JSON (see {output_filename} or server logs for possible hints)")

        # Ensure that the JSON has a 'message' field
        try:
            message = response_json["message"]
        except KeyError as exception:
            raise Exception(f"Could not find 'message' field in response from {url}, got {response_json}")

        # Ensure that the 'message' field contains 'OK'
        if message != "OK":
            raise Exception(f"Got message '{message}' from {url}; expected 'OK'")

        print("Dashboard imported successfully")


if __name__ == "__main__":
    importer = Importer()
    importer.import_dashboard(EXPORTED_ZIP_FILE, DATABASE_NAME, DATABASE_PASSWORD)
Simplicidentate answered 18/7 at 4:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.