mirror of
https://github.com/BillyOutlast/posthog.git
synced 2026-02-04 03:01:23 +01:00
fix(exports): Fix exports for HogQL results without columns (#21631)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user