diff --git a/backend/src/core/utils/async_network.py b/backend/src/core/utils/async_network.py index aa0dc3bc..fe3c3363 100644 --- a/backend/src/core/utils/async_network.py +++ b/backend/src/core/utils/async_network.py @@ -201,15 +201,23 @@ class AsyncAPIClient: auth_headers = await self.get_headers() req_headers.update(auth_headers) - response = await self._client.request( - method=method, - url=url, - params=params, - json=data, - headers=req_headers, - timeout=httpx.Timeout(timeout or self.request_settings.get("timeout", self.DEFAULT_TIMEOUT)), - follow_redirects=allow_redirects, - ) + # Support both pre-serialized JSON strings (legacy convention) + # and Python dicts/list. Strings are sent as raw body via `content` + # to avoid double-encoding by httpx's `json` parameter. + request_kwargs: dict[str, Any] = { + "method": method, + "url": url, + "params": params, + "headers": req_headers, + "timeout": httpx.Timeout(timeout or self.request_settings.get("timeout", self.DEFAULT_TIMEOUT)), + "follow_redirects": allow_redirects, + } + if isinstance(data, str): + request_kwargs["content"] = data.encode("utf-8") + elif data is not None: + request_kwargs["json"] = data + + response = await self._client.request(**request_kwargs) if response.status_code == 401: if method.upper() in ("GET", "HEAD", "OPTIONS"): @@ -217,12 +225,8 @@ class AsyncAPIClient: self._authenticated = False auth_headers = await self.get_headers() req_headers.update(auth_headers) - response = await self._client.request( - method=method, url=url, params=params, json=data, - headers=req_headers, - timeout=httpx.Timeout(timeout or self.request_settings.get("timeout", self.DEFAULT_TIMEOUT)), - follow_redirects=allow_redirects, - ) + retry_kwargs = {**request_kwargs, "headers": req_headers} + response = await self._client.request(**retry_kwargs) else: raise AuthenticationError() diff --git a/backend/src/plugins/translate/preview_executor.py b/backend/src/plugins/translate/preview_executor.py index 358e38e7..00665418 100644 --- a/backend/src/plugins/translate/preview_executor.py +++ b/backend/src/plugins/translate/preview_executor.py @@ -37,7 +37,7 @@ class PreviewExecutor: # region fetch_sample_rows [TYPE Function] # @PURPOSE: Fetch sample rows from Superset dataset for preview. - # @SIDE_EFFECT Calls Superset chart data endpoint. + # @SIDE_EFFECT Calls Superset chart data API. async def fetch_sample_rows( self, job: TranslationJob, @@ -60,13 +60,20 @@ class PreviewExecutor: dataset_id=int(job.source_datasource_id), dataset_record=dataset_detail, template_params={}, effective_filters=[], ) + + # Build column list from dataset schema for explicit column projection + column_names = [ + col["name"] for col in dataset_detail.get("columns", []) + if col.get("name") and col.get("is_active", True) + ] + queries = query_context.get("queries", []) if queries: queries[0]["row_limit"] = sample_size - queries[0].pop("result_type", None) - queries[0].pop("columns", None) + queries[0]["columns"] = column_names or [] queries[0]["metrics"] = [] - query_context["result_type"] = "samples" + queries[0].pop("result_type", None) + query_context["result_type"] = "query" form_data = query_context.get("form_data", {}) form_data.pop("query_mode", None)