[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:
Daniel Vaz Gaspar 2020-01-15 18:09:55 +00:00 committed by GitHub
parent 65c5922a3e
commit 123246fca6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 3 deletions

View File

@ -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

View File

@ -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

View File

@ -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()