fix(exports): Fix exports for HogQL results without columns (#21631)

This commit is contained in:
Julian Bez
2024-04-18 20:52:45 +01:00
committed by GitHub
parent c147f9114a
commit 3da867d00f
3 changed files with 100 additions and 3 deletions

View File

@@ -364,7 +364,7 @@
OR "posthog_annotation"."team_id" = 2)
AND NOT "posthog_annotation"."deleted")
ORDER BY "posthog_annotation"."date_marker" DESC
LIMIT 1000 /*controller='project_annotations-list',route='api/projects/%28%3FP%3Cparent_lookup_team_id%3E%5B%5E/.%5D%2B%29/annotations/%3F%24'*/
LIMIT 1000
'''
# ---
# name: TestAnnotation.test_retrieving_annotation_is_not_n_plus_1.2

View File

@@ -86,7 +86,10 @@ def _convert_response_to_csv_data(data: Any) -> Generator[Any, None, None]:
for row in results:
row_dict = {}
for idx, x in enumerate(row):
row_dict[data["columns"][idx]] = x
if not data.get("columns"):
row_dict[f"column_{idx}"] = x
else:
row_dict[data["columns"][idx]] = x
yield row_dict
return
@@ -285,6 +288,9 @@ def _export_to_dict(exported_asset: ExportedAsset, limit: int) -> Any:
if not is_any_col_list_or_dict:
# If values are serialised then keep the order of the keys, else allow it to be unordered
renderer.header = all_csv_rows[0].keys()
else:
# If we have no rows, that means we couldn't convert anything, so put something to avoid confusion
all_csv_rows = [{"error": "No data available or unable to format for export."}]
return renderer, all_csv_rows, render_context

View File

@@ -1,3 +1,4 @@
from datetime import datetime
from typing import Any, Dict, Optional
from unittest import mock
from unittest.mock import MagicMock, Mock, patch, ANY
@@ -28,7 +29,8 @@ from posthog.tasks.exports.csv_exporter import (
add_query_params,
)
from posthog.hogql.constants import CSV_EXPORT_BREAKDOWN_LIMIT_INITIAL
from posthog.test.base import APIBaseTest, _create_event, flush_persons_and_events
from posthog.test.base import APIBaseTest, _create_event, flush_persons_and_events, _create_person
from posthog.test.test_journeys import journeys_for
from posthog.utils import absolute_uri
TEST_PREFIX = "Test-Exports"
@@ -497,6 +499,95 @@ class TestCSVExporter(APIBaseTest):
self.assertEqual(first_row[1], "$pageview")
self.assertEqual(first_row[4], str(self.team.pk))
@patch("posthog.hogql.constants.MAX_SELECT_RETURNED_ROWS", 10)
@patch("posthog.models.exported_asset.UUIDT")
def test_csv_exporter_funnels_query(self, mocked_uuidt: Any, MAX_SELECT_RETURNED_ROWS: int = 10) -> None:
_create_person(
distinct_ids=[f"user_1"],
team=self.team,
)
events_by_person = {
"user_1": [
{
"event": "$pageview",
"timestamp": datetime(2024, 3, 22, 13, 46),
"properties": {"utm_medium": "test''123"},
},
{
"event": "$pageview",
"timestamp": datetime(2024, 3, 22, 13, 47),
"properties": {"utm_medium": "test''123"},
},
],
}
journeys_for(events_by_person, self.team)
flush_persons_and_events()
exported_asset = ExportedAsset(
team=self.team,
export_format=ExportedAsset.ExportFormat.CSV,
export_context={
"source": {
"kind": "FunnelsQuery",
"series": [
{"kind": "EventsNode", "name": "$pageview", "event": "$pageview"},
{"kind": "EventsNode", "name": "$pageview", "event": "$pageview"},
],
"interval": "day",
"dateRange": {"date_to": "2024-03-22", "date_from": "2024-03-22"},
"funnelsFilter": {"funnelVizType": "steps"},
"breakdownFilter": {"breakdown": "utm_medium", "breakdown_type": "event"},
}
},
)
exported_asset.save()
mocked_uuidt.return_value = "a-guid"
with self.settings(OBJECT_STORAGE_ENABLED=True, OBJECT_STORAGE_EXPORTS_FOLDER="Test-Exports"):
csv_exporter.export_tabular(exported_asset)
content = object_storage.read(exported_asset.content_location)
lines = (content or "").split("\r\n")
self.assertEqual(len(lines), 3)
self.assertEqual(
lines[0],
"column_0.action_id,column_0.name,column_0.custom_name,column_0.order,column_0.count,column_0.type,column_0.average_conversion_time,column_0.median_conversion_time,column_0.breakdown.0,column_0.breakdown_value.0,column_1.action_id,column_1.name,column_1.custom_name,column_1.order,column_1.count,column_1.type,column_1.average_conversion_time,column_1.median_conversion_time,column_1.breakdown.0,column_1.breakdown_value.0",
)
first_row = lines[1].split(",")
self.assertEqual(first_row[0], "$pageview")
@patch("posthog.models.exported_asset.UUIDT")
def test_csv_exporter_empty_result(self, mocked_uuidt: Any) -> None:
exported_asset = ExportedAsset(
team=self.team,
export_format=ExportedAsset.ExportFormat.CSV,
export_context={
"source": {
"kind": "FunnelsQuery",
"series": [
{"kind": "EventsNode", "name": "$pageview", "event": "$pageview"},
{"kind": "EventsNode", "name": "$pageview", "event": "$pageview"},
],
"interval": "day",
"dateRange": {"date_to": "2024-03-22", "date_from": "2024-03-22"},
"funnelsFilter": {"funnelVizType": "steps"},
"breakdownFilter": {"breakdown": "utm_medium", "breakdown_type": "event"},
}
},
)
exported_asset.save()
mocked_uuidt.return_value = "a-guid"
with patch("posthog.tasks.exports.csv_exporter.get_from_hogql_query") as mocked_get_from_hogql_query:
mocked_get_from_hogql_query.return_value = iter([])
with self.settings(OBJECT_STORAGE_ENABLED=True, OBJECT_STORAGE_EXPORTS_FOLDER="Test-Exports"):
csv_exporter.export_tabular(exported_asset)
content = object_storage.read(exported_asset.content_location)
lines = (content or "").split("\r\n")
self.assertEqual(lines[0], "error")
self.assertEqual(lines[1], "No data available or unable to format for export.")
def _split_to_dict(self, url: str) -> Dict[str, Any]:
first_split_parts = url.split("?")
assert len(first_split_parts) == 2