616 lines
29 KiB
Python
616 lines
29 KiB
Python
# #region AsyncSupersetClientModule [C:3] [TYPE Module] [SEMANTICS superset, transform, dashboard, filter, async-superset-client]
|
|
# @BRIEF Parse a Superset dashboard URL and extract native filter state asynchronously.
|
|
# @LAYER: Core
|
|
import asyncio
|
|
import json
|
|
import re
|
|
from typing import Any, cast
|
|
|
|
from .config_models import Environment
|
|
from .logger import belief_scope, logger as app_logger
|
|
from .superset_client import SupersetClient
|
|
from .utils.async_network import AsyncAPIClient
|
|
|
|
|
|
# #region AsyncSupersetClient [C:3] [TYPE Class]
|
|
# @BRIEF Async sibling of SupersetClient for dashboard read paths.
|
|
# @RELATION INHERITS -> [SupersetClient]
|
|
# @RELATION DEPENDS_ON -> [AsyncAPIClient]
|
|
# @RELATION CALLS -> [AsyncAPIClient.request]
|
|
class AsyncSupersetClient(SupersetClient):
|
|
# #region AsyncSupersetClientInit [TYPE Function] [C:3]
|
|
# @PURPOSE: Initialize async Superset client with AsyncAPIClient transport.
|
|
# @PRE: env is valid Environment instance.
|
|
# @POST: Client uses async network transport and inherited projection helpers.
|
|
# @DATA_CONTRACT: Input[Environment] -> self.network[AsyncAPIClient]
|
|
# @RELATION: [DEPENDS_ON] ->[AsyncAPIClient]
|
|
def __init__(self, env: Environment):
|
|
self.env = env
|
|
auth_payload = {
|
|
"username": env.username,
|
|
"password": env.password,
|
|
"provider": "db",
|
|
"refresh": "true",
|
|
}
|
|
self.network = AsyncAPIClient(
|
|
config={"base_url": env.url, "auth": auth_payload},
|
|
verify_ssl=env.verify_ssl,
|
|
timeout=env.timeout,
|
|
)
|
|
self.delete_before_reimport = False
|
|
# #endregion AsyncSupersetClientInit
|
|
# #region AsyncSupersetClientClose [TYPE Function] [C:3]
|
|
# @PURPOSE: Close async transport resources.
|
|
# @POST: Underlying AsyncAPIClient is closed.
|
|
# @SIDE_EFFECT: Closes network sockets.
|
|
# @RELATION: [CALLS] ->[AsyncAPIClient.aclose]
|
|
async def aclose(self) -> None:
|
|
await self.network.aclose()
|
|
# #endregion AsyncSupersetClientClose
|
|
# #region get_dashboards_page_async [TYPE Function] [C:3]
|
|
# @PURPOSE: Fetch one dashboards page asynchronously.
|
|
# @POST: Returns total count and page result list.
|
|
# @DATA_CONTRACT: Input[query: Optional[Dict]] -> Output[Tuple[int, List[Dict]]]
|
|
# @RELATION: [CALLS] -> [AsyncAPIClient.request]
|
|
async def get_dashboards_page_async(
|
|
self, query: dict | None = None
|
|
) -> tuple[int, list[dict]]:
|
|
with belief_scope("AsyncSupersetClient.get_dashboards_page_async"):
|
|
validated_query = self._validate_query_params(query or {})
|
|
if "columns" not in validated_query:
|
|
validated_query["columns"] = [
|
|
"slug",
|
|
"id",
|
|
"url",
|
|
"changed_on_utc",
|
|
"dashboard_title",
|
|
"published",
|
|
"created_by",
|
|
"changed_by",
|
|
"changed_by_name",
|
|
"owners",
|
|
]
|
|
response_json = cast(
|
|
dict[str, Any],
|
|
await self.network.request(
|
|
method="GET",
|
|
endpoint="/dashboard/",
|
|
params={"q": json.dumps(validated_query)},
|
|
),
|
|
)
|
|
result = response_json.get("result", [])
|
|
total_count = response_json.get("count", len(result))
|
|
return total_count, result
|
|
# #endregion get_dashboards_page_async
|
|
# #region get_dashboard_async [TYPE Function] [C:3]
|
|
# @PURPOSE: Fetch one dashboard payload asynchronously.
|
|
# @POST: Returns raw dashboard payload from Superset API.
|
|
# @DATA_CONTRACT: Input[dashboard_id: int] -> Output[Dict]
|
|
# @RELATION: [CALLS] ->[AsyncAPIClient.request]
|
|
async def get_dashboard_async(self, dashboard_id: int) -> dict:
|
|
with belief_scope(
|
|
"AsyncSupersetClient.get_dashboard_async", f"id={dashboard_id}"
|
|
):
|
|
response = await self.network.request(
|
|
method="GET", endpoint=f"/dashboard/{dashboard_id}"
|
|
)
|
|
return cast(dict, response)
|
|
# #endregion get_dashboard_async
|
|
# #region get_chart_async [TYPE Function] [C:3]
|
|
# @PURPOSE: Fetch one chart payload asynchronously.
|
|
# @POST: Returns raw chart payload from Superset API.
|
|
# @DATA_CONTRACT: Input[chart_id: int] -> Output[Dict]
|
|
# @RELATION: [CALLS] ->[AsyncAPIClient.request]
|
|
async def get_chart_async(self, chart_id: int) -> dict:
|
|
with belief_scope("AsyncSupersetClient.get_chart_async", f"id={chart_id}"):
|
|
response = await self.network.request(
|
|
method="GET", endpoint=f"/chart/{chart_id}"
|
|
)
|
|
return cast(dict, response)
|
|
# #endregion get_chart_async
|
|
# #region get_dashboard_detail_async [TYPE Function] [C:3]
|
|
# @PURPOSE: Fetch dashboard detail asynchronously with concurrent charts/datasets requests.
|
|
# @POST: Returns dashboard detail payload for overview page.
|
|
# @DATA_CONTRACT: Input[dashboard_id: int] -> Output[Dict]
|
|
# @RELATION: [CALLS] ->[get_dashboard_async]
|
|
# @RELATION: [CALLS] ->[get_chart_async]
|
|
async def get_dashboard_detail_async(self, dashboard_id: int) -> dict:
|
|
with belief_scope(
|
|
"AsyncSupersetClient.get_dashboard_detail_async", f"id={dashboard_id}"
|
|
):
|
|
dashboard_response = await self.get_dashboard_async(dashboard_id)
|
|
dashboard_data = dashboard_response.get("result", dashboard_response)
|
|
charts: list[dict] = []
|
|
datasets: list[dict] = []
|
|
def extract_dataset_id_from_form_data(
|
|
form_data: dict | None,
|
|
) -> int | None:
|
|
if not isinstance(form_data, dict):
|
|
return None
|
|
datasource = form_data.get("datasource")
|
|
if isinstance(datasource, str):
|
|
matched = re.match(r"^(\d+)__", datasource)
|
|
if matched:
|
|
try:
|
|
return int(matched.group(1))
|
|
except ValueError:
|
|
return None
|
|
if isinstance(datasource, dict):
|
|
ds_id = datasource.get("id")
|
|
try:
|
|
return int(ds_id) if ds_id is not None else None
|
|
except (TypeError, ValueError):
|
|
return None
|
|
ds_id = form_data.get("datasource_id")
|
|
try:
|
|
return int(ds_id) if ds_id is not None else None
|
|
except (TypeError, ValueError):
|
|
return None
|
|
chart_task = self.network.request(
|
|
method="GET",
|
|
endpoint=f"/dashboard/{dashboard_id}/charts",
|
|
)
|
|
dataset_task = self.network.request(
|
|
method="GET",
|
|
endpoint=f"/dashboard/{dashboard_id}/datasets",
|
|
)
|
|
charts_response, datasets_response = await asyncio.gather(
|
|
chart_task,
|
|
dataset_task,
|
|
return_exceptions=True,
|
|
)
|
|
if not isinstance(charts_response, Exception):
|
|
charts_payload = (
|
|
charts_response.get("result", [])
|
|
if isinstance(charts_response, dict)
|
|
else []
|
|
)
|
|
for chart_obj in charts_payload:
|
|
if not isinstance(chart_obj, dict):
|
|
continue
|
|
chart_id = chart_obj.get("id")
|
|
if chart_id is None:
|
|
continue
|
|
form_data = chart_obj.get("form_data")
|
|
if isinstance(form_data, str):
|
|
try:
|
|
form_data = json.loads(form_data)
|
|
except Exception:
|
|
form_data = {}
|
|
dataset_id = extract_dataset_id_from_form_data(
|
|
form_data
|
|
) or chart_obj.get("datasource_id")
|
|
charts.append(
|
|
{
|
|
"id": int(chart_id),
|
|
"title": chart_obj.get("slice_name")
|
|
or chart_obj.get("name")
|
|
or f"Chart {chart_id}",
|
|
"viz_type": (
|
|
form_data.get("viz_type")
|
|
if isinstance(form_data, dict)
|
|
else None
|
|
),
|
|
"dataset_id": int(dataset_id)
|
|
if dataset_id is not None
|
|
else None,
|
|
"last_modified": chart_obj.get("changed_on"),
|
|
"overview": chart_obj.get("description")
|
|
or (
|
|
form_data.get("viz_type")
|
|
if isinstance(form_data, dict)
|
|
else None
|
|
)
|
|
or "Chart",
|
|
}
|
|
)
|
|
else:
|
|
app_logger.warning(
|
|
"[get_dashboard_detail_async][Warning] Failed to fetch dashboard charts: %s",
|
|
charts_response,
|
|
)
|
|
if not isinstance(datasets_response, Exception):
|
|
datasets_payload = (
|
|
datasets_response.get("result", [])
|
|
if isinstance(datasets_response, dict)
|
|
else []
|
|
)
|
|
for dataset_obj in datasets_payload:
|
|
if not isinstance(dataset_obj, dict):
|
|
continue
|
|
dataset_id = dataset_obj.get("id")
|
|
if dataset_id is None:
|
|
continue
|
|
db_payload = dataset_obj.get("database")
|
|
db_name = (
|
|
db_payload.get("database_name")
|
|
if isinstance(db_payload, dict)
|
|
else None
|
|
)
|
|
table_name = (
|
|
dataset_obj.get("table_name")
|
|
or dataset_obj.get("datasource_name")
|
|
or dataset_obj.get("name")
|
|
or f"Dataset {dataset_id}"
|
|
)
|
|
schema = dataset_obj.get("schema")
|
|
fq_name = f"{schema}.{table_name}" if schema else table_name
|
|
datasets.append(
|
|
{
|
|
"id": int(dataset_id),
|
|
"table_name": table_name,
|
|
"schema": schema,
|
|
"database": db_name
|
|
or dataset_obj.get("database_name")
|
|
or "Unknown",
|
|
"last_modified": dataset_obj.get("changed_on"),
|
|
"overview": fq_name,
|
|
}
|
|
)
|
|
else:
|
|
app_logger.warning(
|
|
"[get_dashboard_detail_async][Warning] Failed to fetch dashboard datasets: %s",
|
|
datasets_response,
|
|
)
|
|
if not charts:
|
|
raw_position_json = dashboard_data.get("position_json")
|
|
chart_ids_from_position = set()
|
|
if isinstance(raw_position_json, str) and raw_position_json:
|
|
try:
|
|
parsed_position = json.loads(raw_position_json)
|
|
chart_ids_from_position.update(
|
|
self._extract_chart_ids_from_layout(parsed_position)
|
|
)
|
|
except Exception:
|
|
app_logger.debug("Could not parse position JSON for dashboard %s", dashboard_ref)
|
|
elif isinstance(raw_position_json, dict):
|
|
chart_ids_from_position.update(
|
|
self._extract_chart_ids_from_layout(raw_position_json)
|
|
)
|
|
raw_json_metadata = dashboard_data.get("json_metadata")
|
|
if isinstance(raw_json_metadata, str) and raw_json_metadata:
|
|
try:
|
|
parsed_metadata = json.loads(raw_json_metadata)
|
|
chart_ids_from_position.update(
|
|
self._extract_chart_ids_from_layout(parsed_metadata)
|
|
)
|
|
except Exception:
|
|
app_logger.debug("Could not parse json_metadata for dashboard %s", dashboard_ref)
|
|
elif isinstance(raw_json_metadata, dict):
|
|
chart_ids_from_position.update(
|
|
self._extract_chart_ids_from_layout(raw_json_metadata)
|
|
)
|
|
fallback_chart_tasks = [
|
|
self.get_chart_async(int(chart_id))
|
|
for chart_id in sorted(chart_ids_from_position)
|
|
]
|
|
fallback_chart_responses = await asyncio.gather(
|
|
*fallback_chart_tasks,
|
|
return_exceptions=True,
|
|
)
|
|
for chart_id, chart_response in zip(
|
|
sorted(chart_ids_from_position), fallback_chart_responses
|
|
):
|
|
if isinstance(chart_response, Exception):
|
|
app_logger.warning(
|
|
"[get_dashboard_detail_async][Warning] Failed to resolve fallback chart %s: %s",
|
|
chart_id,
|
|
chart_response,
|
|
)
|
|
continue
|
|
chart_data = chart_response.get("result", chart_response)
|
|
charts.append(
|
|
{
|
|
"id": int(chart_id),
|
|
"title": chart_data.get("slice_name")
|
|
or chart_data.get("name")
|
|
or f"Chart {chart_id}",
|
|
"viz_type": chart_data.get("viz_type"),
|
|
"dataset_id": chart_data.get("datasource_id"),
|
|
"last_modified": chart_data.get("changed_on"),
|
|
"overview": chart_data.get("description")
|
|
or chart_data.get("viz_type")
|
|
or "Chart",
|
|
}
|
|
)
|
|
dataset_ids_from_charts = {
|
|
c.get("dataset_id") for c in charts if c.get("dataset_id") is not None
|
|
}
|
|
known_dataset_ids = {
|
|
d.get("id") for d in datasets if d.get("id") is not None
|
|
}
|
|
missing_dataset_ids = sorted(
|
|
int(item)
|
|
for item in dataset_ids_from_charts
|
|
if item not in known_dataset_ids
|
|
)
|
|
if missing_dataset_ids:
|
|
dataset_fetch_tasks = [
|
|
self.network.request(
|
|
method="GET", endpoint=f"/dataset/{dataset_id}"
|
|
)
|
|
for dataset_id in missing_dataset_ids
|
|
]
|
|
dataset_fetch_responses = await asyncio.gather(
|
|
*dataset_fetch_tasks,
|
|
return_exceptions=True,
|
|
)
|
|
for dataset_id, dataset_response in zip(
|
|
missing_dataset_ids, dataset_fetch_responses
|
|
):
|
|
if isinstance(dataset_response, Exception):
|
|
app_logger.warning(
|
|
"[get_dashboard_detail_async][Warning] Failed to backfill dataset %s: %s",
|
|
dataset_id,
|
|
dataset_response,
|
|
)
|
|
continue
|
|
dataset_data = (
|
|
dataset_response.get("result", dataset_response)
|
|
if isinstance(dataset_response, dict)
|
|
else {}
|
|
)
|
|
db_payload = dataset_data.get("database")
|
|
db_name = (
|
|
db_payload.get("database_name")
|
|
if isinstance(db_payload, dict)
|
|
else None
|
|
)
|
|
table_name = (
|
|
dataset_data.get("table_name")
|
|
or dataset_data.get("datasource_name")
|
|
or dataset_data.get("name")
|
|
or f"Dataset {dataset_id}"
|
|
)
|
|
schema = dataset_data.get("schema")
|
|
fq_name = f" {schema}.{table_name}" if schema else table_name
|
|
datasets.append(
|
|
{
|
|
"id": int(dataset_id),
|
|
"table_name": table_name,
|
|
"schema": schema,
|
|
"database": db_name
|
|
or dataset_data.get("database_name")
|
|
or "Unknown",
|
|
"last_modified": dataset_data.get("changed_on"),
|
|
"overview": fq_name,
|
|
}
|
|
)
|
|
return {
|
|
"id": int(dashboard_data.get("id") or dashboard_id),
|
|
"title": dashboard_data.get("dashboard_title")
|
|
or dashboard_data.get("title")
|
|
or f"Dashboard {dashboard_id}",
|
|
"slug": dashboard_data.get("slug"),
|
|
"url": dashboard_data.get("url"),
|
|
"description": dashboard_data.get("description"),
|
|
"last_modified": dashboard_data.get("changed_on_utc")
|
|
or dashboard_data.get("changed_on"),
|
|
"published": dashboard_data.get("published"),
|
|
"charts": charts,
|
|
"datasets": datasets,
|
|
"chart_count": len(charts),
|
|
"dataset_count": len(datasets),
|
|
}
|
|
# #endregion get_dashboard_detail_async
|
|
# #region get_dashboard_permalink_state_async [TYPE Function] [C:2]
|
|
# @PURPOSE: Fetch stored dashboard permalink state asynchronously.
|
|
# @POST: Returns dashboard permalink state payload from Superset API.
|
|
# @DATA_CONTRACT: Input[permalink_key: str] -> Output[Dict]
|
|
async def get_dashboard_permalink_state_async(self, permalink_key: str) -> dict:
|
|
with belief_scope(
|
|
"AsyncSupersetClient.get_dashboard_permalink_state_async",
|
|
f"key={permalink_key}",
|
|
):
|
|
response = await self.network.request(
|
|
method="GET", endpoint=f"/dashboard/permalink/{permalink_key}"
|
|
)
|
|
return cast(dict, response)
|
|
# #endregion get_dashboard_permalink_state_async
|
|
# #region get_native_filter_state_async [TYPE Function] [C:2]
|
|
# @PURPOSE: Fetch stored native filter state asynchronously.
|
|
# @POST: Returns native filter state payload from Superset API.
|
|
# @DATA_CONTRACT: Input[dashboard_id: Union[int, str], filter_state_key: str] -> Output[Dict]
|
|
async def get_native_filter_state_async(
|
|
self, dashboard_id: int, filter_state_key: str
|
|
) -> dict:
|
|
with belief_scope(
|
|
"AsyncSupersetClient.get_native_filter_state_async",
|
|
f"dashboard={dashboard_id}, key={filter_state_key}",
|
|
):
|
|
response = await self.network.request(
|
|
method="GET",
|
|
endpoint=f"/dashboard/{dashboard_id}/filter_state/{filter_state_key}",
|
|
)
|
|
return cast(dict, response)
|
|
# #endregion get_native_filter_state_async
|
|
# #region extract_native_filters_from_permalink_async [TYPE Function] [C:3]
|
|
# @PURPOSE: Extract native filters dataMask from a permalink key asynchronously.
|
|
# @POST: Returns extracted dataMask with filter states.
|
|
# @DATA_CONTRACT: Input[permalink_key: str] -> Output[Dict]
|
|
# @RELATION: [CALLS] ->[get_dashboard_permalink_state_async]
|
|
async def extract_native_filters_from_permalink_async(
|
|
self, permalink_key: str
|
|
) -> dict:
|
|
with belief_scope(
|
|
"AsyncSupersetClient.extract_native_filters_from_permalink_async",
|
|
f"key={permalink_key}",
|
|
):
|
|
permalink_response = await self.get_dashboard_permalink_state_async(
|
|
permalink_key
|
|
)
|
|
result = permalink_response.get("result", permalink_response)
|
|
state = result.get("state", result)
|
|
data_mask = state.get("dataMask", {})
|
|
extracted_filters = {}
|
|
for filter_id, filter_data in data_mask.items():
|
|
if not isinstance(filter_data, dict):
|
|
continue
|
|
extracted_filters[filter_id] = {
|
|
"extraFormData": filter_data.get("extraFormData", {}),
|
|
"filterState": filter_data.get("filterState", {}),
|
|
"ownState": filter_data.get("ownState", {}),
|
|
}
|
|
return {
|
|
"dataMask": extracted_filters,
|
|
"activeTabs": state.get("activeTabs", []),
|
|
"anchor": state.get("anchor"),
|
|
"chartStates": state.get("chartStates", {}),
|
|
"permalink_key": permalink_key,
|
|
}
|
|
# #endregion extract_native_filters_from_permalink_async
|
|
# #region extract_native_filters_from_key_async [TYPE Function] [C:3]
|
|
# @PURPOSE: Extract native filters from a native_filters_key URL parameter asynchronously.
|
|
# @POST: Returns extracted filter state with extraFormData.
|
|
# @DATA_CONTRACT: Input[dashboard_id: Union[int, str], filter_state_key: str] -> Output[Dict]
|
|
# @RELATION: [CALLS] ->[get_native_filter_state_async]
|
|
async def extract_native_filters_from_key_async(
|
|
self, dashboard_id: int, filter_state_key: str
|
|
) -> dict:
|
|
with belief_scope(
|
|
"AsyncSupersetClient.extract_native_filters_from_key_async",
|
|
f"dashboard={dashboard_id}, key={filter_state_key}",
|
|
):
|
|
filter_response = await self.get_native_filter_state_async(
|
|
dashboard_id, filter_state_key
|
|
)
|
|
result = filter_response.get("result", filter_response)
|
|
value = result.get("value")
|
|
if isinstance(value, str):
|
|
try:
|
|
parsed_value = json.loads(value)
|
|
except json.JSONDecodeError as e:
|
|
app_logger.warning(
|
|
"[extract_native_filters_from_key_async][Warning] Failed to parse filter state JSON: %s",
|
|
e,
|
|
)
|
|
parsed_value = {}
|
|
elif isinstance(value, dict):
|
|
parsed_value = value
|
|
else:
|
|
parsed_value = {}
|
|
extracted_filters = {}
|
|
if "id" in parsed_value and "extraFormData" in parsed_value:
|
|
filter_id = parsed_value.get("id", filter_state_key)
|
|
extracted_filters[filter_id] = {
|
|
"extraFormData": parsed_value.get("extraFormData", {}),
|
|
"filterState": parsed_value.get("filterState", {}),
|
|
"ownState": parsed_value.get("ownState", {}),
|
|
}
|
|
else:
|
|
for filter_id, filter_data in parsed_value.items():
|
|
if not isinstance(filter_data, dict):
|
|
continue
|
|
extracted_filters[filter_id] = {
|
|
"extraFormData": filter_data.get("extraFormData", {}),
|
|
"filterState": filter_data.get("filterState", {}),
|
|
"ownState": filter_data.get("ownState", {}),
|
|
}
|
|
return {
|
|
"dataMask": extracted_filters,
|
|
"dashboard_id": dashboard_id,
|
|
"filter_state_key": filter_state_key,
|
|
}
|
|
# #endregion extract_native_filters_from_key_async
|
|
# #region parse_dashboard_url_for_filters_async [TYPE Function] [C:3]
|
|
# @PURPOSE: Parse a Superset dashboard URL and extract native filter state asynchronously.
|
|
# @POST: Returns extracted filter state or empty dict if no filters found.
|
|
# @DATA_CONTRACT: Input[url: str] -> Output[Dict]
|
|
# @RELATION: [CALLS] ->[extract_native_filters_from_permalink_async]
|
|
# @RELATION: [CALLS] ->[extract_native_filters_from_key_async]
|
|
async def parse_dashboard_url_for_filters_async(self, url: str) -> dict:
|
|
with belief_scope(
|
|
"AsyncSupersetClient.parse_dashboard_url_for_filters_async", f"url={url}"
|
|
):
|
|
import urllib.parse
|
|
parsed_url = urllib.parse.urlparse(url)
|
|
query_params = urllib.parse.parse_qs(parsed_url.query)
|
|
path_parts = parsed_url.path.rstrip("/").split("/")
|
|
result = {
|
|
"url": url,
|
|
"dashboard_id": None,
|
|
"filter_type": None,
|
|
"filters": {},
|
|
}
|
|
# Check for permalink URL: /dashboard/p/{key}/
|
|
if "p" in path_parts:
|
|
try:
|
|
p_index = path_parts.index("p")
|
|
if p_index + 1 < len(path_parts):
|
|
permalink_key = path_parts[p_index + 1]
|
|
filter_data = (
|
|
await self.extract_native_filters_from_permalink_async(
|
|
permalink_key
|
|
)
|
|
)
|
|
result["filter_type"] = "permalink"
|
|
result["filters"] = filter_data
|
|
return result
|
|
except ValueError:
|
|
app_logger.debug("Permalink key not found in URL for dashboard %s", dashboard_ref)
|
|
# Check for native_filters_key in query params
|
|
native_filters_key = query_params.get("native_filters_key", [None])[0]
|
|
if native_filters_key:
|
|
dashboard_ref = None
|
|
if "dashboard" in path_parts:
|
|
try:
|
|
dash_index = path_parts.index("dashboard")
|
|
if dash_index + 1 < len(path_parts):
|
|
potential_id = path_parts[dash_index + 1]
|
|
if potential_id not in ("p", "list", "new"):
|
|
dashboard_ref = potential_id
|
|
except ValueError:
|
|
app_logger.debug("Dashboard not found in path_parts for native_filters_key")
|
|
if dashboard_ref:
|
|
# Resolve slug to numeric ID — the filter_state API requires a numeric ID
|
|
resolved_id = None
|
|
try:
|
|
resolved_id = int(dashboard_ref)
|
|
except (ValueError, TypeError):
|
|
try:
|
|
dash_resp = await self.get_dashboard_async(dashboard_ref)
|
|
dash_data = (
|
|
dash_resp.get("result", dash_resp)
|
|
if isinstance(dash_resp, dict)
|
|
else {}
|
|
)
|
|
raw_id = dash_data.get("id")
|
|
if raw_id is not None:
|
|
resolved_id = int(raw_id)
|
|
except Exception as e:
|
|
app_logger.warning(
|
|
"[parse_dashboard_url_for_filters_async][Warning] Failed to resolve dashboard slug '%s' to ID: %s",
|
|
dashboard_ref,
|
|
e,
|
|
)
|
|
if resolved_id is not None:
|
|
filter_data = await self.extract_native_filters_from_key_async(
|
|
resolved_id, native_filters_key
|
|
)
|
|
result["filter_type"] = "native_filters_key"
|
|
result["dashboard_id"] = resolved_id
|
|
result["filters"] = filter_data
|
|
return result
|
|
else:
|
|
app_logger.warning(
|
|
"[parse_dashboard_url_for_filters_async][Warning] Could not resolve dashboard_id from URL for native_filters_key"
|
|
)
|
|
return result
|
|
# Check for native_filters in query params (direct filter values)
|
|
native_filters = query_params.get("native_filters", [None])[0]
|
|
if native_filters:
|
|
try:
|
|
parsed_filters = json.loads(native_filters)
|
|
result["filter_type"] = "native_filters"
|
|
result["filters"] = {"dataMask": parsed_filters}
|
|
return result
|
|
except json.JSONDecodeError as e:
|
|
app_logger.warning(
|
|
"[parse_dashboard_url_for_filters_async][Warning] Failed to parse native_filters JSON: %s",
|
|
e,
|
|
)
|
|
return result
|
|
# #endregion parse_dashboard_url_for_filters_async
|
|
# #endregion AsyncSupersetClient
|
|
# #endregion AsyncSupersetClientModule
|