Skip to content

Commit 0259d22

Browse files
committed
Fix: Report page url fix for PowerBI (#24651)
* report page url fix * add tests
1 parent 330d292 commit 0259d22

File tree

4 files changed

+136
-1
lines changed

4 files changed

+136
-1
lines changed

ingestion/src/metadata/ingestion/source/dashboard/powerbi/client.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
PowerBIReport,
3939
PowerBiTable,
4040
PowerBiToken,
41+
ReportPagesAPIResponse,
4142
ReportsResponse,
4243
TablesResponse,
4344
Tile,
@@ -281,6 +282,21 @@ def fetch_dataset_tables(
281282

282283
return None
283284

285+
def fetch_report_pages(self, group_id: str, report_id: str) -> Optional[List[dict]]:
286+
# get report pages for report url formation
287+
try:
288+
# https://api.powerbi.com/v1.0/myorg/groups/4e57dcbb-***/reports/a2902011-***/pages
289+
response_data = self.client.get(
290+
f"/myorg/groups/{group_id}/reports/{report_id}/pages"
291+
)
292+
if response_data:
293+
response = ReportPagesAPIResponse(**response_data)
294+
return response.value
295+
except Exception as exc:
296+
logger.debug(traceback.format_exc())
297+
logger.warning(f"Error fetching report pages: {exc}")
298+
return []
299+
284300
def regex_to_odata_condition(self, regex: str) -> str:
285301
"""
286302
Convert a regex pattern to an OData filter condition

ingestion/src/metadata/ingestion/source/dashboard/powerbi/metadata.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
PowerBiMeasureModel,
7272
PowerBIReport,
7373
PowerBiTable,
74+
ReportPage,
7475
)
7576
from metadata.ingestion.source.database.column_type_parser import ColumnTypeParser
7677
from metadata.utils import fqn
@@ -305,9 +306,23 @@ def _get_report_url(self, workspace_id: str, dashboard_id: str) -> str:
305306
"""
306307
Method to build the dashboard url
307308
"""
309+
page_id = ""
310+
try:
311+
pages: Optional[
312+
List[ReportPage]
313+
] = self.client.api_client.fetch_report_pages(workspace_id, dashboard_id)
314+
if len(pages) >= 1:
315+
# get first page out of multiple pages otherwise
316+
# get page if of single page
317+
page_id = pages[0].name
318+
page_id = f"/{page_id}" if page_id else ""
319+
except Exception as exc:
320+
logger.debug(traceback.format_exc())
321+
logger.warning(f"Error building report page url: {exc}")
322+
# https://app.powerbi.com/groups/4e57dcbb-***/reports/a2902011-***/098b***?experience=power-bi
308323
return (
309324
f"{clean_uri(self.service_connection.hostPort)}/groups/"
310-
f"{workspace_id}/reports/{dashboard_id}/ReportSection?experience=power-bi"
325+
f"{workspace_id}/reports/{dashboard_id}{page_id}?experience=power-bi"
311326
)
312327

313328
def _get_dataset_url(self, workspace_id: str, dataset_id: str) -> str:

ingestion/src/metadata/ingestion/source/dashboard/powerbi/models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,22 @@ class DataModelSchema(BaseModel):
302302

303303
tables: Optional[List[PowerBiTable]] = None
304304
connectionFile: Optional[ConnectionFile] = None
305+
306+
307+
class ReportPage(BaseModel):
308+
"""
309+
PowerBI report pages API response
310+
single report Page object
311+
"""
312+
313+
name: str
314+
displayName: Optional[str] = None
315+
316+
317+
class ReportPagesAPIResponse(BaseModel):
318+
"""
319+
PowerBI report pages API response
320+
"""
321+
322+
odata_context: str = Field(alias="@odata.context")
323+
value: Optional[List[ReportPage]] = None

ingestion/tests/unit/topology/dashboard/test_powerbi.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
PowerBIDashboard,
2424
PowerBiTable,
2525
PowerBITableSource,
26+
ReportPage,
2627
UpstreaDataflow,
2728
)
2829
from metadata.utils import fqn
@@ -741,3 +742,87 @@ def test_create_dataset_upstream_dataset_column_lineage(self):
741742
self.assertEqual(
742743
result[0].toColumn.root, "service.downstream_dataset.orders.order_id"
743744
)
745+
746+
@pytest.mark.order(13)
747+
def test_get_report_url(self):
748+
"""
749+
Test report URL generation with different page scenarios
750+
"""
751+
from unittest.mock import MagicMock
752+
753+
workspace_id = "test-workspace-123"
754+
dashboard_id = "test-dashboard-456"
755+
756+
# Create a mock client with api_client
757+
mock_api_client = MagicMock()
758+
self.powerbi.client = MagicMock()
759+
self.powerbi.client.api_client = mock_api_client
760+
761+
# Test with multiple pages - should use first page name
762+
with patch(
763+
"metadata.ingestion.source.dashboard.powerbi.metadata.clean_uri"
764+
) as mock_clean_uri:
765+
mock_clean_uri.return_value = "https://app.powerbi.com"
766+
mock_api_client.fetch_report_pages.return_value = [
767+
ReportPage(name="page1", displayName="Page 1"),
768+
ReportPage(name="page2", displayName="Page 2"),
769+
ReportPage(name="page3", displayName="Page 3"),
770+
]
771+
772+
result = self.powerbi._get_report_url(workspace_id, dashboard_id)
773+
774+
mock_api_client.fetch_report_pages.assert_called_once_with(
775+
workspace_id, dashboard_id
776+
)
777+
self.assertEqual(
778+
result,
779+
f"https://app.powerbi.com/groups/{workspace_id}/reports/{dashboard_id}/page1?experience=power-bi",
780+
)
781+
782+
# Test with single page - should use that page name
783+
with patch(
784+
"metadata.ingestion.source.dashboard.powerbi.metadata.clean_uri"
785+
) as mock_clean_uri:
786+
mock_clean_uri.return_value = "https://app.powerbi.com"
787+
mock_api_client.fetch_report_pages.reset_mock()
788+
mock_api_client.fetch_report_pages.return_value = [
789+
ReportPage(name="single-page", displayName="Single Page")
790+
]
791+
792+
result = self.powerbi._get_report_url(workspace_id, dashboard_id)
793+
794+
self.assertEqual(
795+
result,
796+
f"https://app.powerbi.com/groups/{workspace_id}/reports/{dashboard_id}/single-page?experience=power-bi",
797+
)
798+
799+
# Test with no pages - should not add page_id
800+
with patch(
801+
"metadata.ingestion.source.dashboard.powerbi.metadata.clean_uri"
802+
) as mock_clean_uri:
803+
mock_clean_uri.return_value = "https://app.powerbi.com"
804+
mock_api_client.fetch_report_pages.reset_mock()
805+
mock_api_client.fetch_report_pages.return_value = []
806+
807+
result = self.powerbi._get_report_url(workspace_id, dashboard_id)
808+
809+
self.assertEqual(
810+
result,
811+
f"https://app.powerbi.com/groups/{workspace_id}/reports/{dashboard_id}?experience=power-bi",
812+
)
813+
814+
# Test with exception during fetch_report_pages - should handle gracefully
815+
with patch(
816+
"metadata.ingestion.source.dashboard.powerbi.metadata.clean_uri"
817+
) as mock_clean_uri:
818+
mock_clean_uri.return_value = "https://app.powerbi.com"
819+
mock_api_client.fetch_report_pages.reset_mock()
820+
mock_api_client.fetch_report_pages.side_effect = Exception("API Error")
821+
822+
result = self.powerbi._get_report_url(workspace_id, dashboard_id)
823+
824+
# Should build URL without page_id when exception occurs
825+
self.assertEqual(
826+
result,
827+
f"https://app.powerbi.com/groups/{workspace_id}/reports/{dashboard_id}?experience=power-bi",
828+
)

0 commit comments

Comments
 (0)