This is an automated email from the ASF dual-hosted git repository. msyavuz pushed a commit to branch msyavuz/fix/refresh-async in repository https://gitbox.apache.org/repos/asf/superset.git
commit 0bab66ed5e55c00fd19734763cb06e76c7ecab34 Author: Mehmet Salih Yavuz <[email protected]> AuthorDate: Tue Feb 10 18:57:42 2026 +0300 fix(charts): Force refresh skips cache and triggers async job --- superset/charts/data/api.py | 20 ++++++----- tests/integration_tests/charts/data/api_tests.py | 42 ++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/superset/charts/data/api.py b/superset/charts/data/api.py index 6166876a36b..cfa84525d73 100644 --- a/superset/charts/data/api.py +++ b/superset/charts/data/api.py @@ -351,15 +351,17 @@ class ChartDataRestApi(ChartRestApi): """ Execute command as an async query. """ - # First, look for the chart query results in the cache. - with contextlib.suppress(ChartDataCacheLoadError): - result = command.run(force_cached=True) - if result is not None: - # Log is_cached if extra payload callback is provided. - # This indicates no async job was triggered - data was already cached - # and a synchronous response is being returned immediately. - self._log_is_cached(result, add_extra_log_payload) - return self._send_chart_response(result) + # First, look for the chart query results in the cache, + # but only if we're not forcing a refresh. + if not form_data.get("force"): + with contextlib.suppress(ChartDataCacheLoadError): + result = command.run(force_cached=True) + if result is not None: + # Log is_cached if extra payload callback is provided. + # This indicates no async job was triggered - data was already + # cached and a synchronous response is being returned immediately. + self._log_is_cached(result, add_extra_log_payload) + return self._send_chart_response(result) # Otherwise, kick off a background job to run the chart query. # Clients will either poll or be notified of query completion, # at which point they will call the /data/<cache_key> endpoint diff --git a/tests/integration_tests/charts/data/api_tests.py b/tests/integration_tests/charts/data/api_tests.py index 39241cc47f8..a4c1b95c9e6 100644 --- a/tests/integration_tests/charts/data/api_tests.py +++ b/tests/integration_tests/charts/data/api_tests.py @@ -832,6 +832,48 @@ class TestPostChartDataApi(BaseTestChartDataApi): # is_cached should be [True] when retrieved from cache assert records[0]["is_cached"] == [True] + @with_feature_flags(GLOBAL_ASYNC_QUERIES=True) + @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") + @mock.patch("superset.charts.data.api.ChartDataCommand.run") + def test_chart_data_async_force_refresh(self, mock_run): + """ + Chart data API: Test that force=true skips cache and triggers async job + """ + app._got_first_request = False + async_query_manager_factory.init_app(app) + + # Mock the command.run to return cached data + class QueryContext: + result_format = ChartDataResultFormat.JSON + result_type = ChartDataResultType.FULL + + mock_run.return_value = { + "query_context": QueryContext(), + "queries": [{"query": "select * from foo", "is_cached": True}], + } + + # Test without force - should return cached data synchronously + self.query_context_payload["result_type"] = ChartDataResultType.FULL + rv = self.post_assert_metric(CHART_DATA_URI, self.query_context_payload, "data") + assert rv.status_code == 200 + mock_run.assert_called_once_with(force_cached=True) + + # Reset the mock + mock_run.reset_mock() + + # Test with force=true - should skip cache and return async response + self.query_context_payload["force"] = True + rv = self.post_assert_metric(CHART_DATA_URI, self.query_context_payload, "data") + assert rv.status_code == 202 + # When force=true, command.run should not be called at all in _run_async + # since we skip the cache check entirely + mock_run.assert_not_called() + data = json.loads(rv.data.decode("utf-8")) + keys = list(data.keys()) + self.assertCountEqual( # noqa: PT009 + keys, ["channel_id", "job_id", "user_id", "status", "errors", "result_url"] + ) + @with_feature_flags(GLOBAL_ASYNC_QUERIES=True) @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") def test_chart_data_async_results_type(self):
