From c2b39db03e2f2241960130ea1d05cac49741b94e Mon Sep 17 00:00:00 2001 From: Tsuyoshi Yoshizawa Date: Thu, 26 Dec 2019 23:16:48 +0900 Subject: [PATCH] Support download as TSV File (#4445) --- .../QueryControlDropdown.jsx | 12 +++++++++ .../dashboard-widget/VisualizationWidget.jsx | 9 +++++++ .../components/queries/VisualizationEmbed.jsx | 12 +++++++++ redash/handlers/query_results.py | 25 ++++++++++++------- redash/serializers/__init__.py | 2 +- redash/serializers/query_result.py | 4 +-- tests/serializers/test_query_results.py | 25 +++++++++++++------ 7 files changed, 70 insertions(+), 19 deletions(-) diff --git a/client/app/components/EditVisualizationButton/QueryControlDropdown.jsx b/client/app/components/EditVisualizationButton/QueryControlDropdown.jsx index fd36e2ead..fdbfa2afc 100644 --- a/client/app/components/EditVisualizationButton/QueryControlDropdown.jsx +++ b/client/app/components/EditVisualizationButton/QueryControlDropdown.jsx @@ -27,6 +27,7 @@ export function QueryControlDropdown(props) { )} Download as CSV File + + + Download as TSV File + + , + + {!isQueryResultEmpty ? ( + + Download as TSV File + + ) : ( + "Download as TSV File" + )} + , {!isQueryResultEmpty ? ( diff --git a/client/app/components/queries/VisualizationEmbed.jsx b/client/app/components/queries/VisualizationEmbed.jsx index 7ef854561..7a7cc1e9f 100644 --- a/client/app/components/queries/VisualizationEmbed.jsx +++ b/client/app/components/queries/VisualizationEmbed.jsx @@ -52,6 +52,7 @@ function VisualizationEmbedFooter({ query, queryResults, updatedAt, refreshStart Download as CSV File + + + Download as TSV File + + 0: self.add_cors_headers(response.headers) @@ -390,7 +391,8 @@ class QueryResultResource(BaseResource): else: abort(404, message="No cached result found for this query.") - def make_json_response(self, query_result): + @staticmethod + def make_json_response(query_result): data = json_dumps({"query_result": query_result.to_dict()}) headers = {"Content-Type": "application/json"} return make_response(data, 200, headers) @@ -398,7 +400,12 @@ class QueryResultResource(BaseResource): @staticmethod def make_csv_response(query_result): headers = {"Content-Type": "text/csv; charset=UTF-8"} - return make_response(serialize_query_result_to_csv(query_result), 200, headers) + return make_response(serialize_query_result_to_dsv(query_result, ","), 200, headers) + + @staticmethod + def make_tsv_response(query_result): + headers = {"Content-Type": "text/tab-separated-values; charset=UTF-8"} + return make_response(serialize_query_result_to_dsv(query_result, "\t"), 200, headers) @staticmethod def make_excel_response(query_result): diff --git a/redash/serializers/__init__.py b/redash/serializers/__init__.py index 755f8fa01..b0c5fc820 100644 --- a/redash/serializers/__init__.py +++ b/redash/serializers/__init__.py @@ -14,7 +14,7 @@ from redash.models.parameterized_query import ParameterizedQuery from .query_result import ( serialize_query_result, - serialize_query_result_to_csv, + serialize_query_result_to_dsv, serialize_query_result_to_xlsx, ) diff --git a/redash/serializers/query_result.py b/redash/serializers/query_result.py index 92fff2387..9470c1a48 100644 --- a/redash/serializers/query_result.py +++ b/redash/serializers/query_result.py @@ -78,14 +78,14 @@ def serialize_query_result(query_result, is_api_user): return query_result.to_dict() -def serialize_query_result_to_csv(query_result): +def serialize_query_result_to_dsv(query_result, delimiter): s = io.StringIO() query_data = query_result.data fieldnames, special_columns = _get_column_lists(query_data["columns"] or []) - writer = csv.DictWriter(s, extrasaction="ignore", fieldnames=fieldnames) + writer = csv.DictWriter(s, extrasaction="ignore", fieldnames=fieldnames, delimiter=delimiter) writer.writeheader() for row in query_data["rows"]: diff --git a/tests/serializers/test_query_results.py b/tests/serializers/test_query_results.py index 49e02ce41..68649bf7f 100644 --- a/tests/serializers/test_query_results.py +++ b/tests/serializers/test_query_results.py @@ -6,7 +6,7 @@ from tests import BaseTestCase from redash import models from redash.utils import utcnow, json_dumps -from redash.serializers import serialize_query_result, serialize_query_result_to_csv +from redash.serializers import serialize_query_result, serialize_query_result_to_dsv data = { @@ -37,14 +37,14 @@ class QueryResultSerializationTest(BaseTestCase): self.assertSetEqual(set(["data", "retrieved_at"]), set(serialized.keys())) -class CsvSerializationTest(BaseTestCase): - def get_csv_content(self): +class DsvSerializationTest(BaseTestCase): + def delimited_content(self, delimiter): query_result = self.factory.create_query_result(data=json_dumps(data)) - return serialize_query_result_to_csv(query_result) + return serialize_query_result_to_dsv(query_result, delimiter) def test_serializes_booleans_correctly(self): with self.app.test_request_context("/"): - parsed = csv.DictReader(io.StringIO(self.get_csv_content())) + parsed = csv.DictReader(io.StringIO(self.delimited_content(","))) rows = list(parsed) self.assertEqual(rows[0]["bool"], "true") @@ -53,7 +53,7 @@ class CsvSerializationTest(BaseTestCase): def test_serializes_datatime_with_correct_format(self): with self.app.test_request_context("/"): - parsed = csv.DictReader(io.StringIO(self.get_csv_content())) + parsed = csv.DictReader(io.StringIO(self.delimited_content(","))) rows = list(parsed) self.assertEqual(rows[0]["datetime"], "26/05/19 12:39") @@ -65,8 +65,19 @@ class CsvSerializationTest(BaseTestCase): def test_serializes_datatime_as_is_in_case_of_error(self): with self.app.test_request_context("/"): - parsed = csv.DictReader(io.StringIO(self.get_csv_content())) + parsed = csv.DictReader(io.StringIO(self.delimited_content(","))) rows = list(parsed) self.assertEqual(rows[3]["datetime"], "459") self.assertEqual(rows[3]["date"], "123") + + def test_serializes_tsv_format(self): + delimiter = "\t" + with self.app.test_request_context("/"): + parsed = csv.DictReader(io.StringIO(self.delimited_content(delimiter)), delimiter=delimiter) + rows = list(parsed) + + self.assertEqual(rows[0]["datetime"], "26/05/19 12:39") + self.assertEqual(rows[1]["bool"], "false") + self.assertEqual(rows[2]["date"], "") + self.assertEqual(rows[3]["datetime"], "459")