def export_dashboards(dashboard_file: Optional[str] = None) -> None: """Export dashboards to ZIP file""" # pylint: disable=import-outside-toplevel from superset.dashboards.commands.export import ExportDashboardsCommand from superset.models.dashboard import Dashboard g.user = security_manager.find_user(username="******") dashboard_ids = [id_ for (id_,) in db.session.query(Dashboard.id).all()] timestamp = datetime.now().strftime("%Y%m%dT%H%M%S") root = f"dashboard_export_{timestamp}" dashboard_file = dashboard_file or f"{root}.zip" try: with ZipFile(dashboard_file, "w") as bundle: for file_name, file_content in ExportDashboardsCommand( dashboard_ids ).run(): with bundle.open(f"{root}/{file_name}", "w") as fp: fp.write(file_content.encode()) except Exception: # pylint: disable=broad-except logger.exception( "There was an error when exporting the dashboards, please check " "the exception traceback in the log" ) sys.exit(1)
def test_export_dashboard_command_invalid_dataset(self, mock_g1, mock_g2): """Test that an error is raised when exporting an invalid dataset""" mock_g1.user = security_manager.find_user("admin") mock_g2.user = security_manager.find_user("admin") command = ExportDashboardsCommand([-1]) contents = command.run() with self.assertRaises(DashboardNotFoundError): next(contents)
def test_export_dashboard_command_no_access(self, mock_g1, mock_g2): """Test that users can't export datasets they don't have access to""" mock_g1.user = security_manager.find_user("gamma") mock_g2.user = security_manager.find_user("gamma") example_dashboard = db.session.query(Dashboard).filter_by(id=1).one() command = ExportDashboardsCommand([example_dashboard.id]) contents = command.run() with self.assertRaises(DashboardNotFoundError): next(contents)
def test_export_dashboard_command_no_related(self, mock_g1, mock_g2): """ Test that only the dashboard is exported when export_related=False. """ mock_g1.user = security_manager.find_user("admin") mock_g2.user = security_manager.find_user("admin") example_dashboard = (db.session.query(Dashboard).filter_by( slug="world_health").one()) command = ExportDashboardsCommand([example_dashboard.id], export_related=False) contents = dict(command.run()) expected_paths = { "metadata.yaml", "dashboards/World_Banks_Data.yaml", } assert expected_paths == set(contents.keys())
def test_export_dashboard_command_key_order(self, mock_g1, mock_g2): """Test that they keys in the YAML have the same order as export_fields""" mock_g1.user = security_manager.find_user("admin") mock_g2.user = security_manager.find_user("admin") example_dashboard = db.session.query(Dashboard).filter_by(id=1).one() command = ExportDashboardsCommand([example_dashboard.id]) contents = dict(command.run()) metadata = yaml.safe_load(contents["dashboards/World_Banks_Data.yaml"]) assert list(metadata.keys()) == [ "dashboard_title", "description", "css", "slug", "uuid", "position", "metadata", "version", ]
def export(self, **kwargs: Any) -> Response: """Export dashboards --- get: description: >- Exports multiple Dashboards and downloads them as YAML files. parameters: - in: query name: q content: application/json: schema: $ref: '#/components/schemas/get_export_ids_schema' 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' """ requested_ids = kwargs["rison"] token = request.args.get("token") if is_feature_enabled("VERSIONED_EXPORT"): timestamp = datetime.now().strftime("%Y%m%dT%H%M%S") root = f"dashboard_export_{timestamp}" filename = f"{root}.zip" buf = BytesIO() with ZipFile(buf, "w") as bundle: try: for file_name, file_content in ExportDashboardsCommand( requested_ids).run(): with bundle.open(f"{root}/{file_name}", "w") as fp: fp.write(file_content.encode()) except DashboardNotFoundError: return self.response_404() buf.seek(0) response = send_file( buf, mimetype="application/zip", as_attachment=True, attachment_filename=filename, ) if token: response.set_cookie(token, "done", max_age=600) return response query = self.datamodel.session.query(Dashboard).filter( Dashboard.id.in_(requested_ids)) 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"] if token: resp.set_cookie(token, "done", max_age=600) return resp
def test_export_dashboard_command(self, mock_g1, mock_g2): mock_g1.user = security_manager.find_user("admin") mock_g2.user = security_manager.find_user("admin") example_dashboard = db.session.query(Dashboard).filter_by(id=1).one() command = ExportDashboardsCommand([example_dashboard.id]) contents = dict(command.run()) expected_paths = { "metadata.yaml", "dashboards/World_Banks_Data.yaml", "charts/Region_Filter.yaml", "datasets/examples/wb_health_population.yaml", "databases/examples.yaml", "charts/Worlds_Population.yaml", "charts/Most_Populated_Countries.yaml", "charts/Growth_Rate.yaml", "charts/Rural.yaml", "charts/Life_Expectancy_VS_Rural.yaml", "charts/Rural_Breakdown.yaml", "charts/Worlds_Pop_Growth.yaml", "charts/Box_plot.yaml", "charts/Treemap.yaml", } assert expected_paths == set(contents.keys()) metadata = yaml.safe_load(contents["dashboards/World_Banks_Data.yaml"]) # remove chart UUIDs from metadata so we can compare for chart_info in metadata["position"].values(): if isinstance(chart_info, dict) and "uuid" in chart_info.get( "meta", {}): del chart_info["meta"]["chartId"] del chart_info["meta"]["uuid"] assert metadata == { "dashboard_title": "World Bank's Data", "description": None, "css": "", "slug": "world_health", "uuid": str(example_dashboard.uuid), "position": { "DASHBOARD_CHART_TYPE-0": { "children": [], "id": "DASHBOARD_CHART_TYPE-0", "meta": { "height": 50, "width": 4 }, "type": "CHART", }, "DASHBOARD_CHART_TYPE-1": { "children": [], "id": "DASHBOARD_CHART_TYPE-1", "meta": { "height": 50, "width": 4 }, "type": "CHART", }, "DASHBOARD_CHART_TYPE-2": { "children": [], "id": "DASHBOARD_CHART_TYPE-2", "meta": { "height": 50, "width": 4 }, "type": "CHART", }, "DASHBOARD_CHART_TYPE-3": { "children": [], "id": "DASHBOARD_CHART_TYPE-3", "meta": { "height": 50, "width": 4 }, "type": "CHART", }, "DASHBOARD_CHART_TYPE-4": { "children": [], "id": "DASHBOARD_CHART_TYPE-4", "meta": { "height": 50, "width": 4 }, "type": "CHART", }, "DASHBOARD_CHART_TYPE-5": { "children": [], "id": "DASHBOARD_CHART_TYPE-5", "meta": { "height": 50, "width": 4 }, "type": "CHART", }, "DASHBOARD_CHART_TYPE-6": { "children": [], "id": "DASHBOARD_CHART_TYPE-6", "meta": { "height": 50, "width": 4 }, "type": "CHART", }, "DASHBOARD_CHART_TYPE-7": { "children": [], "id": "DASHBOARD_CHART_TYPE-7", "meta": { "height": 50, "width": 4 }, "type": "CHART", }, "DASHBOARD_CHART_TYPE-8": { "children": [], "id": "DASHBOARD_CHART_TYPE-8", "meta": { "height": 50, "width": 4 }, "type": "CHART", }, "DASHBOARD_CHART_TYPE-9": { "children": [], "id": "DASHBOARD_CHART_TYPE-9", "meta": { "height": 50, "width": 4 }, "type": "CHART", }, "DASHBOARD_VERSION_KEY": "v2", }, "metadata": { "timed_refresh_immune_slices": [], "expanded_slices": {}, "refresh_frequency": 0, "default_filters": "{}", "color_scheme": None, }, "version": "1.0.0", }
def test_export_dashboard_command(self, mock_g1, mock_g2): mock_g1.user = security_manager.find_user("admin") mock_g2.user = security_manager.find_user("admin") example_dashboard = (db.session.query(Dashboard).filter_by( slug="world_health").one()) command = ExportDashboardsCommand([example_dashboard.id]) contents = dict(command.run()) expected_paths = { "metadata.yaml", "dashboards/World_Banks_Data.yaml", "datasets/examples/wb_health_population.yaml", "databases/examples.yaml", } for chart in example_dashboard.slices: chart_slug = secure_filename(chart.slice_name) expected_paths.add(f"charts/{chart_slug}_{chart.id}.yaml") assert expected_paths == set(contents.keys()) metadata = yaml.safe_load(contents["dashboards/World_Banks_Data.yaml"]) # remove chart UUIDs from metadata so we can compare for chart_info in metadata["position"].values(): if isinstance(chart_info, dict) and "uuid" in chart_info.get( "meta", {}): del chart_info["meta"]["chartId"] del chart_info["meta"]["uuid"] assert metadata == { "dashboard_title": "World Bank's Data", "description": None, "css": None, "slug": "world_health", "uuid": str(example_dashboard.uuid), "position": { "CHART-36bfc934": { "children": [], "id": "CHART-36bfc934", "meta": { "height": 25, "sliceName": "Region Filter", "width": 2 }, "type": "CHART", }, "CHART-37982887": { "children": [], "id": "CHART-37982887", "meta": { "height": 25, "sliceName": "World's Population", "width": 2, }, "type": "CHART", }, "CHART-17e0f8d8": { "children": [], "id": "CHART-17e0f8d8", "meta": { "height": 92, "sliceName": "Most Populated Countries", "width": 3, }, "type": "CHART", }, "CHART-2ee52f30": { "children": [], "id": "CHART-2ee52f30", "meta": { "height": 38, "sliceName": "Growth Rate", "width": 6 }, "type": "CHART", }, "CHART-2d5b6871": { "children": [], "id": "CHART-2d5b6871", "meta": { "height": 52, "sliceName": "% Rural", "width": 7 }, "type": "CHART", }, "CHART-0fd0d252": { "children": [], "id": "CHART-0fd0d252", "meta": { "height": 50, "sliceName": "Life Expectancy VS Rural %", "width": 8, }, "type": "CHART", }, "CHART-97f4cb48": { "children": [], "id": "CHART-97f4cb48", "meta": { "height": 38, "sliceName": "Rural Breakdown", "width": 3 }, "type": "CHART", }, "CHART-b5e05d6f": { "children": [], "id": "CHART-b5e05d6f", "meta": { "height": 50, "sliceName": "World's Pop Growth", "width": 4, }, "type": "CHART", }, "CHART-e76e9f5f": { "children": [], "id": "CHART-e76e9f5f", "meta": { "height": 50, "sliceName": "Box plot", "width": 4 }, "type": "CHART", }, "CHART-a4808bba": { "children": [], "id": "CHART-a4808bba", "meta": { "height": 50, "sliceName": "Treemap", "width": 8 }, "type": "CHART", }, "CHART-3nc0d8sk": { "children": [], "id": "CHART-3nc0d8sk", "meta": { "height": 50, "sliceName": "Treemap", "width": 8 }, "type": "CHART", }, "COLUMN-071bbbad": { "children": ["ROW-1e064e3c", "ROW-afdefba9"], "id": "COLUMN-071bbbad", "meta": { "background": "BACKGROUND_TRANSPARENT", "width": 9 }, "type": "COLUMN", }, "COLUMN-fe3914b8": { "children": ["CHART-36bfc934", "CHART-37982887"], "id": "COLUMN-fe3914b8", "meta": { "background": "BACKGROUND_TRANSPARENT", "width": 2 }, "type": "COLUMN", }, "GRID_ID": { "children": ["ROW-46632bc2", "ROW-3fa26c5d", "ROW-812b3f13"], "id": "GRID_ID", "type": "GRID", }, "HEADER_ID": { "id": "HEADER_ID", "meta": { "text": "World's Bank Data" }, "type": "HEADER", }, "ROOT_ID": { "children": ["GRID_ID"], "id": "ROOT_ID", "type": "ROOT" }, "ROW-1e064e3c": { "children": ["COLUMN-fe3914b8", "CHART-2d5b6871"], "id": "ROW-1e064e3c", "meta": { "background": "BACKGROUND_TRANSPARENT" }, "type": "ROW", }, "ROW-3fa26c5d": { "children": ["CHART-b5e05d6f", "CHART-0fd0d252"], "id": "ROW-3fa26c5d", "meta": { "background": "BACKGROUND_TRANSPARENT" }, "type": "ROW", }, "ROW-46632bc2": { "children": ["COLUMN-071bbbad", "CHART-17e0f8d8"], "id": "ROW-46632bc2", "meta": { "background": "BACKGROUND_TRANSPARENT" }, "type": "ROW", }, "ROW-812b3f13": { "children": ["CHART-a4808bba", "CHART-e76e9f5f"], "id": "ROW-812b3f13", "meta": { "background": "BACKGROUND_TRANSPARENT" }, "type": "ROW", }, "ROW-afdefba9": { "children": ["CHART-2ee52f30", "CHART-97f4cb48"], "id": "ROW-afdefba9", "meta": { "background": "BACKGROUND_TRANSPARENT" }, "type": "ROW", }, "DASHBOARD_VERSION_KEY": "v2", }, "metadata": { "mock_key": "mock_value" }, "version": "1.0.0", }