semantic-link-labs 0.7.3__py3-none-any.whl → 0.8.0__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 (75) hide show
  1. {semantic_link_labs-0.7.3.dist-info → semantic_link_labs-0.8.0.dist-info}/METADATA +19 -4
  2. {semantic_link_labs-0.7.3.dist-info → semantic_link_labs-0.8.0.dist-info}/RECORD +75 -50
  3. {semantic_link_labs-0.7.3.dist-info → semantic_link_labs-0.8.0.dist-info}/WHEEL +1 -1
  4. sempy_labs/__init__.py +109 -31
  5. sempy_labs/_bpa_translation/{_translations_am-ET.po → _model/_translations_am-ET.po} +22 -0
  6. sempy_labs/_bpa_translation/{_translations_ar-AE.po → _model/_translations_ar-AE.po} +24 -0
  7. sempy_labs/_bpa_translation/_model/_translations_bg-BG.po +938 -0
  8. sempy_labs/_bpa_translation/_model/_translations_ca-ES.po +934 -0
  9. sempy_labs/_bpa_translation/{_translations_cs-CZ.po → _model/_translations_cs-CZ.po} +179 -157
  10. sempy_labs/_bpa_translation/{_translations_da-DK.po → _model/_translations_da-DK.po} +24 -0
  11. sempy_labs/_bpa_translation/{_translations_de-DE.po → _model/_translations_de-DE.po} +77 -52
  12. sempy_labs/_bpa_translation/{_translations_el-GR.po → _model/_translations_el-GR.po} +25 -0
  13. sempy_labs/_bpa_translation/{_translations_es-ES.po → _model/_translations_es-ES.po} +67 -43
  14. sempy_labs/_bpa_translation/{_translations_fa-IR.po → _model/_translations_fa-IR.po} +24 -0
  15. sempy_labs/_bpa_translation/_model/_translations_fi-FI.po +915 -0
  16. sempy_labs/_bpa_translation/{_translations_fr-FR.po → _model/_translations_fr-FR.po} +83 -57
  17. sempy_labs/_bpa_translation/{_translations_ga-IE.po → _model/_translations_ga-IE.po} +25 -0
  18. sempy_labs/_bpa_translation/{_translations_he-IL.po → _model/_translations_he-IL.po} +23 -0
  19. sempy_labs/_bpa_translation/{_translations_hi-IN.po → _model/_translations_hi-IN.po} +24 -0
  20. sempy_labs/_bpa_translation/{_translations_hu-HU.po → _model/_translations_hu-HU.po} +25 -0
  21. sempy_labs/_bpa_translation/_model/_translations_id-ID.po +918 -0
  22. sempy_labs/_bpa_translation/{_translations_is-IS.po → _model/_translations_is-IS.po} +25 -0
  23. sempy_labs/_bpa_translation/{_translations_it-IT.po → _model/_translations_it-IT.po} +25 -0
  24. sempy_labs/_bpa_translation/{_translations_ja-JP.po → _model/_translations_ja-JP.po} +21 -0
  25. sempy_labs/_bpa_translation/_model/_translations_ko-KR.po +823 -0
  26. sempy_labs/_bpa_translation/_model/_translations_mt-MT.po +937 -0
  27. sempy_labs/_bpa_translation/{_translations_nl-NL.po → _model/_translations_nl-NL.po} +80 -56
  28. sempy_labs/_bpa_translation/{_translations_pl-PL.po → _model/_translations_pl-PL.po} +101 -76
  29. sempy_labs/_bpa_translation/{_translations_pt-BR.po → _model/_translations_pt-BR.po} +25 -0
  30. sempy_labs/_bpa_translation/{_translations_pt-PT.po → _model/_translations_pt-PT.po} +25 -0
  31. sempy_labs/_bpa_translation/_model/_translations_ro-RO.po +939 -0
  32. sempy_labs/_bpa_translation/{_translations_ru-RU.po → _model/_translations_ru-RU.po} +25 -0
  33. sempy_labs/_bpa_translation/_model/_translations_sk-SK.po +925 -0
  34. sempy_labs/_bpa_translation/_model/_translations_sl-SL.po +922 -0
  35. sempy_labs/_bpa_translation/{_translations_ta-IN.po → _model/_translations_ta-IN.po} +26 -0
  36. sempy_labs/_bpa_translation/{_translations_te-IN.po → _model/_translations_te-IN.po} +24 -0
  37. sempy_labs/_bpa_translation/{_translations_th-TH.po → _model/_translations_th-TH.po} +24 -0
  38. sempy_labs/_bpa_translation/_model/_translations_tr-TR.po +925 -0
  39. sempy_labs/_bpa_translation/_model/_translations_uk-UA.po +933 -0
  40. sempy_labs/_bpa_translation/{_translations_zh-CN.po → _model/_translations_zh-CN.po} +116 -97
  41. sempy_labs/_bpa_translation/{_translations_zu-ZA.po → _model/_translations_zu-ZA.po} +25 -0
  42. sempy_labs/_capacities.py +577 -0
  43. sempy_labs/_capacity_migration.py +624 -0
  44. sempy_labs/_clear_cache.py +8 -8
  45. sempy_labs/_connections.py +140 -0
  46. sempy_labs/_environments.py +156 -0
  47. sempy_labs/_git.py +20 -21
  48. sempy_labs/_helper_functions.py +151 -10
  49. sempy_labs/_icons.py +62 -0
  50. sempy_labs/_list_functions.py +232 -887
  51. sempy_labs/_model_bpa.py +8 -32
  52. sempy_labs/_notebooks.py +143 -0
  53. sempy_labs/_query_scale_out.py +30 -8
  54. sempy_labs/_spark.py +460 -0
  55. sempy_labs/_sql.py +88 -19
  56. sempy_labs/_translations.py +3 -0
  57. sempy_labs/_vertipaq.py +162 -99
  58. sempy_labs/_workspaces.py +294 -0
  59. sempy_labs/admin/__init__.py +53 -0
  60. sempy_labs/admin/_basic_functions.py +806 -0
  61. sempy_labs/admin/_domains.py +411 -0
  62. sempy_labs/directlake/_directlake_schema_sync.py +1 -2
  63. sempy_labs/directlake/_generate_shared_expression.py +11 -14
  64. sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +14 -24
  65. sempy_labs/report/__init__.py +9 -6
  66. sempy_labs/report/_report_bpa.py +359 -0
  67. sempy_labs/report/_report_bpa_rules.py +113 -0
  68. sempy_labs/report/_report_helper.py +254 -0
  69. sempy_labs/report/_report_list_functions.py +95 -0
  70. sempy_labs/report/_report_rebind.py +0 -4
  71. sempy_labs/report/_reportwrapper.py +2039 -0
  72. sempy_labs/tom/_model.py +83 -5
  73. {semantic_link_labs-0.7.3.dist-info → semantic_link_labs-0.8.0.dist-info}/LICENSE +0 -0
  74. {semantic_link_labs-0.7.3.dist-info → semantic_link_labs-0.8.0.dist-info}/top_level.txt +0 -0
  75. /sempy_labs/_bpa_translation/{_translations_sv-SE.po → _model/_translations_sv-SE.po} +0 -0
