feat(dashboards): show owners and improve grid actions UI

This commit is contained in:
2026-02-28 10:04:56 +03:00
parent 442d0e0ac2
commit 066747de59
5 changed files with 912 additions and 162 deletions

View File

@@ -61,6 +61,7 @@ class LastTask(BaseModel):
None,
pattern="^PENDING|RUNNING|SUCCESS|FAILED|ERROR|AWAITING_INPUT|WAITING_INPUT|AWAITING_MAPPING$",
)
validation_status: Optional[str] = Field(None, pattern="^PASS|FAIL|WARN|UNKNOWN$")
# [/DEF:LastTask:DataClass]
# [DEF:DashboardItem:DataClass]
@@ -70,6 +71,9 @@ class DashboardItem(BaseModel):
slug: Optional[str] = None
url: Optional[str] = None
last_modified: Optional[str] = None
created_by: Optional[str] = None
modified_by: Optional[str] = None
owners: Optional[List[str]] = None
git_status: Optional[GitStatus] = None
last_task: Optional[LastTask] = None
# [/DEF:DashboardItem:DataClass]

View File

@@ -106,7 +106,17 @@ class SupersetClient:
def get_dashboards_summary(self) -> List[Dict]:
with belief_scope("SupersetClient.get_dashboards_summary"):
query = {
"columns": ["id", "dashboard_title", "changed_on_utc", "published"]
"columns": [
"id",
"dashboard_title",
"changed_on_utc",
"published",
"created_by",
"created_by_name",
"changed_by",
"changed_by_name",
"owners",
]
}
_, dashboards = self.get_dashboards(query=query)
@@ -117,11 +127,83 @@ class SupersetClient:
"id": dash.get("id"),
"title": dash.get("dashboard_title"),
"last_modified": dash.get("changed_on_utc"),
"status": "published" if dash.get("published") else "draft"
"status": "published" if dash.get("published") else "draft",
"created_by": self._extract_user_display(
dash.get("created_by_name"),
dash.get("created_by"),
),
"modified_by": self._extract_user_display(
dash.get("changed_by_name"),
dash.get("changed_by"),
),
"owners": self._extract_owner_labels(dash.get("owners")),
})
return result
# [/DEF:get_dashboards_summary:Function]
# [DEF:_extract_owner_labels:Function]
# @PURPOSE: Normalize dashboard owners payload to stable display labels.
# @PRE: owners payload can be None, list of dicts or list of strings.
# @POST: Returns deduplicated non-empty owner labels preserving order.
# @RETURN: List[str]
def _extract_owner_labels(self, owners_payload: Optional[List[Union[Dict, str]]]) -> List[str]:
if not isinstance(owners_payload, list):
return []
normalized: List[str] = []
for owner in owners_payload:
label: Optional[str] = None
if isinstance(owner, dict):
label = self._extract_user_display(None, owner)
else:
label = self._sanitize_user_text(owner)
if label and label not in normalized:
normalized.append(label)
return normalized
# [/DEF:_extract_owner_labels:Function]
# [DEF:_extract_user_display:Function]
# @PURPOSE: Normalize user payload to a stable display name.
# @PRE: user payload can be string, dict or None.
# @POST: Returns compact non-empty display value or None.
# @RETURN: Optional[str]
def _extract_user_display(self, preferred_value: Optional[str], user_payload: Optional[Dict]) -> Optional[str]:
preferred = self._sanitize_user_text(preferred_value)
if preferred:
return preferred
if isinstance(user_payload, dict):
full_name = self._sanitize_user_text(user_payload.get("full_name"))
if full_name:
return full_name
first_name = self._sanitize_user_text(user_payload.get("first_name")) or ""
last_name = self._sanitize_user_text(user_payload.get("last_name")) or ""
combined = " ".join(part for part in [first_name, last_name] if part).strip()
if combined:
return combined
username = self._sanitize_user_text(user_payload.get("username"))
if username:
return username
email = self._sanitize_user_text(user_payload.get("email"))
if email:
return email
return None
# [/DEF:_extract_user_display:Function]
# [DEF:_sanitize_user_text:Function]
# @PURPOSE: Convert scalar value to non-empty user-facing text.
# @PRE: value can be any scalar type.
# @POST: Returns trimmed string or None.
# @RETURN: Optional[str]
def _sanitize_user_text(self, value: Optional[Union[str, int]]) -> Optional[str]:
if value is None:
return None
normalized = str(value).strip()
if not normalized:
return None
return normalized
# [/DEF:_sanitize_user_text:Function]
# [DEF:get_dashboard:Function]
# @PURPOSE: Fetches a single dashboard by ID.
# @PRE: Client is authenticated and dashboard_id exists.