Skip to content

Commit 60f29ba

Browse files
chore(logs): Add is_cached in sync AND async results (apache#36102)
1 parent 306f4c1 commit 60f29ba

File tree

2 files changed

+44
-13
lines changed

2 files changed

+44
-13
lines changed

superset/charts/data/api.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def get_data( # noqa: C901
171171
and query_context.result_format == ChartDataResultFormat.JSON
172172
and query_context.result_type == ChartDataResultType.FULL
173173
):
174-
return self._run_async(json_body, command)
174+
return self._run_async(json_body, command, add_extra_log_payload)
175175

176176
try:
177177
form_data = json.loads(chart.params)
@@ -265,7 +265,7 @@ def data( # noqa: C901
265265
and query_context.result_format == ChartDataResultFormat.JSON
266266
and query_context.result_type == ChartDataResultType.FULL
267267
):
268-
return self._run_async(json_body, command)
268+
return self._run_async(json_body, command, add_extra_log_payload)
269269

270270
form_data = json_body.get("form_data")
271271
return self._get_data_response(
@@ -334,7 +334,10 @@ def data_from_cache(self, cache_key: str) -> Response:
334334
return self._get_data_response(command, True)
335335

336336
def _run_async(
337-
self, form_data: dict[str, Any], command: ChartDataCommand
337+
self,
338+
form_data: dict[str, Any],
339+
command: ChartDataCommand,
340+
add_extra_log_payload: Callable[..., None] | None = None,
338341
) -> Response:
339342
"""
340343
Execute command as an async query.
@@ -343,6 +346,10 @@ def _run_async(
343346
with contextlib.suppress(ChartDataCacheLoadError):
344347
result = command.run(force_cached=True)
345348
if result is not None:
349+
# Log is_cached if extra payload callback is provided.
350+
# This indicates no async job was triggered - data was already cached
351+
# and a synchronous response is being returned immediately.
352+
self._log_is_cached(result, add_extra_log_payload)
346353
return self._send_chart_response(result)
347354
# Otherwise, kick off a background job to run the chart query.
348355
# Clients will either poll or be notified of query completion,
@@ -424,6 +431,25 @@ def _process_data(query_data: Any) -> Any:
424431

425432
return self.response_400(message=f"Unsupported result_format: {result_format}")
426433

434+
def _log_is_cached(
435+
self,
436+
result: dict[str, Any],
437+
add_extra_log_payload: Callable[..., None] | None,
438+
) -> None:
439+
"""
440+
Log is_cached values from query results to event logger.
441+
442+
Extracts is_cached from each query in the result and logs it.
443+
If there's a single query, logs the boolean value directly.
444+
If multiple queries, logs as a list.
445+
"""
446+
if add_extra_log_payload and result and "queries" in result:
447+
is_cached_values = [query.get("is_cached") for query in result["queries"]]
448+
if len(is_cached_values) == 1:
449+
add_extra_log_payload(is_cached=is_cached_values[0])
450+
elif is_cached_values:
451+
add_extra_log_payload(is_cached=is_cached_values)
452+
427453
@event_logger.log_this
428454
def _get_data_response(
429455
self,
@@ -442,12 +468,7 @@ def _get_data_response(
442468
return self.response_400(message=exc.message)
443469

444470
# Log is_cached if extra payload callback is provided
445-
if add_extra_log_payload and result and "queries" in result:
446-
is_cached_values = [query.get("is_cached") for query in result["queries"]]
447-
if len(is_cached_values) == 1:
448-
add_extra_log_payload(is_cached=is_cached_values[0])
449-
elif is_cached_values:
450-
add_extra_log_payload(is_cached=is_cached_values)
471+
self._log_is_cached(result, add_extra_log_payload)
451472

452473
return self._send_chart_response(result, form_data, datasource)
453474

tests/integration_tests/charts/data/api_tests.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -753,10 +753,11 @@ def test_chart_data_async(self):
753753

754754
@with_feature_flags(GLOBAL_ASYNC_QUERIES=True)
755755
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
756-
def test_chart_data_async_cached_sync_response(self):
756+
@mock.patch("superset.extensions.event_logger.log")
757+
def test_chart_data_async_cached_sync_response(self, mock_event_logger):
757758
"""
758759
Chart data API: Test chart data query returns results synchronously
759-
when results are already cached.
760+
when results are already cached, and that is_cached is logged.
760761
"""
761762
app._got_first_request = False
762763
async_query_manager_factory.init_app(app)
@@ -767,7 +768,7 @@ class QueryContext:
767768

768769
cmd_run_val = {
769770
"query_context": QueryContext(),
770-
"queries": [{"query": "select * from foo"}],
771+
"queries": [{"query": "select * from foo", "is_cached": True}],
771772
}
772773

773774
with mock.patch.object(
@@ -780,7 +781,16 @@ class QueryContext:
780781
assert rv.status_code == 200
781782
data = json.loads(rv.data.decode("utf-8"))
782783
patched_run.assert_called_once_with(force_cached=True)
783-
assert data == {"result": [{"query": "select * from foo"}]}
784+
assert data == {
785+
"result": [{"query": "select * from foo", "is_cached": True}]
786+
}
787+
788+
# Verify that is_cached was logged to event logger
789+
call_kwargs = mock_event_logger.call_args[1]
790+
records = call_kwargs.get("records", [])
791+
assert len(records) > 0
792+
# is_cached should be True when retrieved from cache in async path
793+
assert records[0]["is_cached"] is True
784794

785795
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
786796
@mock.patch("superset.extensions.event_logger.log")

0 commit comments

Comments
 (0)