semantic-link-labs 0.12.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. semantic_link_labs-0.12.8.dist-info/METADATA +354 -0
  2. semantic_link_labs-0.12.8.dist-info/RECORD +243 -0
  3. semantic_link_labs-0.12.8.dist-info/WHEEL +5 -0
  4. semantic_link_labs-0.12.8.dist-info/licenses/LICENSE +21 -0
  5. semantic_link_labs-0.12.8.dist-info/top_level.txt +1 -0
  6. sempy_labs/__init__.py +606 -0
  7. sempy_labs/_a_lib_info.py +2 -0
  8. sempy_labs/_ai.py +437 -0
  9. sempy_labs/_authentication.py +264 -0
  10. sempy_labs/_bpa_translation/_model/_translations_am-ET.po +869 -0
  11. sempy_labs/_bpa_translation/_model/_translations_ar-AE.po +908 -0
  12. sempy_labs/_bpa_translation/_model/_translations_bg-BG.po +968 -0
  13. sempy_labs/_bpa_translation/_model/_translations_ca-ES.po +963 -0
  14. sempy_labs/_bpa_translation/_model/_translations_cs-CZ.po +943 -0
  15. sempy_labs/_bpa_translation/_model/_translations_da-DK.po +945 -0
  16. sempy_labs/_bpa_translation/_model/_translations_de-DE.po +988 -0
  17. sempy_labs/_bpa_translation/_model/_translations_el-GR.po +993 -0
  18. sempy_labs/_bpa_translation/_model/_translations_es-ES.po +971 -0
  19. sempy_labs/_bpa_translation/_model/_translations_fa-IR.po +933 -0
  20. sempy_labs/_bpa_translation/_model/_translations_fi-FI.po +942 -0
  21. sempy_labs/_bpa_translation/_model/_translations_fr-FR.po +994 -0
  22. sempy_labs/_bpa_translation/_model/_translations_ga-IE.po +967 -0
  23. sempy_labs/_bpa_translation/_model/_translations_he-IL.po +902 -0
  24. sempy_labs/_bpa_translation/_model/_translations_hi-IN.po +944 -0
  25. sempy_labs/_bpa_translation/_model/_translations_hu-HU.po +963 -0
  26. sempy_labs/_bpa_translation/_model/_translations_id-ID.po +946 -0
  27. sempy_labs/_bpa_translation/_model/_translations_is-IS.po +939 -0
  28. sempy_labs/_bpa_translation/_model/_translations_it-IT.po +986 -0
  29. sempy_labs/_bpa_translation/_model/_translations_ja-JP.po +846 -0
  30. sempy_labs/_bpa_translation/_model/_translations_ko-KR.po +839 -0
  31. sempy_labs/_bpa_translation/_model/_translations_mt-MT.po +967 -0
  32. sempy_labs/_bpa_translation/_model/_translations_nl-NL.po +978 -0
  33. sempy_labs/_bpa_translation/_model/_translations_pl-PL.po +962 -0
  34. sempy_labs/_bpa_translation/_model/_translations_pt-BR.po +962 -0
  35. sempy_labs/_bpa_translation/_model/_translations_pt-PT.po +957 -0
  36. sempy_labs/_bpa_translation/_model/_translations_ro-RO.po +968 -0
  37. sempy_labs/_bpa_translation/_model/_translations_ru-RU.po +964 -0
  38. sempy_labs/_bpa_translation/_model/_translations_sk-SK.po +952 -0
  39. sempy_labs/_bpa_translation/_model/_translations_sl-SL.po +950 -0
  40. sempy_labs/_bpa_translation/_model/_translations_sv-SE.po +942 -0
  41. sempy_labs/_bpa_translation/_model/_translations_ta-IN.po +976 -0
  42. sempy_labs/_bpa_translation/_model/_translations_te-IN.po +947 -0
  43. sempy_labs/_bpa_translation/_model/_translations_th-TH.po +924 -0
  44. sempy_labs/_bpa_translation/_model/_translations_tr-TR.po +953 -0
  45. sempy_labs/_bpa_translation/_model/_translations_uk-UA.po +961 -0
  46. sempy_labs/_bpa_translation/_model/_translations_zh-CN.po +804 -0
  47. sempy_labs/_bpa_translation/_model/_translations_zu-ZA.po +969 -0
  48. sempy_labs/_capacities.py +1198 -0
  49. sempy_labs/_capacity_migration.py +660 -0
  50. sempy_labs/_clear_cache.py +351 -0
  51. sempy_labs/_connections.py +610 -0
  52. sempy_labs/_dashboards.py +69 -0
  53. sempy_labs/_data_access_security.py +98 -0
  54. sempy_labs/_data_pipelines.py +162 -0
  55. sempy_labs/_dataflows.py +668 -0
  56. sempy_labs/_dax.py +501 -0
  57. sempy_labs/_daxformatter.py +80 -0
  58. sempy_labs/_delta_analyzer.py +467 -0
  59. sempy_labs/_delta_analyzer_history.py +301 -0
  60. sempy_labs/_dictionary_diffs.py +221 -0
  61. sempy_labs/_documentation.py +147 -0
  62. sempy_labs/_domains.py +51 -0
  63. sempy_labs/_eventhouses.py +182 -0
  64. sempy_labs/_external_data_shares.py +230 -0
  65. sempy_labs/_gateways.py +521 -0
  66. sempy_labs/_generate_semantic_model.py +521 -0
  67. sempy_labs/_get_connection_string.py +84 -0
  68. sempy_labs/_git.py +543 -0
  69. sempy_labs/_graphQL.py +90 -0
  70. sempy_labs/_helper_functions.py +2833 -0
  71. sempy_labs/_icons.py +149 -0
  72. sempy_labs/_job_scheduler.py +609 -0
  73. sempy_labs/_kql_databases.py +149 -0
  74. sempy_labs/_kql_querysets.py +124 -0
  75. sempy_labs/_kusto.py +137 -0
  76. sempy_labs/_labels.py +124 -0
  77. sempy_labs/_list_functions.py +1720 -0
  78. sempy_labs/_managed_private_endpoints.py +253 -0
  79. sempy_labs/_mirrored_databases.py +416 -0
  80. sempy_labs/_mirrored_warehouses.py +60 -0
  81. sempy_labs/_ml_experiments.py +113 -0
  82. sempy_labs/_model_auto_build.py +140 -0
  83. sempy_labs/_model_bpa.py +557 -0
  84. sempy_labs/_model_bpa_bulk.py +378 -0
  85. sempy_labs/_model_bpa_rules.py +859 -0
  86. sempy_labs/_model_dependencies.py +343 -0
  87. sempy_labs/_mounted_data_factories.py +123 -0
  88. sempy_labs/_notebooks.py +441 -0
  89. sempy_labs/_one_lake_integration.py +151 -0
  90. sempy_labs/_onelake.py +131 -0
  91. sempy_labs/_query_scale_out.py +433 -0
  92. sempy_labs/_refresh_semantic_model.py +435 -0
  93. sempy_labs/_semantic_models.py +468 -0
  94. sempy_labs/_spark.py +455 -0
  95. sempy_labs/_sql.py +241 -0
  96. sempy_labs/_sql_audit_settings.py +207 -0
  97. sempy_labs/_sql_endpoints.py +214 -0
  98. sempy_labs/_tags.py +201 -0
  99. sempy_labs/_translations.py +43 -0
  100. sempy_labs/_user_delegation_key.py +44 -0
  101. sempy_labs/_utils.py +79 -0
  102. sempy_labs/_vertipaq.py +1021 -0
  103. sempy_labs/_vpax.py +388 -0
  104. sempy_labs/_warehouses.py +234 -0
  105. sempy_labs/_workloads.py +140 -0
  106. sempy_labs/_workspace_identity.py +72 -0
  107. sempy_labs/_workspaces.py +595 -0
  108. sempy_labs/admin/__init__.py +170 -0
  109. sempy_labs/admin/_activities.py +167 -0
  110. sempy_labs/admin/_apps.py +145 -0
  111. sempy_labs/admin/_artifacts.py +65 -0
  112. sempy_labs/admin/_basic_functions.py +463 -0
  113. sempy_labs/admin/_capacities.py +508 -0
  114. sempy_labs/admin/_dataflows.py +45 -0
  115. sempy_labs/admin/_datasets.py +186 -0
  116. sempy_labs/admin/_domains.py +522 -0
  117. sempy_labs/admin/_external_data_share.py +100 -0
  118. sempy_labs/admin/_git.py +72 -0
  119. sempy_labs/admin/_items.py +265 -0
  120. sempy_labs/admin/_labels.py +211 -0
  121. sempy_labs/admin/_reports.py +241 -0
  122. sempy_labs/admin/_scanner.py +118 -0
  123. sempy_labs/admin/_shared.py +82 -0
  124. sempy_labs/admin/_sharing_links.py +110 -0
  125. sempy_labs/admin/_tags.py +131 -0
  126. sempy_labs/admin/_tenant.py +503 -0
  127. sempy_labs/admin/_tenant_keys.py +89 -0
  128. sempy_labs/admin/_users.py +140 -0
  129. sempy_labs/admin/_workspaces.py +236 -0
  130. sempy_labs/deployment_pipeline/__init__.py +23 -0
  131. sempy_labs/deployment_pipeline/_items.py +580 -0
  132. sempy_labs/directlake/__init__.py +57 -0
  133. sempy_labs/directlake/_autosync.py +58 -0
  134. sempy_labs/directlake/_directlake_schema_compare.py +120 -0
  135. sempy_labs/directlake/_directlake_schema_sync.py +161 -0
  136. sempy_labs/directlake/_dl_helper.py +274 -0
  137. sempy_labs/directlake/_generate_shared_expression.py +94 -0
  138. sempy_labs/directlake/_get_directlake_lakehouse.py +62 -0
  139. sempy_labs/directlake/_get_shared_expression.py +34 -0
  140. sempy_labs/directlake/_guardrails.py +96 -0
  141. sempy_labs/directlake/_list_directlake_model_calc_tables.py +70 -0
  142. sempy_labs/directlake/_show_unsupported_directlake_objects.py +90 -0
  143. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +239 -0
  144. sempy_labs/directlake/_update_directlake_partition_entity.py +259 -0
  145. sempy_labs/directlake/_warm_cache.py +236 -0
  146. sempy_labs/dotnet_lib/dotnet.runtime.config.json +10 -0
  147. sempy_labs/environment/__init__.py +23 -0
  148. sempy_labs/environment/_items.py +212 -0
  149. sempy_labs/environment/_pubstage.py +223 -0
  150. sempy_labs/eventstream/__init__.py +37 -0
  151. sempy_labs/eventstream/_items.py +263 -0
  152. sempy_labs/eventstream/_topology.py +652 -0
  153. sempy_labs/graph/__init__.py +59 -0
  154. sempy_labs/graph/_groups.py +651 -0
  155. sempy_labs/graph/_sensitivity_labels.py +120 -0
  156. sempy_labs/graph/_teams.py +125 -0
  157. sempy_labs/graph/_user_licenses.py +96 -0
  158. sempy_labs/graph/_users.py +516 -0
  159. sempy_labs/graph_model/__init__.py +15 -0
  160. sempy_labs/graph_model/_background_jobs.py +63 -0
  161. sempy_labs/graph_model/_items.py +149 -0
  162. sempy_labs/lakehouse/__init__.py +67 -0
  163. sempy_labs/lakehouse/_blobs.py +247 -0
  164. sempy_labs/lakehouse/_get_lakehouse_columns.py +102 -0
  165. sempy_labs/lakehouse/_get_lakehouse_tables.py +274 -0
  166. sempy_labs/lakehouse/_helper.py +250 -0
  167. sempy_labs/lakehouse/_lakehouse.py +351 -0
  168. sempy_labs/lakehouse/_livy_sessions.py +143 -0
  169. sempy_labs/lakehouse/_materialized_lake_views.py +157 -0
  170. sempy_labs/lakehouse/_partitioning.py +165 -0
  171. sempy_labs/lakehouse/_schemas.py +217 -0
  172. sempy_labs/lakehouse/_shortcuts.py +440 -0
  173. sempy_labs/migration/__init__.py +35 -0
  174. sempy_labs/migration/_create_pqt_file.py +238 -0
  175. sempy_labs/migration/_direct_lake_to_import.py +105 -0
  176. sempy_labs/migration/_migrate_calctables_to_lakehouse.py +398 -0
  177. sempy_labs/migration/_migrate_calctables_to_semantic_model.py +148 -0
  178. sempy_labs/migration/_migrate_model_objects_to_semantic_model.py +533 -0
  179. sempy_labs/migration/_migrate_tables_columns_to_semantic_model.py +172 -0
  180. sempy_labs/migration/_migration_validation.py +71 -0
  181. sempy_labs/migration/_refresh_calc_tables.py +131 -0
  182. sempy_labs/mirrored_azure_databricks_catalog/__init__.py +15 -0
  183. sempy_labs/mirrored_azure_databricks_catalog/_discover.py +213 -0
  184. sempy_labs/mirrored_azure_databricks_catalog/_refresh_catalog_metadata.py +45 -0
  185. sempy_labs/ml_model/__init__.py +23 -0
  186. sempy_labs/ml_model/_functions.py +427 -0
  187. sempy_labs/report/_BPAReportTemplate.json +232 -0
  188. sempy_labs/report/__init__.py +55 -0
  189. sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json +9 -0
  190. sempy_labs/report/_bpareporttemplate/.platform +11 -0
  191. sempy_labs/report/_bpareporttemplate/StaticResources/SharedResources/BaseThemes/CY24SU06.json +710 -0
  192. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/page.json +11 -0
  193. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/1b08bce3bebabb0a27a8/visual.json +191 -0
  194. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/2f22ddb70c301693c165/visual.json +438 -0
  195. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/3b1182230aa6c600b43a/visual.json +127 -0
  196. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/58577ba6380c69891500/visual.json +576 -0
  197. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/a2a8fa5028b3b776c96c/visual.json +207 -0
  198. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/adfd47ef30652707b987/visual.json +506 -0
  199. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/b6a80ee459e716e170b1/visual.json +127 -0
  200. sempy_labs/report/_bpareporttemplate/definition/pages/01d72098bda5055bd500/visuals/ce3130a721c020cc3d81/visual.json +513 -0
  201. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/page.json +8 -0
  202. sempy_labs/report/_bpareporttemplate/definition/pages/92735ae19b31712208ad/visuals/66e60dfb526437cd78d1/visual.json +112 -0
  203. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/page.json +11 -0
  204. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/07deb8bce824e1be37d7/visual.json +513 -0
  205. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0b1c68838818b32ad03b/visual.json +352 -0
  206. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0c171de9d2683d10b930/visual.json +37 -0
  207. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/0efa01be0510e40a645e/visual.json +542 -0
  208. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/6bf2f0eb830ab53cc668/visual.json +221 -0
  209. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/88d8141cb8500b60030c/visual.json +127 -0
  210. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/a753273590beed656a03/visual.json +576 -0
  211. sempy_labs/report/_bpareporttemplate/definition/pages/c597da16dc7e63222a82/visuals/b8fdc82cddd61ac447bc/visual.json +127 -0
  212. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/page.json +9 -0
  213. sempy_labs/report/_bpareporttemplate/definition/pages/d37dce724a0ccc30044b/visuals/ce8532a7e25020271077/visual.json +38 -0
  214. sempy_labs/report/_bpareporttemplate/definition/pages/pages.json +10 -0
  215. sempy_labs/report/_bpareporttemplate/definition/report.json +176 -0
  216. sempy_labs/report/_bpareporttemplate/definition/version.json +4 -0
  217. sempy_labs/report/_bpareporttemplate/definition.pbir +14 -0
  218. sempy_labs/report/_download_report.py +76 -0
  219. sempy_labs/report/_export_report.py +257 -0
  220. sempy_labs/report/_generate_report.py +427 -0
  221. sempy_labs/report/_paginated.py +76 -0
  222. sempy_labs/report/_report_bpa.py +354 -0
  223. sempy_labs/report/_report_bpa_rules.py +115 -0
  224. sempy_labs/report/_report_functions.py +581 -0
  225. sempy_labs/report/_report_helper.py +227 -0
  226. sempy_labs/report/_report_list_functions.py +110 -0
  227. sempy_labs/report/_report_rebind.py +149 -0
  228. sempy_labs/report/_reportwrapper.py +3100 -0
  229. sempy_labs/report/_save_report.py +147 -0
  230. sempy_labs/snowflake_database/__init__.py +10 -0
  231. sempy_labs/snowflake_database/_items.py +105 -0
  232. sempy_labs/sql_database/__init__.py +21 -0
  233. sempy_labs/sql_database/_items.py +201 -0
  234. sempy_labs/sql_database/_mirroring.py +79 -0
  235. sempy_labs/theme/__init__.py +12 -0
  236. sempy_labs/theme/_org_themes.py +129 -0
  237. sempy_labs/tom/__init__.py +3 -0
  238. sempy_labs/tom/_model.py +5977 -0
  239. sempy_labs/variable_library/__init__.py +19 -0
  240. sempy_labs/variable_library/_functions.py +403 -0
  241. sempy_labs/warehouse/__init__.py +28 -0
  242. sempy_labs/warehouse/_items.py +234 -0
  243. sempy_labs/warehouse/_restore_points.py +309 -0