@@ -0,0 +1,359 @@
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
+
18
+
19
+ @log
20
+ def run_report_bpa(
21
+ report: str,
22
+ rules: Optional[pd.DataFrame] = None,
23
+ workspace: Optional[str] = None,
24
+ # language: Optional[str] = None,
25
+ export: Optional[bool] = False,
26
+ return_dataframe: Optional[bool] = False,
27
+ ):
28
+ """
29
+ Displays an HTML visualization of the results of the Best Practice Analyzer scan for a report.
30
+
31
+ Parameters
32
+ ----------
33
+ report : str
34
+ Name of the report.
35
+ rules : pandas.DataFrame, default=None
36
+ A pandas dataframe containing rules to be evaluated.
37
+ workspace : str, default=None
38
+ The Fabric workspace name.
39
+ Defaults to None which resolves to the workspace of the attached lakehouse
40
+ or if no lakehouse attached, resolves to the workspace of the notebook.
41
+ export : bool, default=False
42
+ If True, exports the resulting dataframe to a delta table in the lakehouse attached to the notebook.
43
+ return_dataframe : bool, default=False
44
+ If True, returns a pandas dataframe instead of the visualization.
45
+
46
+ Returns
47
+ -------
48
+ pandas.DataFrame
49
+ A pandas dataframe in HTML format showing report objects which violated the best practice analyzer rules.
50
+ """
51
+
52
+ rpt = ReportWrapper(report=report, workspace=workspace)
53
+ dfCV = rpt.list_custom_visuals()
54
+ dfP = rpt.list_pages()
55
+ dfRF = rpt.list_report_filters()
56
+ dfRF["Filter Object"] = format_dax_object_name(
57
+ dfRF["Table Name"], dfRF["Object Name"]
58
+ )
59
+ dfPF = rpt.list_page_filters()
60
+ dfPF["Filter Object"] = (
61
+ dfPF["Page Display Name"]
62
+ + " : "
63
+ + format_dax_object_name(dfPF["Table Name"], dfPF["Object Name"])
64
+ )
65
+ dfVF = rpt.list_visual_filters()
66
+ dfVF["Filter Object"] = (
67
+ format_dax_object_name(dfVF["Page Display Name"], dfVF["Visual Name"])
68
+ + " : "
69
+ + format_dax_object_name(dfVF["Table Name"], dfVF["Object Name"])
70
+ )
71
+ dfRLM = rpt.list_report_level_measures()
72
+ dfV = rpt.list_visuals()
73
+ dfV["Visual Full Name"] = format_dax_object_name(
74
+ dfV["Page Display Name"], dfV["Visual Name"]
75
+ )
76
+ dfSMO = rpt.list_semantic_model_objects(extended=True)
77
+
78
+ # Translations
79
+ if rules is None:
80
+ rules = report_bpa_rules()
81
+
82
+ scope_to_dataframe = {
83
+ "Custom Visual": (dfCV, ["Custom Visual Display Name"]),
84
+ "Page": (dfP, ["Page Display Name"]),
85
+ "Visual": (dfV, ["Visual Full Name"]),
86
+ "Report Filter": (dfRF, ["Filter Object"]),
87
+ "Page Filter": (dfPF, ["Filter Object"]),
88
+ "Visual Filter": (dfVF, ["Filter Object"]),
89
+ "Report Level Measure": (dfRLM, ["Measure Name"]),
90
+ "Semantic Model": (dfSMO, ["Report Source Object"]),
91
+ }
92
+
93
+ def execute_rule(row):
94
+ scopes = row["Scope"]
95
+
96
+ # support both str and list as scope type
97
+ if isinstance(scopes, str):
98
+ scopes = [scopes]
99
+
100
+ # collect output dataframes
101
+ df_outputs = []
102
+
103
+ for scope in scopes:
104
+ # common fields for each scope
105
+ (df, violation_cols_or_func) = scope_to_dataframe[scope]
106
+
107
+ # execute rule and subset df
108
+ df_violations = df[row["Expression"](df)]
109
+
110
+ # subset the right output columns (e.g. Table Name & Column Name)
111
+ if isinstance(violation_cols_or_func, list):
112
+ violation_func = lambda violations: violations[violation_cols_or_func]
113
+ else:
114
+ violation_func = violation_cols_or_func
115
+
116
+ # build output data frame
117
+ df_output = violation_func(df_violations).copy()
118
+
119
+ df_output.columns = ["Object Name"]
120
+ df_output["Rule Name"] = row["Rule Name"]
121
+ df_output["Category"] = row["Category"]
122
+
123
+ # Handle 'Object Type' based on scope
124
+ if scope == "Semantic Model":
125
+ # Ensure 'Report Source Object' is unique in dfSMO
126
+ dfSMO_unique = dfSMO.drop_duplicates(subset="Report Source Object")
127
+ # Map 'Object Name' to the 'Report Source' column in dfSMO
128
+ df_output["Object Type"] = df_output["Object Name"].map(
129
+ dfSMO_unique.set_index("Report Source Object")["Report Source"]
130
+ )
131
+ else:
132
+ df_output["Object Type"] = scope
133
+
134
+ df_output["Severity"] = row["Severity"]
135
+ df_output["Description"] = row["Description"]
136
+ df_output["URL"] = row["URL"]
137
+ df_output["Report URL"] = rpt._get_web_url()
138
+
139
+ page_mapping_dict = dfP.set_index("Page Display Name")["Page URL"].to_dict()
140
+
141
+ if scope == "Page":
142
+ df_output["Report URL"] = df_output["Object Name"].map(
143
+ page_mapping_dict
144
+ )
145
+ elif scope == "Page Filter":
146
+ df_output["Page Name"] = df_output["Object Name"].str.extract(
147
+ r"(.*?) : '"
148
+ )
149
+ df_output["Report URL"] = df_output["Page Name"].map(page_mapping_dict)
150
+ df_output.drop(columns=["Page Name"], inplace=True)
151
+ elif scope in ["Visual", "Visual Filter"]:
152
+ df_output["Page Name"] = df_output["Object Name"].str.extract(
153
+ r"'(.*?)'\[.*?\]"
154
+ )
155
+ df_output["Report URL"] = df_output["Page Name"].map(page_mapping_dict)
156
+ df_output.drop(columns=["Page Name"], inplace=True)
157
+
158
+ df_outputs.append(df_output)
159
+
160
+ return df_outputs
161
+
162
+ # flatten list of lists
163
+ flatten_dfs = [
164
+ df for dfs in rules.apply(execute_rule, axis=1).tolist() for df in dfs
165
+ ]
166
+
167
+ finalDF = pd.concat(flatten_dfs, ignore_index=True)
168
+ finalDF = finalDF[
169
+ [
170
+ "Category",
171
+ "Rule Name",
172
+ "Object Type",
173
+ "Object Name",
174
+ "Severity",
175
+ "Description",
176
+ "URL",
177
+ "Report URL",
178
+ ]
179
+ ]
180
+
181
+ if return_dataframe:
182
+ return finalDF
183
+
184
+ if export:
185
+ from pyspark.sql import SparkSession
186
+
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
+ lakehouse_id = fabric.get_lakehouse_id()
195
+ lake_workspace = fabric.get_workspace_id()
196
+ lakehouse = resolve_lakehouse_name(
197
+ lakehouse_id=lakehouse_id, workspace=lake_workspace
198
+ )
199
+
200
+ lakeT = get_lakehouse_tables(lakehouse=lakehouse, workspace=lake_workspace)
201
+ lakeT_filt = lakeT[lakeT["Table Name"] == delta_table_name]
202
+
203
+ spark = SparkSession.builder.getOrCreate()
204
+ query = f"SELECT MAX(RunId) FROM {lakehouse}.{delta_table_name}"
205
+
206
+ if len(lakeT_filt) == 0:
207
+ runId = 1
208
+ else:
209
+ dfSpark = spark.sql(query)
210
+ maxRunId = dfSpark.collect()[0][0]
211
+ runId = maxRunId + 1
212
+
213
+ export_df = finalDF.copy()
214
+ capacity_id, capacity_name = resolve_workspace_capacity(workspace=workspace)
215
+ export_df["Capacity Name"] = capacity_name
216
+ export_df["Capacity Id"] = capacity_id
217
+ export_df["Workspace Name"] = workspace
218
+ export_df["Workspace Id"] = fabric.resolve_workspace_id(workspace)
219
+ export_df["Report Name"] = report
220
+ export_df["Report Id"] = resolve_report_id(report, workspace)
221
+ export_df["RunId"] = runId
222
+ export_df["Timestamp"] = now
223
+ export_df["RunId"] = export_df["RunId"].astype(int)
224
+
225
+ export_df = [
226
+ [
227
+ "Capacity Name",
228
+ "Capacity Id",
229
+ "Workspace Name",
230
+ "Workspace Id",
231
+ "Report Name",
232
+ "Report Id",
233
+ "Category",
234
+ "Rule Name",
235
+ "Object Type",
236
+ "Object Name",
237
+ "Severity",
238
+ "Description",
239
+ "URL",
240
+ ]
241
+ ]
242
+ save_as_delta_table(
243
+ dataframe=export_df,
244
+ delta_table_name=delta_table_name,
245
+ write_mode="append",
246
+ merge_schema=True,
247
+ )
248
+
249
+ return
250
+
251
+ finalDF.replace(
252
+ {"Warning": icons.warning, "Error": icons.error, "Info": icons.info},
253
+ inplace=True,
254
+ )
255
+
256
+ pd.set_option("display.max_colwidth", 100)
257
+
258
+ finalDF = (
259
+ finalDF[
260
+ [
261
+ "Category",
262
+ "Rule Name",
263
+ "Object Type",
264
+ "Object Name",
265
+ "Severity",
266
+ "Description",
267
+ "URL",
268
+ "Report URL",
269
+ ]
270
+ ]
271
+ .sort_values(["Category", "Rule Name", "Object Type", "Object Name"])
272
+ .set_index(["Category", "Rule Name"])
273
+ )
274
+
275
+ bpa2 = finalDF.reset_index()
276
+ bpa_dict = {
277
+ cat: bpa2[bpa2["Category"] == cat].drop("Category", axis=1)
278
+ for cat in bpa2["Category"].drop_duplicates().values
279
+ }
280
+
281
+ styles = """
282
+ <style>
283
+ .tab { overflow: hidden; border: 1px solid #ccc; background-color: #f1f1f1; }
284
+ .tab button { background-color: inherit; float: left; border: none; outline: none; cursor: pointer; padding: 14px 16px; transition: 0.3s; }
285
+ .tab button:hover { background-color: #ddd; }
286
+ .tab button.active { background-color: #ccc; }
287
+ .tabcontent { display: none; padding: 6px 12px; border: 1px solid #ccc; border-top: none; }
288
+ .tabcontent.active { display: block; }
289
+ .tooltip { position: relative; display: inline-block; }
290
+ .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; }
291
+ .tooltip:hover .tooltiptext { visibility: visible; opacity: 1; }
292
+ </style>
293
+ """
294
+
295
+ # JavaScript for tab functionality
296
+ script = """
297
+ <script>
298
+ function openTab(evt, tabName) {
299
+ var i, tabcontent, tablinks;
300
+ tabcontent = document.getElementsByClassName("tabcontent");
301
+ for (i = 0; i < tabcontent.length; i++) {
302
+ tabcontent[i].style.display = "none";
303
+ }
304
+ tablinks = document.getElementsByClassName("tablinks");
305
+ for (i = 0; i < tablinks.length; i++) {
306
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
307
+ }
308
+ document.getElementById(tabName).style.display = "block";
309
+ evt.currentTarget.className += " active";
310
+ }
311
+ </script>
312
+ """
313
+
314
+ # HTML for tabs
315
+ tab_html = '<div class="tab">'
316
+ content_html = ""
317
+ for i, (title, df) in enumerate(bpa_dict.items()):
318
+ if df.shape[0] == 0:
319
+ continue
320
+
321
+ tab_id = f"tab{i}"
322
+ active_class = ""
323
+ if i == 0:
324
+ active_class = "active"
325
+
326
+ summary = " + ".join(
327
+ [f"{idx} ({v})" for idx, v in df["Severity"].value_counts().items()]
328
+ )
329
+ tab_html += f'<button class="tablinks {active_class}" onclick="openTab(event, \'{tab_id}\')"><b>{title}</b><br/>{summary}</button>'
330
+ content_html += f'<div id="{tab_id}" class="tabcontent {active_class}">'
331
+
332
+ # Adding tooltip for Rule Name using Description column
333
+ content_html += '<table border="1">'
334
+ content_html += "<tr><th>Rule Name</th><th>Object Type</th><th>Object Name</th><th>Severity</th></tr>"
335
+ for _, row in df.iterrows():
336
+ content_html += "<tr>"
337
+ if pd.notnull(row["URL"]):
338
+ content_html += f'<td class="tooltip" onmouseover="adjustTooltipPosition(event)"><a href="{row["URL"]}">{row["Rule Name"]}</a><span class="tooltiptext">{row["Description"]}</span></td>'
339
+ elif pd.notnull(row["Description"]):
340
+ content_html += f'<td class="tooltip" onmouseover="adjustTooltipPosition(event)">{row["Rule Name"]}<span class="tooltiptext">{row["Description"]}</span></td>'
341
+ else:
342
+ content_html += f'<td>{row["Rule Name"]}</td>'
343
+ content_html += f'<td>{row["Object Type"]}</td>'
344
+ if pd.notnull(row["Report URL"]):
345
+ content_html += (
346
+ f'<td><a href="{row["Report URL"]}">{row["Object Name"]}</a></td>'
347
+ )
348
+ else:
349
+ content_html += f'<td>{row["Object Name"]}</td>'
350
+ # content_html += f'<td>{row["Object Name"]}</td>'
351
+ content_html += f'<td style="text-align: center;">{row["Severity"]}</td>'
352
+ content_html += "</tr>"
353
+ content_html += "</table>"
354
+
355
+ content_html += "</div>"
356
+ tab_html += "</div>"
357
+
358
+ # Display the tabs, tab contents, and run the script
359
+ 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