semantic-link-labs 0.7.4__py3-none-any.whl → 0.8.1__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.

Potentially problematic release.


This version of semantic-link-labs might be problematic. Click here for more details.

Files changed (59) hide show
  1. {semantic_link_labs-0.7.4.dist-info → semantic_link_labs-0.8.1.dist-info}/METADATA +43 -7
  2. {semantic_link_labs-0.7.4.dist-info → semantic_link_labs-0.8.1.dist-info}/RECORD +59 -40
  3. {semantic_link_labs-0.7.4.dist-info → semantic_link_labs-0.8.1.dist-info}/WHEEL +1 -1
  4. sempy_labs/__init__.py +116 -58
  5. sempy_labs/_ai.py +0 -2
  6. sempy_labs/_capacities.py +39 -3
  7. sempy_labs/_capacity_migration.py +623 -0
  8. sempy_labs/_clear_cache.py +8 -8
  9. sempy_labs/_connections.py +15 -13
  10. sempy_labs/_data_pipelines.py +118 -0
  11. sempy_labs/_documentation.py +144 -0
  12. sempy_labs/_eventhouses.py +118 -0
  13. sempy_labs/_eventstreams.py +118 -0
  14. sempy_labs/_generate_semantic_model.py +3 -3
  15. sempy_labs/_git.py +23 -24
  16. sempy_labs/_helper_functions.py +140 -47
  17. sempy_labs/_icons.py +40 -0
  18. sempy_labs/_kql_databases.py +134 -0
  19. sempy_labs/_kql_querysets.py +124 -0
  20. sempy_labs/_list_functions.py +218 -421
  21. sempy_labs/_mirrored_warehouses.py +50 -0
  22. sempy_labs/_ml_experiments.py +122 -0
  23. sempy_labs/_ml_models.py +120 -0
  24. sempy_labs/_model_auto_build.py +0 -4
  25. sempy_labs/_model_bpa.py +10 -12
  26. sempy_labs/_model_bpa_bulk.py +8 -7
  27. sempy_labs/_model_dependencies.py +26 -18
  28. sempy_labs/_notebooks.py +5 -16
  29. sempy_labs/_query_scale_out.py +6 -5
  30. sempy_labs/_refresh_semantic_model.py +7 -19
  31. sempy_labs/_spark.py +40 -45
  32. sempy_labs/_sql.py +60 -15
  33. sempy_labs/_vertipaq.py +25 -25
  34. sempy_labs/_warehouses.py +132 -0
  35. sempy_labs/_workspaces.py +0 -3
  36. sempy_labs/admin/__init__.py +53 -0
  37. sempy_labs/admin/_basic_functions.py +888 -0
  38. sempy_labs/admin/_domains.py +411 -0
  39. sempy_labs/directlake/_directlake_schema_sync.py +1 -1
  40. sempy_labs/directlake/_dl_helper.py +32 -16
  41. sempy_labs/directlake/_generate_shared_expression.py +11 -14
  42. sempy_labs/directlake/_guardrails.py +7 -7
  43. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +14 -24
  44. sempy_labs/directlake/_update_directlake_partition_entity.py +1 -1
  45. sempy_labs/directlake/_warm_cache.py +1 -1
  46. sempy_labs/lakehouse/_get_lakehouse_tables.py +3 -3
  47. sempy_labs/lakehouse/_lakehouse.py +3 -2
  48. sempy_labs/migration/_migrate_calctables_to_lakehouse.py +5 -0
  49. sempy_labs/report/__init__.py +9 -6
  50. sempy_labs/report/_generate_report.py +1 -1
  51. sempy_labs/report/_report_bpa.py +369 -0
  52. sempy_labs/report/_report_bpa_rules.py +113 -0
  53. sempy_labs/report/_report_helper.py +254 -0
  54. sempy_labs/report/_report_list_functions.py +95 -0
  55. sempy_labs/report/_report_rebind.py +0 -4
  56. sempy_labs/report/_reportwrapper.py +2037 -0
  57. sempy_labs/tom/_model.py +333 -22
  58. {semantic_link_labs-0.7.4.dist-info → semantic_link_labs-0.8.1.dist-info}/LICENSE +0 -0
  59. {semantic_link_labs-0.7.4.dist-info → semantic_link_labs-0.8.1.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,7 @@
1
1
  import sempy.fabric as fabric
2
2
  from tqdm.auto import tqdm
3
- from pyspark.sql import SparkSession
4
3
  from sempy_labs._helper_functions import resolve_lakehouse_name
5
4
  from typing import List, Optional, Union
6
- import sempy_labs._icons as icons
7
5
  from sempy._utils._log import log
8
6
 
9
7
 
@@ -16,6 +14,7 @@ def lakehouse_attached() -> bool:
16
14
  bool
17
15
  Returns True if a lakehouse is attached to the notebook.
18
16
  """
17
+ from pyspark.sql import SparkSession
19
18
 
20
19
  spark = SparkSession.builder.getOrCreate()
21
20
  lakeId = spark.conf.get("trident.lakehouse.id")
@@ -49,6 +48,7 @@ def optimize_lakehouse_tables(
49
48
  or if no lakehouse attached, resolves to the workspace of the notebook.
50
49
  """
51
50
 
51
+ from pyspark.sql import SparkSession
52
52
  from sempy_labs.lakehouse._get_lakehouse_tables import get_lakehouse_tables
53
53
  from delta import DeltaTable
54
54
 
@@ -107,6 +107,7 @@ def vacuum_lakehouse_tables(
107
107
  The default retention period is 168 hours (7 days) unless manually configured via table properties.
108
108
  """
109
109
 
110
+ from pyspark.sql import SparkSession
110
111
  from sempy_labs.lakehouse._get_lakehouse_tables import get_lakehouse_tables
111
112
  from delta import DeltaTable
112
113
 
@@ -8,6 +8,7 @@ from sempy_labs._helper_functions import (
8
8
  resolve_lakehouse_id,
9
9
  create_abfss_path,
10
10
  retry,
11
+ generate_guid,
11
12
  )
12
13
  from sempy_labs.tom import connect_semantic_model
13
14
  from pyspark.sql import SparkSession
@@ -343,6 +344,8 @@ def migrate_field_parameters(
343
344
 
344
345
  tbl = TOM.Table()
345
346
  tbl.Name = tName
347
+ tbl.LineageTag = generate_guid()
348
+ tbl.SourceLineageTag = generate_guid()
346
349
  tbl.Partitions.Add(par)
347
350
 
348
351
  columns = ["Value1", "Value2", "Value3"]
@@ -352,6 +355,8 @@ def migrate_field_parameters(
352
355
  col.Name = colName
353
356
  col.SourceColumn = "[" + colName + "]"
354
357
  col.DataType = TOM.DataType.String
358
+ col.LineageTag = generate_guid()
359
+ col.SourceLineageTag = generate_guid()
355
360
 
356
361
  tbl.Columns.Add(col)
357
362
 
@@ -1,3 +1,7 @@
1
+ from sempy_labs.report._reportwrapper import (
2
+ ReportWrapper,
3
+ )
4
+
1
5
  from sempy_labs.report._generate_report import (
2
6
  create_report_from_reportjson,
3
7
  get_report_definition,
@@ -10,15 +14,14 @@ from sempy_labs.report._report_functions import (
10
14
  export_report,
11
15
  clone_report,
12
16
  launch_report,
13
- # list_report_pages,
14
- # list_report_visuals,
15
- # list_report_bookmarks,
16
17
  # translate_report_titles
17
18
  )
18
19
  from sempy_labs.report._report_rebind import (
19
20
  report_rebind,
20
21
  report_rebind_all,
21
22
  )
23
+ from sempy_labs.report._report_bpa_rules import report_bpa_rules
24
+ from sempy_labs.report._report_bpa import run_report_bpa
22
25
 
23
26
  __all__ = [
24
27
  "create_report_from_reportjson",
@@ -28,12 +31,12 @@ __all__ = [
28
31
  "export_report",
29
32
  "clone_report",
30
33
  "launch_report",
31
- # list_report_pages,
32
- # list_report_visuals,
33
- # list_report_bookmarks,
34
34
  # translate_report_titles,
35
35
  "report_rebind",
36
36
  "report_rebind_all",
37
37
  "get_report_definition",
38
38
  "create_model_bpa_report",
39
+ "ReportWrapper",
40
+ "report_bpa_rules",
41
+ "run_report_bpa",
39
42
  ]
@@ -313,7 +313,7 @@ def _create_report(
313
313
  dataset: str,
314
314
  dataset_workspace: Optional[str] = None,
315
315
  report_workspace: Optional[str] = None,
316
- update_if_exists: Optional[bool] = False,
316
+ update_if_exists: bool = False,
317
317
  ):
318
318
 
319
319
  from sempy_labs.report import report_rebind
@@ -0,0 +1,369 @@
1
+ import sempy.fabric as fabric
2
+ from typing import Optional
3
+ import pandas as pd
4
+ import datetime
5
+ from sempy._utils._log import log
6
+ from sempy_labs.report import ReportWrapper, report_bpa_rules
7
+ from sempy_labs._helper_functions import (
8
+ format_dax_object_name,
9
+ save_as_delta_table,
10
+ resolve_report_id,
11
+ resolve_lakehouse_name,
12
+ resolve_workspace_capacity,
13
+ )
14
+ from sempy_labs.lakehouse import get_lakehouse_tables, lakehouse_attached
15
+ import sempy_labs._icons as icons
16
+ from IPython.display import display, HTML
17
+ import sempy_labs.report._report_helper as helper
18
+
19
+
20
+ @log
21
+ def run_report_bpa(
22
+ report: str,
23
+ rules: Optional[pd.DataFrame] = None,
24
+ workspace: Optional[str] = None,
25
+ # language: Optional[str] = None,
26
+ export: bool = False,
27
+ return_dataframe: bool = False,
28
+ ):
29
+ """
30
+ Displays an HTML visualization of the results of the Best Practice Analyzer scan for a report.
31
+
32
+ Parameters
33
+ ----------
34
+ report : str
35
+ Name of the report.
36
+ rules : pandas.DataFrame, default=None
37
+ A pandas dataframe containing rules to be evaluated.
38
+ workspace : str, default=None
39
+ The Fabric workspace name.
40
+ Defaults to None which resolves to the workspace of the attached lakehouse
41
+ or if no lakehouse attached, resolves to the workspace of the notebook.
42
+ export : bool, default=False
43
+ If True, exports the resulting dataframe to a delta table in the lakehouse attached to the notebook.
44
+ return_dataframe : bool, default=False
45
+ If True, returns a pandas dataframe instead of the visualization.
46
+
47
+ Returns
48
+ -------
49
+ pandas.DataFrame
50
+ A pandas dataframe in HTML format showing report objects which violated the best practice analyzer rules.
51
+ """
52
+
53
+ rpt = ReportWrapper(report=report, workspace=workspace)
54
+
55
+ dfCV = rpt.list_custom_visuals()
56
+ dfP = rpt.list_pages()
57
+ dfRF = rpt.list_report_filters()
58
+ dfRF["Filter Object"] = format_dax_object_name(
59
+ dfRF["Table Name"], dfRF["Object Name"]
60
+ )
61
+ dfPF = rpt.list_page_filters()
62
+ # Convert back to dataframe
63
+ # if isinstance(dfPF, pd.io.formats.style.Styler):
64
+ # dfPF = dfPF.data
65
+ # if isinstance(dfP, pd.io.formats.style.Styler):
66
+ # dfP = dfP.data
67
+
68
+ dfPF["Filter Object"] = (
69
+ dfPF["Page Display Name"]
70
+ + " : "
71
+ + format_dax_object_name(dfPF["Table Name"], dfPF["Object Name"])
72
+ )
73
+ dfVF = rpt.list_visual_filters()
74
+ dfVF["Filter Object"] = (
75
+ format_dax_object_name(dfVF["Page Display Name"], dfVF["Visual Name"])
76
+ + " : "
77
+ + format_dax_object_name(dfVF["Table Name"], dfVF["Object Name"])
78
+ )
79
+ dfRLM = rpt.list_report_level_measures()
80
+ dfV = rpt.list_visuals()
81
+ dfV["Visual Full Name"] = format_dax_object_name(
82
+ dfV["Page Display Name"], dfV["Visual Name"]
83
+ )
84
+ dfSMO = rpt.list_semantic_model_objects(extended=True)
85
+
86
+ # Translations
87
+ if rules is None:
88
+ rules = report_bpa_rules()
89
+
90
+ scope_to_dataframe = {
91
+ "Custom Visual": (dfCV, ["Custom Visual Display Name"]),
92
+ "Page": (dfP, ["Page Display Name"]),
93
+ "Visual": (dfV, ["Visual Full Name"]),
94
+ "Report Filter": (dfRF, ["Filter Object"]),
95
+ "Page Filter": (dfPF, ["Filter Object"]),
96
+ "Visual Filter": (dfVF, ["Filter Object"]),
97
+ "Report Level Measure": (dfRLM, ["Measure Name"]),
98
+ "Semantic Model": (dfSMO, ["Report Source Object"]),
99
+ }
100
+
101
+ def execute_rule(row):
102
+ scopes = row["Scope"]
103
+
104
+ # support both str and list as scope type
105
+ if isinstance(scopes, str):
106
+ scopes = [scopes]
107
+
108
+ # collect output dataframes
109
+ df_outputs = []
110
+
111
+ for scope in scopes:
112
+ # common fields for each scope
113
+ (df, violation_cols_or_func) = scope_to_dataframe[scope]
114
+
115
+ # execute rule and subset df
116
+ df_violations = df[row["Expression"](df)]
117
+
118
+ # subset the right output columns (e.g. Table Name & Column Name)
119
+ if isinstance(violation_cols_or_func, list):
120
+ violation_func = lambda violations: violations[violation_cols_or_func]
121
+ else:
122
+ violation_func = violation_cols_or_func
123
+
124
+ # build output data frame
125
+ df_output = violation_func(df_violations).copy()
126
+
127
+ df_output.columns = ["Object Name"]
128
+ df_output["Rule Name"] = row["Rule Name"]
129
+ df_output["Category"] = row["Category"]
130
+
131
+ # Handle 'Object Type' based on scope
132
+ if scope == "Semantic Model":
133
+ # Ensure 'Report Source Object' is unique in dfSMO
134
+ dfSMO_unique = dfSMO.drop_duplicates(subset="Report Source Object")
135
+ # Map 'Object Name' to the 'Report Source' column in dfSMO
136
+ df_output["Object Type"] = df_output["Object Name"].map(
137
+ dfSMO_unique.set_index("Report Source Object")["Report Source"]
138
+ )
139
+ else:
140
+ df_output["Object Type"] = scope
141
+
142
+ df_output["Severity"] = row["Severity"]
143
+ df_output["Description"] = row["Description"]
144
+ df_output["URL"] = row["URL"]
145
+ df_output["Report URL"] = helper.get_web_url(
146
+ report=report, workspace=workspace
147
+ )
148
+
149
+ page_mapping_dict = dfP.set_index("Page Display Name")["Page URL"].to_dict()
150
+
151
+ if scope == "Page":
152
+ df_output["Report URL"] = df_output["Object Name"].map(
153
+ page_mapping_dict
154
+ )
155
+ elif scope == "Page Filter":
156
+ df_output["Page Name"] = df_output["Object Name"].str.extract(
157
+ r"(.*?) : '"
158
+ )
159
+ df_output["Report URL"] = df_output["Page Name"].map(page_mapping_dict)
160
+ df_output.drop(columns=["Page Name"], inplace=True)
161
+ elif scope in ["Visual", "Visual Filter"]:
162
+ df_output["Page Name"] = df_output["Object Name"].str.extract(
163
+ r"'(.*?)'\[.*?\]"
164
+ )
165
+ df_output["Report URL"] = df_output["Page Name"].map(page_mapping_dict)
166
+ df_output.drop(columns=["Page Name"], inplace=True)
167
+
168
+ df_outputs.append(df_output)
169
+
170
+ return df_outputs
171
+
172
+ # flatten list of lists
173
+ flatten_dfs = [
174
+ df for dfs in rules.apply(execute_rule, axis=1).tolist() for df in dfs
175
+ ]
176
+
177
+ finalDF = pd.concat(flatten_dfs, ignore_index=True)
178
+ finalDF = finalDF[
179
+ [
180
+ "Category",
181
+ "Rule Name",
182
+ "Object Type",
183
+ "Object Name",
184
+ "Severity",
185
+ "Description",
186
+ "URL",
187
+ "Report URL",
188
+ ]
189
+ ]
190
+
191
+ if return_dataframe:
192
+ return finalDF
193
+
194
+ if export:
195
+ from pyspark.sql import SparkSession
196
+
197
+ if not lakehouse_attached():
198
+ raise ValueError(
199
+ f"{icons.red_dot} In order to export the BPA results, a lakehouse must be attached to the notebook."
200
+ )
201
+
202
+ now = datetime.datetime.now()
203
+ delta_table_name = "reportbparesults"
204
+ lakehouse_id = fabric.get_lakehouse_id()
205
+ lake_workspace = fabric.get_workspace_id()
206
+ lakehouse = resolve_lakehouse_name(
207
+ lakehouse_id=lakehouse_id, workspace=lake_workspace
208
+ )
209
+
210
+ lakeT = get_lakehouse_tables(lakehouse=lakehouse, workspace=lake_workspace)
211
+ lakeT_filt = lakeT[lakeT["Table Name"] == delta_table_name]
212
+
213
+ spark = SparkSession.builder.getOrCreate()
214
+ query = f"SELECT MAX(RunId) FROM {lakehouse}.{delta_table_name}"
215
+
216
+ if len(lakeT_filt) == 0:
217
+ runId = 1
218
+ else:
219
+ dfSpark = spark.sql(query)
220
+ maxRunId = dfSpark.collect()[0][0]
221
+ runId = maxRunId + 1
222
+
223
+ export_df = finalDF.copy()
224
+ capacity_id, capacity_name = resolve_workspace_capacity(workspace=workspace)
225
+ export_df["Capacity Name"] = capacity_name
226
+ export_df["Capacity Id"] = capacity_id
227
+ export_df["Workspace Name"] = workspace
228
+ export_df["Workspace Id"] = fabric.resolve_workspace_id(workspace)
229
+ export_df["Report Name"] = report
230
+ export_df["Report Id"] = resolve_report_id(report, workspace)
231
+ export_df["RunId"] = runId
232
+ export_df["Timestamp"] = now
233
+ export_df["RunId"] = export_df["RunId"].astype(int)
234
+
235
+ export_df = [
236
+ [
237
+ "Capacity Name",
238
+ "Capacity Id",
239
+ "Workspace Name",
240
+ "Workspace Id",
241
+ "Report Name",
242
+ "Report Id",
243
+ "Category",
244
+ "Rule Name",
245
+ "Object Type",
246
+ "Object Name",
247
+ "Severity",
248
+ "Description",
249
+ "URL",
250
+ ]
251
+ ]
252
+ save_as_delta_table(
253
+ dataframe=export_df,
254
+ delta_table_name=delta_table_name,
255
+ write_mode="append",
256
+ merge_schema=True,
257
+ )
258
+
259
+ return
260
+
261
+ finalDF.replace(
262
+ {"Warning": icons.warning, "Error": icons.error, "Info": icons.info},
263
+ inplace=True,
264
+ )
265
+
266
+ pd.set_option("display.max_colwidth", 100)
267
+
268
+ finalDF = (
269
+ finalDF[
270
+ [
271
+ "Category",
272
+ "Rule Name",
273
+ "Object Type",
274
+ "Object Name",
275
+ "Severity",
276
+ "Description",
277
+ "URL",
278
+ "Report URL",
279
+ ]
280
+ ]
281
+ .sort_values(["Category", "Rule Name", "Object Type", "Object Name"])
282
+ .set_index(["Category", "Rule Name"])
283
+ )
284
+
285
+ bpa2 = finalDF.reset_index()
286
+ bpa_dict = {
287
+ cat: bpa2[bpa2["Category"] == cat].drop("Category", axis=1)
288
+ for cat in bpa2["Category"].drop_duplicates().values
289
+ }
290
+
291
+ styles = """
292
+ <style>
293
+ .tab { overflow: hidden; border: 1px solid #ccc; background-color: #f1f1f1; }
294
+ .tab button { background-color: inherit; float: left; border: none; outline: none; cursor: pointer; padding: 14px 16px; transition: 0.3s; }
295
+ .tab button:hover { background-color: #ddd; }
296
+ .tab button.active { background-color: #ccc; }
297
+ .tabcontent { display: none; padding: 6px 12px; border: 1px solid #ccc; border-top: none; }
298
+ .tabcontent.active { display: block; }
299
+ .tooltip { position: relative; display: inline-block; }
300
+ .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; }
301
+ .tooltip:hover .tooltiptext { visibility: visible; opacity: 1; }
302
+ </style>
303
+ """
304
+
305
+ # JavaScript for tab functionality
306
+ script = """
307
+ <script>
308
+ function openTab(evt, tabName) {
309
+ var i, tabcontent, tablinks;
310
+ tabcontent = document.getElementsByClassName("tabcontent");
311
+ for (i = 0; i < tabcontent.length; i++) {
312
+ tabcontent[i].style.display = "none";
313
+ }
314
+ tablinks = document.getElementsByClassName("tablinks");
315
+ for (i = 0; i < tablinks.length; i++) {
316
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
317
+ }
318
+ document.getElementById(tabName).style.display = "block";
319
+ evt.currentTarget.className += " active";
320
+ }
321
+ </script>
322
+ """
323
+
324
+ # HTML for tabs
325
+ tab_html = '<div class="tab">'
326
+ content_html = ""
327
+ for i, (title, df) in enumerate(bpa_dict.items()):
328
+ if df.shape[0] == 0:
329
+ continue
330
+
331
+ tab_id = f"tab{i}"
332
+ active_class = ""
333
+ if i == 0:
334
+ active_class = "active"
335
+
336
+ summary = " + ".join(
337
+ [f"{idx} ({v})" for idx, v in df["Severity"].value_counts().items()]
338
+ )
339
+ tab_html += f'<button class="tablinks {active_class}" onclick="openTab(event, \'{tab_id}\')"><b>{title}</b><br/>{summary}</button>'
340
+ content_html += f'<div id="{tab_id}" class="tabcontent {active_class}">'
341
+
342
+ # Adding tooltip for Rule Name using Description column
343
+ content_html += '<table border="1">'
344
+ content_html += "<tr><th>Rule Name</th><th>Object Type</th><th>Object Name</th><th>Severity</th></tr>"
345
+ for _, row in df.iterrows():
346
+ content_html += "<tr>"
347
+ if pd.notnull(row["URL"]):
348
+ content_html += f'<td class="tooltip" onmouseover="adjustTooltipPosition(event)"><a href="{row["URL"]}">{row["Rule Name"]}</a><span class="tooltiptext">{row["Description"]}</span></td>'
349
+ elif pd.notnull(row["Description"]):
350
+ content_html += f'<td class="tooltip" onmouseover="adjustTooltipPosition(event)">{row["Rule Name"]}<span class="tooltiptext">{row["Description"]}</span></td>'
351
+ else:
352
+ content_html += f'<td>{row["Rule Name"]}</td>'
353
+ content_html += f'<td>{row["Object Type"]}</td>'
354
+ if pd.notnull(row["Report URL"]):
355
+ content_html += (
356
+ f'<td><a href="{row["Report URL"]}">{row["Object Name"]}</a></td>'
357
+ )
358
+ else:
359
+ content_html += f'<td>{row["Object Name"]}</td>'
360
+ # content_html += f'<td>{row["Object Name"]}</td>'
361
+ content_html += f'<td style="text-align: center;">{row["Severity"]}</td>'
362
+ content_html += "</tr>"
363
+ content_html += "</table>"
364
+
365
+ content_html += "</div>"
366
+ tab_html += "</div>"
367
+
368
+ # Display the tabs, tab contents, and run the script
369
+ return display(HTML(styles + tab_html + content_html + script))
@@ -0,0 +1,113 @@
1
+ import pandas as pd
2
+
3
+
4
+ def report_bpa_rules() -> pd.DataFrame:
5
+ """
6
+ Shows the default rules for the report BPA used by the run_report_bpa function.
7
+
8
+ Returns
9
+ -------
10
+ pandas.DataFrame
11
+ A pandas dataframe containing the default rules for the run_report_bpa function.
12
+ """
13
+
14
+ rules = pd.DataFrame(
15
+ [
16
+ (
17
+ "Error Prevention",
18
+ "Semantic Model",
19
+ "Error",
20
+ "Fix report objects which reference invalid semantic model objects",
21
+ lambda df: df["Valid Semantic Model Object"] == False,
22
+ "This rule highlights visuals, report filters, page filters or visual filters which reference an invalid semantic model object (i.e Measure/Column/Hierarchy).",
23
+ ),
24
+ (
25
+ "Performance",
26
+ "Custom Visual",
27
+ "Warning",
28
+ "Remove custom visuals which are not used in the report",
29
+ lambda df: df["Used in Report"] == False,
30
+ "Removing unused custom visuals from a report may lead to faster report performance.",
31
+ ),
32
+ (
33
+ "Performance",
34
+ "Page",
35
+ "Warning",
36
+ "Reduce the number of visible visuals on the page",
37
+ lambda df: df["Visible Visual Count"] > 15,
38
+ 'Reducing the number of visable visuals on a page will lead to faster report performance. This rule flags pages with over " + visVisuals + " visible visuals.',
39
+ ),
40
+ (
41
+ "Performance",
42
+ "Visual",
43
+ "Warning",
44
+ "Reduce the number of objects within visuals",
45
+ lambda df: df["Visual Object Count"] > 5,
46
+ "Reducing the number of objects (i.e. measures, columns) which are used in a visual will lead to faster report performance.",
47
+ ),
48
+ (
49
+ "Performance",
50
+ ["Report Filter", "Page Filter", "Visual Filter"],
51
+ "Warning",
52
+ "Reduce usage of filters on measures",
53
+ lambda df: df["Object Type"] == "Measure",
54
+ "Measure filters may cause performance degradation, especially against a large semantic model.",
55
+ ),
56
+ (
57
+ "Performance",
58
+ "Visual",
59
+ "Warning",
60
+ "Avoid setting 'Show items with no data' on columns",
61
+ lambda df: df["Show Items With No Data"],
62
+ "This setting will show all column values for all columns in the visual which may lead to performance degradation.",
63
+ "https://learn.microsoft.com/power-bi/create-reports/desktop-show-items-no-data",
64
+ ),
65
+ (
66
+ "Performance",
67
+ "Page",
68
+ "Warning",
69
+ "Avoid tall report pages with vertical scrolling",
70
+ lambda df: df["Height"] > 720,
71
+ "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.",
72
+ ),
73
+ (
74
+ "Performance",
75
+ "Custom Visual",
76
+ "Info",
77
+ "Reduce usage of custom visuals",
78
+ lambda df: df["Custom Visual Name"] == df["Custom Visual Name"],
79
+ "Using custom visuals may lead to performance degradation.",
80
+ ),
81
+ (
82
+ "Maintenance",
83
+ "Report Level Measure",
84
+ "Info",
85
+ "Move report-level measures into the semantic model.",
86
+ lambda df: df["Measure Name"] == df["Measure Name"],
87
+ "It is a best practice to keep measures defined in the semantic model and not in the report.",
88
+ ),
89
+ (
90
+ "Performance",
91
+ ["Report Filter", "Page Filter", "Visual Filter"],
92
+ "Info",
93
+ "Reduce usage of TopN filtering within visuals",
94
+ lambda df: df["Type"] == "TopN",
95
+ "TopN filtering may cause performance degradation, especially against a high cardinality column.",
96
+ ),
97
+ # ('Performance', 'Custom Visual', 'Warning', "Set 'Edit Interactions' for non-data visuals to 'none'",
98
+ # lambda df: df['Custom Visual Name'] == df['Custom Visual Name'],
99
+ # "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.",
100
+ # )
101
+ ],
102
+ columns=[
103
+ "Category",
104
+ "Scope",
105
+ "Severity",
106
+ "Rule Name",
107
+ "Expression",
108
+ "Description",
109
+ "URL",
110
+ ],
111
+ )
112
+
113
+ return rules