@@ -0,0 +1,354 @@
1
+ from typing import Optional
2
+ import pandas as pd
3
+ import datetime
4
+ from sempy._utils._log import log
5
+ from sempy_labs.report._reportwrapper import connect_report
6
+ from sempy_labs.report._report_bpa_rules import report_bpa_rules
7
+ from sempy_labs._helper_functions import (
8
+ format_dax_object_name,
9
+ save_as_delta_table,
10
+ resolve_item_name_and_id,
11
+ resolve_workspace_capacity,
12
+ _get_column_aggregate,
13
+ resolve_workspace_name_and_id,
14
+ )
15
+ from sempy_labs.lakehouse import get_lakehouse_tables, lakehouse_attached
16
+ import sempy_labs._icons as icons
17
+ from IPython.display import display, HTML
18
+ from uuid import UUID
19
+
20
+
21
+ @log
22
+ def run_report_bpa(
23
+ report: str,
24
+ rules: Optional[pd.DataFrame] = None,
25
+ workspace: Optional[str | UUID] = None,
26
+ # language: Optional[str] = None,
27
+ export: bool = False,
28
+ return_dataframe: bool = False,
29
+ ):
30
+ """
31
+ Displays an HTML visualization of the results of the Best Practice Analyzer scan for a report.
32
+ Note: As with all functions which rely on the ReportWrapper, this function requires the report to be in the 'PBIR' format.
33
+
34
+ Parameters
35
+ ----------
36
+ report : str
37
+ Name of the report.
38
+ rules : pandas.DataFrame, default=None
39
+ A pandas dataframe containing rules to be evaluated.
40
+ workspace : str | uuid.UUID, default=None
41
+ The Fabric workspace name or ID.
42
+ Defaults to None which resolves to the workspace of the attached lakehouse
43
+ or if no lakehouse attached, resolves to the workspace of the notebook.
44
+ export : bool, default=False
45
+ If True, exports the resulting dataframe to a delta table in the lakehouse attached to the notebook.
46
+ return_dataframe : bool, default=False
47
+ If True, returns a pandas dataframe instead of the visualization.
48
+
49
+ Returns
50
+ -------
51
+ pandas.DataFrame
52
+ A pandas dataframe in HTML format showing report objects which violated the best practice analyzer rules.
53
+ """
54
+
55
+ with connect_report(
56
+ report=report, workspace=workspace, readonly=True, show_diffs=False
57
+ ) as rpt:
58
+
59
+ dfCV = rpt.list_custom_visuals()
60
+ dfP = rpt.list_pages()
61
+ dfRF = rpt.list_report_filters()
62
+ dfRF["Filter Object"] = format_dax_object_name(
63
+ dfRF["Table Name"], dfRF["Object Name"]
64
+ )
65
+ dfPF = rpt.list_page_filters()
66
+
67
+ dfPF["Filter Object"] = (
68
+ dfPF["Page Display Name"]
69
+ + " : "
70
+ + format_dax_object_name(dfPF["Table Name"], dfPF["Object Name"])
71
+ )
72
+ dfVF = rpt.list_visual_filters()
73
+ dfVF["Filter Object"] = (
74
+ format_dax_object_name(dfVF["Page Display Name"], dfVF["Visual Name"])
75
+ + " : "
76
+ + format_dax_object_name(dfVF["Table Name"], dfVF["Object Name"])
77
+ )
78
+ dfRLM = rpt.list_report_level_measures()
79
+ dfV = rpt.list_visuals()
80
+ dfV["Visual Full Name"] = format_dax_object_name(
81
+ dfV["Page Display Name"], dfV["Visual Name"]
82
+ )
83
+ dfSMO = rpt.list_semantic_model_objects(extended=True)
84
+
85
+ # Translations
86
+ if rules is None:
87
+ rules = report_bpa_rules()
88
+
89
+ scope_to_dataframe = {
90
+ "Custom Visual": (dfCV, ["Custom Visual Display Name"]),
91
+ "Page": (dfP, ["Page Display Name"]),
92
+ "Visual": (dfV, ["Visual Full Name"]),
93
+ "Report Filter": (dfRF, ["Filter Object"]),
94
+ "Page Filter": (dfPF, ["Filter Object"]),
95
+ "Visual Filter": (dfVF, ["Filter Object"]),
96
+ "Report Level Measure": (dfRLM, ["Measure Name"]),
97
+ "Semantic Model": (dfSMO, ["Report Source Object"]),
98
+ }
99
+
100
+ def execute_rule(row):
101
+ scopes = row["Scope"]
102
+
103
+ # support both str and list as scope type
104
+ if isinstance(scopes, str):
105
+ scopes = [scopes]
106
+
107
+ # collect output dataframes
108
+ df_outputs = []
109
+
110
+ for scope in scopes:
111
+ # common fields for each scope
112
+ (df, violation_cols_or_func) = scope_to_dataframe[scope]
113
+
114
+ # execute rule and subset df
115
+ df_violations = df[row["Expression"](df)]
116
+
117
+ # subset the right output columns (e.g. Table Name & Column Name)
118
+ if isinstance(violation_cols_or_func, list):
119
+ violation_func = lambda violations: violations[
120
+ violation_cols_or_func
121
+ ]
122
+ else:
123
+ violation_func = violation_cols_or_func
124
+
125
+ # build output data frame
126
+ df_output = violation_func(df_violations).copy()
127
+
128
+ df_output.columns = ["Object Name"]
129
+ df_output["Rule Name"] = row["Rule Name"]
130
+ df_output["Category"] = row["Category"]
131
+
132
+ # Handle 'Object Type' based on scope
133
+ if scope == "Semantic Model":
134
+ # Ensure 'Report Source Object' is unique in dfSMO
135
+ dfSMO_unique = dfSMO.drop_duplicates(subset="Report Source Object")
136
+ # Map 'Object Name' to the 'Report Source' column in dfSMO
137
+ df_output["Object Type"] = df_output["Object Name"].map(
138
+ dfSMO_unique.set_index("Report Source Object")["Report Source"]
139
+ )
140
+ else:
141
+ df_output["Object Type"] = scope
142
+
143
+ df_output["Severity"] = row["Severity"]
144
+ df_output["Description"] = row["Description"]
145
+ if scope in ["Page", "Page Filter"]:
146
+ if not df_output.empty:
147
+ for idx, r in df_output.iterrows():
148
+ df_output.loc[idx, "URL"] = rpt._get_url(
149
+ page_name=r["Object Name"]
150
+ )
151
+ elif scope in ["Visual", "Visual Filter"]:
152
+ if not df_output.empty:
153
+ for idx, r in df_output.iterrows():
154
+ df_output.loc[idx, "URL"] = rpt._get_url(
155
+ page_name=r["Object Name"].split("[")[0][1:-1],
156
+ visual_name=r["Object Name"].split("[")[1].rstrip("]"),
157
+ )
158
+ else:
159
+ df_output["URL"] = rpt._get_url()
160
+ df_outputs.append(df_output)
161
+
162
+ return df_outputs
163
+
164
+ # flatten list of lists
165
+ flatten_dfs = [
166
+ df for dfs in rules.apply(execute_rule, axis=1).tolist() for df in dfs
167
+ ]
168
+
169
+ finalDF = pd.concat(flatten_dfs, ignore_index=True)
170
+ finalDF = finalDF[
171
+ [
172
+ "Category",
173
+ "Rule Name",
174
+ "Object Type",
175
+ "Object Name",
176
+ "Severity",
177
+ "Description",
178
+ "URL",
179
+ # "Report URL",
180
+ ]
181
+ ]
182
+
183
+ if return_dataframe:
184
+ return finalDF
185
+
186
+ if export:
187
+ if not lakehouse_attached():
188
+ raise ValueError(
189
+ f"{icons.red_dot} In order to export the BPA results, a lakehouse must be attached to the notebook."
190
+ )
191
+
192
+ now = datetime.datetime.now()
193
+ delta_table_name = "reportbparesults"
194
+ lakeT = get_lakehouse_tables()
195
+ lakeT_filt = lakeT[lakeT["Table Name"] == delta_table_name]
196
+
197
+ if len(lakeT_filt) == 0:
198
+ runId = 1
199
+ else:
200
+ max_run_id = _get_column_aggregate(table_name=delta_table_name)
201
+ runId = max_run_id + 1
202
+
203
+ (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace)
204
+ (report_name, report_id) = resolve_item_name_and_id(
205
+ item=report, type="Report", workspace=workspace_id
206
+ )
207
+
208
+ export_df = finalDF.copy()
209
+ capacity_id, capacity_name = resolve_workspace_capacity(workspace=workspace_id)
210
+ export_df["Capacity Name"] = capacity_name
211
+ export_df["Capacity Id"] = capacity_id
212
+ export_df["Workspace Name"] = workspace_name
213
+ export_df["Workspace Id"] = workspace_id
214
+ export_df["Report Name"] = report_name
215
+ export_df["Report Id"] = report_id
216
+ export_df["RunId"] = runId
217
+ export_df["Timestamp"] = now
218
+ export_df["RunId"] = export_df["RunId"].astype(int)
219
+
220
+ export_df = export_df[
221
+ [
222
+ "Capacity Name",
223
+ "Capacity Id",
224
+ "Workspace Name",
225
+ "Workspace Id",
226
+ "Report Name",
227
+ "Report Id",
228
+ "Category",
229
+ "Rule Name",
230
+ "Object Type",
231
+ "Object Name",
232
+ "Severity",
233
+ "Description",
234
+ "URL",
235
+ ]
236
+ ]
237
+
238
+ save_as_delta_table(
239
+ dataframe=export_df,
240
+ delta_table_name=delta_table_name,
241
+ write_mode="append",
242
+ merge_schema=True,
243
+ )
244
+
245
+ return
246
+
247
+ finalDF.replace(
248
+ {"Warning": icons.warning, "Error": icons.error, "Info": icons.info},
249
+ inplace=True,
250
+ )
251
+
252
+ pd.set_option("display.max_colwidth", 100)
253
+
254
+ finalDF = (
255
+ finalDF[
256
+ [
257
+ "Category",
258
+ "Rule Name",
259
+ "Object Type",
260
+ "Object Name",
261
+ "Severity",
262
+ "Description",
263
+ "URL",
264
+ # "Report URL",
265
+ ]
266
+ ]
267
+ .sort_values(["Category", "Rule Name", "Object Type", "Object Name"])
268
+ .set_index(["Category", "Rule Name"])
269
+ )
270
+
271
+ bpa2 = finalDF.reset_index()
272
+ bpa_dict = {
273
+ cat: bpa2[bpa2["Category"] == cat].drop("Category", axis=1)
274
+ for cat in bpa2["Category"].drop_duplicates().values
275
+ }
276
+
277
+ styles = """
278
+ <style>
279
+ .tab { overflow: hidden; border: 1px solid #ccc; background-color: #f1f1f1; }
280
+ .tab button { background-color: inherit; float: left; border: none; outline: none; cursor: pointer; padding: 14px 16px; transition: 0.3s; }
281
+ .tab button:hover { background-color: #ddd; }
282
+ .tab button.active { background-color: #ccc; }
283
+ .tabcontent { display: none; padding: 6px 12px; border: 1px solid #ccc; border-top: none; }
284
+ .tabcontent.active { display: block; }
285
+ .tooltip { position: relative; display: inline-block; }
286
+ .tooltip .tooltiptext { visibility: hidden; width: 300px; background-color: #555; color: #fff; text-align: center; border-radius: 6px; padding: 5px; position: absolute; z-index: 1; bottom: 125%; left: 50%; margin-left: -110px; opacity: 0; transition: opacity 0.3s; }
287
+ .tooltip:hover .tooltiptext { visibility: visible; opacity: 1; }
288
+ </style>
289
+ """
290
+
291
+ # JavaScript for tab functionality
292
+ script = """
293
+ <script>
294
+ function openTab(evt, tabName) {
295
+ var i, tabcontent, tablinks;
296
+ tabcontent = document.getElementsByClassName("tabcontent");
297
+ for (i = 0; i < tabcontent.length; i++) {
298
+ tabcontent[i].style.display = "none";
299
+ }
300
+ tablinks = document.getElementsByClassName("tablinks");
301
+ for (i = 0; i < tablinks.length; i++) {
302
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
303
+ }
304
+ document.getElementById(tabName).style.display = "block";
305
+ evt.currentTarget.className += " active";
306
+ }
307
+ </script>
308
+ """
309
+
310
+ # HTML for tabs
311
+ tab_html = '<div class="tab">'
312
+ content_html = ""
313
+ for i, (title, df) in enumerate(bpa_dict.items()):
314
+ if df.shape[0] == 0:
315
+ continue
316
+
317
+ tab_id = f"tab{i}"
318
+ active_class = ""
319
+ if i == 0:
320
+ active_class = "active"
321
+
322
+ summary = " + ".join(
323
+ [f"{idx} ({v})" for idx, v in df["Severity"].value_counts().items()]
324
+ )
325
+ tab_html += f'<button class="tablinks {active_class}" onclick="openTab(event, \'{tab_id}\')"><b>{title}</b><br/>{summary}</button>'
326
+ content_html += f'<div id="{tab_id}" class="tabcontent {active_class}">'
327
+
328
+ # Adding tooltip for Rule Name using Description column
329
+ content_html += '<table border="1">'
330
+ content_html += "<tr><th>Rule Name</th><th>Object Type</th><th>Object Name</th><th>Severity</th></tr>"
331
+ for _, row in df.iterrows():
332
+ content_html += "<tr>"
333
+ if pd.notnull(row["URL"]):
334
+ content_html += f'<td class="tooltip" onmouseover="adjustTooltipPosition(event)"><a href="{row["URL"]}">{row["Rule Name"]}</a><span class="tooltiptext">{row["Description"]}</span></td>'
335
+ elif pd.notnull(row["Description"]):
336
+ content_html += f'<td class="tooltip" onmouseover="adjustTooltipPosition(event)">{row["Rule Name"]}<span class="tooltiptext">{row["Description"]}</span></td>'
337
+ else:
338
+ content_html += f'<td>{row["Rule Name"]}</td>'
339
+ content_html += f'<td>{row["Object Type"]}</td>'
340
+ if pd.notnull(row["URL"]):
341
+ content_html += (
342
+ f'<td><a href="{row["URL"]}">{row["Object Name"]}</a></td>'
343
+ )
344
+ else:
345
+ content_html += f'<td>{row["Object Name"]}</td>'
346
+ content_html += f'<td style="text-align: center;">{row["Severity"]}</td>'
347
+ content_html += "</tr>"
348
+ content_html += "</table>"
349
+
350
+ content_html += "</div>"
351
+ tab_html += "</div>"
352
+
353
+ # Display the tabs, tab contents, and run the script
354
+ return display(HTML(styles + tab_html + content_html + script))
@@ -0,0 +1,115 @@
1
+ import pandas as pd
2
+ from sempy._utils._log import log
3
+
4
+
5
+ @log
6
+ def report_bpa_rules() -> pd.DataFrame:
7
+ """
8
+ Shows the default rules for the report BPA used by the run_report_bpa function.
9
+
10
+ Returns
11
+ -------
12
+ pandas.DataFrame
13
+ A pandas dataframe containing the default rules for the run_report_bpa function.
14
+ """
15
+
16
+ rules = pd.DataFrame(
17
+ [
18
+ (
19
+ "Error Prevention",
20
+ "Semantic Model",
21
+ "Error",
22
+ "Fix report objects which reference invalid semantic model objects",
23
+ lambda df: df["Valid Semantic Model Object"] == False,
24
+ "This rule highlights visuals, report filters, page filters or visual filters which reference an invalid semantic model object (i.e Measure/Column/Hierarchy).",
25
+ ),
26
+ (
27
+ "Performance",
28
+ "Custom Visual",
29
+ "Warning",
30
+ "Remove custom visuals which are not used in the report",
31
+ lambda df: df["Used in Report"] == False,
32
+ "Removing unused custom visuals from a report may lead to faster report performance.",
33
+ ),
34
+ (
35
+ "Performance",
36
+ "Page",
37
+ "Warning",
38
+ "Reduce the number of visible visuals on the page",
39
+ lambda df: df["Visible Visual Count"] > 15,
40
+ 'Reducing the number of visable visuals on a page will lead to faster report performance. This rule flags pages with over " + visVisuals + " visible visuals.',
41
+ ),
42
+ (
43
+ "Performance",
44
+ "Visual",
45
+ "Warning",
46
+ "Reduce the number of objects within visuals",
47
+ lambda df: df["Visual Object Count"] > 5,
48
+ "Reducing the number of objects (i.e. measures, columns) which are used in a visual will lead to faster report performance.",
49
+ ),
50
+ (
51
+ "Performance",
52
+ ["Report Filter", "Page Filter", "Visual Filter"],
53
+ "Warning",
54
+ "Reduce usage of filters on measures",
55
+ lambda df: df["Object Type"] == "Measure",
56
+ "Measure filters may cause performance degradation, especially against a large semantic model.",
57
+ ),
58
+ (
59
+ "Performance",
60
+ "Visual",
61
+ "Warning",
62
+ "Avoid setting 'Show items with no data' on columns",
63
+ lambda df: df["Show Items With No Data"],
64
+ "This setting will show all column values for all columns in the visual which may lead to performance degradation.",
65
+ "https://learn.microsoft.com/power-bi/create-reports/desktop-show-items-no-data",
66
+ ),
67
+ (
68
+ "Performance",
69
+ "Page",
70
+ "Warning",
71
+ "Avoid tall report pages with vertical scrolling",
72
+ lambda df: df["Height"] > 720,
73
+ "Report pages are designed to be in a single view and not scroll. Pages with scrolling is an indicator that the page has too many elements.",
74
+ ),
75
+ (
76
+ "Performance",
77
+ "Custom Visual",
78
+ "Info",
79
+ "Reduce usage of custom visuals",
80
+ lambda df: df["Custom Visual Name"] == df["Custom Visual Name"],
81
+ "Using custom visuals may lead to performance degradation.",
82
+ ),
83
+ (
84
+ "Maintenance",
85
+ "Report Level Measure",
86
+ "Info",
87
+ "Move report-level measures into the semantic model.",
88
+ lambda df: df["Measure Name"] == df["Measure Name"],
89
+ "It is a best practice to keep measures defined in the semantic model and not in the report.",
90
+ ),
91
+ (
92
+ "Performance",
93
+ ["Report Filter", "Page Filter", "Visual Filter"],
94
+ "Info",
95
+ "Reduce usage of TopN filtering within visuals",
96
+ lambda df: df["Type"] == "TopN",
97
+ "TopN filtering may cause performance degradation, especially against a high cardinality column.",
98
+ ),
99
+ # ('Performance', 'Custom Visual', 'Warning', "Set 'Edit Interactions' for non-data visuals to 'none'",
100
+ # lambda df: df['Custom Visual Name'] == df['Custom Visual Name'],
101
+ # "Setting 'Edit Interactions' to 'None' for non-data visuals may improve performance (since these visuals do not necessitate interactions between other visuals). 'Edit Interactions' may be found in the 'Format' tab of the ribbon in Power BI Desktop.",
102
+ # )
103
+ ],
104
+ columns=[
105
+ "Category",
106
+ "Scope",
107
+ "Severity",
108
+ "Rule Name",
109
+ "Expression",
110
+ "Description",
111
+ "URL",
112
+ ],
113
+ )
114
+
115
+ return rules