mirror of https://github.com/apache/superset.git
[dashboards] New, export api (#8941)
* [dashboards] Multiple exports * [dashboards] Fix, mulexport permission missing * [dashboards] Test for security filtered export * [dashboards] Address PR comments
This commit is contained in:
parent
65c5922a3e
commit
123246fca6
|
@ -93,7 +93,7 @@ def data_payload_response(payload_json, has_error=False):
|
|||
|
||||
def generate_download_headers(extension, filename=None):
|
||||
filename = filename if filename else datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
content_disp = "attachment; filename={}.{}".format(filename, extension)
|
||||
content_disp = f"attachment; filename={filename}.{extension}"
|
||||
headers = {"Content-Disposition": content_disp}
|
||||
return headers
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
import json
|
||||
import re
|
||||
|
||||
from flask import current_app, g, request
|
||||
from flask_appbuilder.api import expose, protect, safe
|
||||
from flask import current_app, g, make_response, request
|
||||
from flask_appbuilder.api import expose, protect, rison, safe
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
from marshmallow import fields, post_load, pre_load, Schema, ValidationError
|
||||
from marshmallow.validate import Length
|
||||
|
@ -31,6 +31,7 @@ from superset.views.base import (
|
|||
BaseSupersetModelRestApi,
|
||||
BaseSupersetSchema,
|
||||
check_ownership_and_item_exists,
|
||||
generate_download_headers,
|
||||
)
|
||||
|
||||
from .mixin import DashboardMixin
|
||||
|
@ -159,6 +160,9 @@ class DashboardPutSchema(BaseDashboardSchema):
|
|||
return self.instance
|
||||
|
||||
|
||||
get_export_ids_schema = {"type": "array", "items": {"type": "integer"}}
|
||||
|
||||
|
||||
class DashboardRestApi(DashboardMixin, BaseSupersetModelRestApi):
|
||||
datamodel = SQLAInterface(Dashboard)
|
||||
|
||||
|
@ -169,6 +173,7 @@ class DashboardRestApi(DashboardMixin, BaseSupersetModelRestApi):
|
|||
method_permission_name = {
|
||||
"get_list": "list",
|
||||
"get": "show",
|
||||
"export": "mulexport",
|
||||
"post": "add",
|
||||
"put": "edit",
|
||||
"delete": "delete",
|
||||
|
@ -356,3 +361,52 @@ class DashboardRestApi(DashboardMixin, BaseSupersetModelRestApi):
|
|||
return self.response(200, message="OK")
|
||||
except SQLAlchemyError as e:
|
||||
return self.response_422(message=str(e))
|
||||
|
||||
@expose("/export/", methods=["GET"])
|
||||
@protect()
|
||||
@safe
|
||||
@rison(get_export_ids_schema)
|
||||
def export(self, **kwargs):
|
||||
"""Export dashboards
|
||||
---
|
||||
get:
|
||||
parameters:
|
||||
- in: query
|
||||
name: q
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
responses:
|
||||
200:
|
||||
description: Dashboard export
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
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'
|
||||
"""
|
||||
query = self.datamodel.session.query(Dashboard).filter(
|
||||
Dashboard.id.in_(kwargs["rison"])
|
||||
)
|
||||
query = self._base_filters.apply_all(query)
|
||||
ids = [item.id for item in query.all()]
|
||||
if not ids:
|
||||
return self.response_404()
|
||||
export = Dashboard.export_dashboards(ids)
|
||||
resp = make_response(export, 200)
|
||||
resp.headers["Content-Disposition"] = generate_download_headers("json")[
|
||||
"Content-Disposition"
|
||||
]
|
||||
return resp
|
||||
|
|
|
@ -24,6 +24,7 @@ from flask_appbuilder.security.sqla import models as ab_models
|
|||
from superset import db, security_manager
|
||||
from superset.models import core as models
|
||||
from superset.models.slice import Slice
|
||||
from superset.views.base import generate_download_headers
|
||||
|
||||
from .base_tests import SupersetTestCase
|
||||
|
||||
|
@ -423,3 +424,43 @@ class DashboardApiTests(SupersetTestCase):
|
|||
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
|
||||
def test_export(self):
|
||||
"""
|
||||
Dashboard API: Test dashboard export
|
||||
"""
|
||||
self.login(username="admin")
|
||||
argument = [1, 2]
|
||||
uri = f"api/v1/dashboard/export/?q={prison.dumps(argument)}"
|
||||
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
self.assertEqual(
|
||||
rv.headers["Content-Disposition"],
|
||||
generate_download_headers("json")["Content-Disposition"],
|
||||
)
|
||||
|
||||
def test_export_not_found(self):
|
||||
"""
|
||||
Dashboard API: Test dashboard export not found
|
||||
"""
|
||||
self.login(username="admin")
|
||||
argument = [1000]
|
||||
uri = f"api/v1/dashboard/export/?q={prison.dumps(argument)}"
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
|
||||
def test_export_not_allowed(self):
|
||||
"""
|
||||
Dashboard API: Test dashboard export not not allowed
|
||||
"""
|
||||
admin_id = self.get_user("admin").id
|
||||
dashboard = self.insert_dashboard("title", "slug1", [admin_id], published=False)
|
||||
|
||||
self.login(username="gamma")
|
||||
argument = [dashboard.id]
|
||||
uri = f"api/v1/dashboard/export/?q={prison.dumps(argument)}"
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
db.session.delete(dashboard)
|
||||
db.session.commit()
|
||||
|
|
Loading…
Reference in New Issue