Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ Exports a full dashboard definition as JSON using the admin export API (`GET /ap

### `get_dashboard_widgets(dashboard_ref)`

Fetches the widget list for a dashboard via `GET /api/v1/dashboards/{dashboardId}/widgets`. The `dashboard_ref` argument is resolved the same way as other helpers: a 24-character `oid` or a dashboard title (see `resolve_dashboard_reference`).
Returns the `widgets` collection from an admin **export** of the dashboard—the same `GET /api/v1/dashboards/export?dashboardIds=...&adminAccess=true` flow as `export_dashboard`, not the lightweight `.../widgets` REST list. Resolves `dashboard_ref` as a 24-character `oid` or a title via `resolve_dashboard_reference`. If the export has no `widgets` key or it is empty, returns an empty list.

**Parameters:**

- `dashboard_ref` (str): Dashboard `oid` or title.

**Returns:**

- `list`: Widget objects on success. On failure, a dict with an `"error"` key (unresolved reference, no HTTP response, non-200 status, invalid JSON, or a non-list body).
- `list`: Widget objects from the export payload on success (may be empty). On failure, a dict with an `"error"` key (unresolved reference or export failure). If `widgets` is present but not a list or object map of widget dicts, returns an error dict.

* * * * *

Expand Down
4 changes: 2 additions & 2 deletions examples/dashboard_example.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ api_client.export_to_csv(response, 'dashboard.csv')

---

## Example 3: Get dashboard widgets (lightweight list)
## Example 3: Get dashboard widgets from export

Use `GET /api/v1/dashboards/{id}/widgets` when you need widget metadata (`type`, `oid`, `title`, etc.) without exporting the full dashboard.
Widget definitions come from the same admin export as `export_dashboard` (`GET /api/v1/dashboards/export` with `dashboardIds` and `adminAccess=true`): the `widgets` field on the exported dashboard object (list or object map of widget payloads).

```python
# Pass dashboard oid or title — same resolution as other dashboard helpers
Expand Down
58 changes: 25 additions & 33 deletions pysisense/dashboard/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,13 @@ def export_dashboard(self, dashboard_id: str) -> dict[str, Any]:
return data[0]

def get_dashboard_widgets(self, dashboard_ref: str) -> list[dict[str, Any]] | dict[str, Any]:
"""Retrieve widget metadata for a dashboard.
"""Retrieve widget definitions from an admin export of the dashboard.

Sends a GET request to ``/api/v1/dashboards/{dashboardId}/widgets``.
The response is a JSON array of widget objects (fields such as ``type``,
``oid``, ``title``, and ``created`` depend on the Sisense version).
Uses ``export_dashboard`` (``GET /api/v1/dashboards/export`` with
``dashboardIds`` and ``adminAccess=true``), then reads the ``widgets``
field from the first exported dashboard object—the same payload shape
used by ``get_dashboard_script`` and ``get_widget_script``. If
``widgets`` is missing or empty, an empty list is returned.

``dashboard_ref`` is resolved with ``resolve_dashboard_reference`` so it
may be either a 24-character dashboard ``oid`` or a dashboard title.
Expand All @@ -261,10 +263,10 @@ def get_dashboard_widgets(self, dashboard_ref: str) -> list[dict[str, Any]] | di
Returns
-------
list[dict[str, Any]] | dict[str, Any]
The widget list on success. On failure, ``{"error": "..."}`` when
the reference cannot be resolved, the HTTP layer returns no
response, the status code is not 200, the body is not valid JSON,
or the parsed body is not a list.
A list of widget objects on success (possibly empty). On failure,
``{"error": "..."}`` when the reference cannot be resolved or
``export_dashboard`` fails. If ``widgets`` is present but neither a
list nor a mapping of widget objects, returns an error dict.
"""
resolved = self.resolve_dashboard_reference(dashboard_ref)
if not resolved.get("success"):
Expand All @@ -278,32 +280,22 @@ def get_dashboard_widgets(self, dashboard_ref: str) -> list[dict[str, Any]] | di
self.logger.error(msg)
return {"error": msg}

endpoint = f"/api/v1/dashboards/{dashboard_id}/widgets"
self.logger.debug(f"Fetching widgets from: {endpoint}")
self.logger.debug(f"Loading widgets via export_dashboard for dashboard '{dashboard_id}'")

response = self.api_client.get(endpoint)

if response is None:
self.logger.error(f"GET request for dashboard widgets failed (no response): {dashboard_id}")
return {"error": f"No response received while retrieving widgets for dashboard '{dashboard_id}'"}

if response.status_code != 200:
try:
error_message = response.json()
except Exception:
error_message = getattr(response, "text", None) or "No response text available."
self.logger.error(f"Failed to retrieve widgets for dashboard {dashboard_id}. Error: {error_message}")
return {"error": f"Failed to retrieve widgets for dashboard '{dashboard_id}'. {error_message}"}
exported = self.export_dashboard(dashboard_id)
if "error" in exported:
self.logger.error(f"get_dashboard_widgets: export failed for '{dashboard_id}': {exported['error']}")
return exported

try:
widgets = response.json()
except Exception:
self.logger.error(f"Failed to parse widgets JSON for dashboard '{dashboard_id}'")
return {"error": f"Failed to parse widgets response for dashboard '{dashboard_id}'"}

if not isinstance(widgets, list):
self.logger.error(f"Unexpected widgets response structure for dashboard '{dashboard_id}'")
return {"error": f"Unexpected widgets response structure for dashboard '{dashboard_id}'"}
raw_widgets = exported.get("widgets") or []
if isinstance(raw_widgets, dict):
widgets = [w for w in raw_widgets.values() if isinstance(w, dict)]
elif isinstance(raw_widgets, list):
widgets = [w for w in raw_widgets if isinstance(w, dict)]
else:
msg = f"Unexpected widgets type in export for dashboard '{dashboard_id}'"
self.logger.error(msg)
return {"error": msg}

self.logger.info(f"Successfully retrieved {len(widgets)} widgets for dashboard '{dashboard_id}'.")
self.logger.info(f"Successfully retrieved {len(widgets)} widgets for dashboard '{dashboard_id}' from export.")
return widgets
46 changes: 37 additions & 9 deletions tests/unit/test_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,66 +138,94 @@ def test_returns_error_on_none_response(self):
{"oid": "w2", "title": "Pivot B", "type": "pivot"},
]

_EXPORT_DASH = {**_DASHBOARD, "widgets": _WIDGETS}


class TestGetDashboardWidgets:
def test_returns_widget_list_on_success(self):
dash = _make_dash(
get_responses={
"/api/v1/dashboards/admin": FakeResponse(200, [_DASHBOARD]),
"/api/v1/dashboards/dash123/widgets": FakeResponse(200, _WIDGETS),
"/api/v1/dashboards/export": FakeResponse(200, [_EXPORT_DASH]),
}
)
result = dash.get_dashboard_widgets("Sales Report")
assert isinstance(result, list)
assert len(result) == 2
assert result[0]["oid"] == "w1"

def test_returns_empty_list_when_widgets_missing(self):
export_no_widgets = {**_DASHBOARD, "widgets": []}
dash = _make_dash(
get_responses={
"/api/v1/dashboards/admin": FakeResponse(200, [_DASHBOARD]),
"/api/v1/dashboards/export": FakeResponse(200, [export_no_widgets]),
}
)
result = dash.get_dashboard_widgets("Sales Report")
assert result == []

def test_normalizes_widgets_object_map(self):
export_map = {**_DASHBOARD, "widgets": {"w1": _WIDGETS[0], "w2": _WIDGETS[1]}}
dash = _make_dash(
get_responses={
"/api/v1/dashboards/admin": FakeResponse(200, [_DASHBOARD]),
"/api/v1/dashboards/export": FakeResponse(200, [export_map]),
}
)
result = dash.get_dashboard_widgets("Sales Report")
assert isinstance(result, list)
assert len(result) == 2
assert {w["oid"] for w in result} == {"w1", "w2"}

def test_returns_error_when_reference_unresolved(self):
dash = _make_dash(get_responses={"/api/v1/dashboards/admin": FakeResponse(200, [])})
result = dash.get_dashboard_widgets("NoSuchDash")
assert isinstance(result, dict)
assert "error" in result

def test_returns_error_on_none_widgets_response(self):
def test_returns_error_on_export_none_response(self):
dash = _make_dash(
get_responses={
"/api/v1/dashboards/admin": FakeResponse(200, [_DASHBOARD]),
"/api/v1/dashboards/dash123/widgets": None,
"/api/v1/dashboards/export": None,
}
)
result = dash.get_dashboard_widgets("dash123")
assert isinstance(result, dict)
assert "error" in result

def test_returns_error_on_non_200_widgets(self):
def test_returns_error_on_export_non_200(self):
dash = _make_dash(
get_responses={
"/api/v1/dashboards/admin": FakeResponse(200, [_DASHBOARD]),
"/api/v1/dashboards/dash123/widgets": FakeResponse(403, {"message": "forbidden"}),
"/api/v1/dashboards/export": FakeResponse(403, {"message": "forbidden"}),
}
)
result = dash.get_dashboard_widgets("Sales Report")
assert isinstance(result, dict)
assert "error" in result

def test_returns_error_when_body_not_list(self):
def test_returns_error_when_widgets_wrong_type(self):
bad_export = {**_DASHBOARD, "widgets": "not-a-list-or-map"}
dash = _make_dash(
get_responses={
"/api/v1/dashboards/admin": FakeResponse(200, [_DASHBOARD]),
"/api/v1/dashboards/dash123/widgets": FakeResponse(200, {"unexpected": True}),
"/api/v1/dashboards/export": FakeResponse(200, [bad_export]),
}
)
result = dash.get_dashboard_widgets("Sales Report")
assert isinstance(result, dict)
assert "error" in result

def test_resolves_24_char_oid_with_single_admin_then_widgets(self):
def test_resolves_24_char_oid_with_admin_then_export(self):
dash_id = "a" * 24
dash_row = {**_DASHBOARD, "oid": dash_id}
export_dash = {**dash_row, "widgets": _WIDGETS}
dash = _make_dash(
get_responses={
"/api/v1/dashboards/admin": FakeResponse(200, [dash_row]),
f"/api/v1/dashboards/{dash_id}/widgets": FakeResponse(200, _WIDGETS),
"/api/v1/dashboards/export": FakeResponse(200, [export_dash]),
}
)
result = dash.get_dashboard_widgets(dash_id)
Expand Down
Loading