semantic-link-labs 0.12.1__py3-none-any.whl → 0.12.3__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 (32) hide show
  1. {semantic_link_labs-0.12.1.dist-info → semantic_link_labs-0.12.3.dist-info}/METADATA +4 -2
  2. {semantic_link_labs-0.12.1.dist-info → semantic_link_labs-0.12.3.dist-info}/RECORD +32 -26
  3. sempy_labs/__init__.py +12 -0
  4. sempy_labs/_a_lib_info.py +1 -1
  5. sempy_labs/_data_access_security.py +98 -0
  6. sempy_labs/_data_pipelines.py +23 -9
  7. sempy_labs/_dataflows.py +0 -1
  8. sempy_labs/_deployment_pipelines.py +49 -27
  9. sempy_labs/_eventstreams.py +9 -1
  10. sempy_labs/_generate_semantic_model.py +2 -2
  11. sempy_labs/_get_connection_string.py +84 -0
  12. sempy_labs/_helper_functions.py +17 -1
  13. sempy_labs/_job_scheduler.py +63 -33
  14. sempy_labs/_labels.py +4 -6
  15. sempy_labs/_model_dependencies.py +5 -2
  16. sempy_labs/_semantic_models.py +118 -0
  17. sempy_labs/_sql_endpoints.py +12 -24
  18. sempy_labs/_warehouses.py +1 -1
  19. sempy_labs/admin/__init__.py +6 -0
  20. sempy_labs/admin/_sharing_links.py +110 -0
  21. sempy_labs/graph/__init__.py +16 -0
  22. sempy_labs/graph/_groups.py +157 -2
  23. sempy_labs/graph/_sensitivity_labels.py +81 -0
  24. sempy_labs/graph/_users.py +162 -0
  25. sempy_labs/lakehouse/_shortcuts.py +16 -11
  26. sempy_labs/report/_bpareporttemplate/.pbi/localSettings.json +9 -0
  27. sempy_labs/report/_bpareporttemplate/.platform +11 -0
  28. sempy_labs/report/_reportwrapper.py +53 -6
  29. sempy_labs/tom/_model.py +49 -18
  30. {semantic_link_labs-0.12.1.dist-info → semantic_link_labs-0.12.3.dist-info}/WHEEL +0 -0
  31. {semantic_link_labs-0.12.1.dist-info → semantic_link_labs-0.12.3.dist-info}/licenses/LICENSE +0 -0
  32. {semantic_link_labs-0.12.1.dist-info → semantic_link_labs-0.12.3.dist-info}/top_level.txt +0 -0
@@ -6,16 +6,25 @@ from ._groups import (
6
6
  add_group_owners,
7
7
  resolve_group_id,
8
8
  renew_group,
9
+ create_group,
10
+ delete_group,
11
+ update_group,
9
12
  )
10
13
  from ._users import (
11
14
  resolve_user_id,
12
15
  get_user,
13
16
  list_users,
14
17
  send_mail,
18
+ create_user,
19
+ delete_user,
20
+ update_user,
15
21
  )
16
22
  from ._teams import (
17
23
  list_teams,
18
24
  )
25
+ from ._sensitivity_labels import (
26
+ list_sensitivity_labels,
27
+ )
19
28
 
20
29
  __all__ = [
21
30
  "list_groups",
@@ -30,4 +39,11 @@ __all__ = [
30
39
  "list_users",
31
40
  "send_mail",
32
41
  "list_teams",
42
+ "create_user",
43
+ "create_group",
44
+ "delete_user",
45
+ "delete_group",
46
+ "update_user",
47
+ "update_group",
48
+ "list_sensitivity_labels",
33
49
  ]
@@ -1,6 +1,6 @@
1
1
  import pandas as pd
2
2
  from uuid import UUID
3
- from .._helper_functions import (
3
+ from sempy_labs._helper_functions import (
4
4
  _is_valid_uuid,
5
5
  _base_api,
6
6
  _create_dataframe,
@@ -8,7 +8,7 @@ from .._helper_functions import (
8
8
  )
9
9
  from sempy._utils._log import log
10
10
  import sempy_labs._icons as icons
11
- from typing import List, Literal
11
+ from typing import List, Literal, Optional
12
12
 
13
13
 
14
14
  @log
@@ -424,3 +424,158 @@ def renew_group(group: str | UUID):
424
424
  )
425
425
 
426
426
  print(f"{icons.green_dot} The '{group}' group has been renewed.")
427
+
428
+
429
+ @log
430
+ def create_group(
431
+ display_name: str,
432
+ description: Optional[str] = None,
433
+ mail_enabled: bool = False,
434
+ security_enabled: bool = True,
435
+ mail_nickname: str = None,
436
+ owners: Optional[str | UUID | List[str | UUID]] = None,
437
+ members: Optional[str | UUID | List[str | UUID]] = None,
438
+ ):
439
+ """
440
+ Creates a new group.
441
+
442
+ This is a wrapper function for the following API: `Create group <https://learn.microsoft.com/graph/api/group-post-groups>`_.
443
+
444
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
445
+
446
+ Parameters
447
+ ----------
448
+ display_name : str
449
+ The name of the group.
450
+ description : str, optional
451
+ The description of the group.
452
+ mail_enabled : bool, default=False
453
+ Whether the group is mail-enabled.
454
+ security_enabled : bool, default=True
455
+ Whether the group is security-enabled.
456
+ mail_nickname : str, default=None
457
+ The mail alias for the group.
458
+ owners : str | uuid.UUID | List[str | uuid.UUID], default=None
459
+ The owners of the group.
460
+ members : str | uuid.UUID | List[str | uuid.UUID], default=None
461
+ The members of the group.
462
+ """
463
+ from sempy_labs.graph._users import resolve_user_id
464
+
465
+ payload = {
466
+ "displayName": display_name,
467
+ "description": description,
468
+ "mailEnabled": mail_enabled,
469
+ "securityEnabled": security_enabled,
470
+ "mailNickname": mail_nickname,
471
+ }
472
+
473
+ if owners:
474
+ if isinstance(owners, str):
475
+ owners = [owners]
476
+ user_list = []
477
+ for o in owners:
478
+ user_id = resolve_user_id(o)
479
+ user_list.append(f"https://graph.microsoft.com/v1.0/users/{user_id}")
480
+ payload["owners@odata.bind"] = user_list
481
+ if members:
482
+ if isinstance(members, str):
483
+ members = [members]
484
+ user_list = []
485
+ for m in members:
486
+ user_id = resolve_user_id(m)
487
+ user_list.append(f"https://graph.microsoft.com/v1.0/users/{user_id}")
488
+ payload["members@odata.bind"] = user_list
489
+
490
+ _base_api(
491
+ request="groups",
492
+ client="graph",
493
+ payload=payload,
494
+ method="post",
495
+ status_codes=201,
496
+ )
497
+
498
+ print(f"{icons.green_dot} The '{display_name}' group has been created.")
499
+
500
+
501
+ @log
502
+ def delete_group(group: str | UUID):
503
+ """
504
+ Deletes a group.
505
+
506
+ This is a wrapper function for the following API: `Delete group <https://learn.microsoft.com/graph/api/group-delete>`_.
507
+
508
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
509
+
510
+ Parameters
511
+ ----------
512
+ group : str | uuid.UUID
513
+ The group name or ID.
514
+ """
515
+
516
+ group_id = resolve_group_id(group)
517
+
518
+ _base_api(
519
+ request=f"groups/{group_id}",
520
+ client="graph",
521
+ status_codes=204,
522
+ method="delete",
523
+ )
524
+
525
+ print(f"{icons.green_dot} The '{group}' group has been deleted successfully.")
526
+
527
+
528
+ @log
529
+ def update_group(
530
+ group: str | UUID,
531
+ display_name: Optional[str] = None,
532
+ mail_nickname: Optional[str] = None,
533
+ description: Optional[str] = None,
534
+ security_enabled: Optional[bool] = None,
535
+ ):
536
+ """
537
+ Updates a group's properties.
538
+
539
+ This is a wrapper function for the following API: `Update group <https://learn.microsoft.com/en-us/graph/api/group-update>`_.
540
+
541
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
542
+
543
+ Parameters
544
+ ----------
545
+ group : str | uuid.UUID
546
+ The group name or ID.
547
+ display_name : str, default=None
548
+ The new display name for the group.
549
+ mail_nickname : str, default=None
550
+ The new mail nickname for the group.
551
+ description : str, default=None
552
+ The new description for the group.
553
+ security_enabled : bool, default=None
554
+ Whether the group is security-enabled.
555
+ """
556
+
557
+ group_id = resolve_group_id(group)
558
+
559
+ payload = {}
560
+ if display_name:
561
+ payload["displayName"] = display_name
562
+ if mail_nickname:
563
+ payload["mailNickname"] = mail_nickname
564
+ if description:
565
+ payload["description"] = description
566
+ if security_enabled is not None and isinstance(security_enabled, bool):
567
+ payload["securityEnabled"] = security_enabled
568
+
569
+ if not payload:
570
+ print(f"{icons.info} No properties to update.")
571
+ return
572
+
573
+ _base_api(
574
+ request=f"groups/{group_id}",
575
+ client="graph",
576
+ status_codes=204,
577
+ payload=payload,
578
+ method="patch",
579
+ )
580
+
581
+ print(f"{icons.green_dot} The '{group}' group has been updated successfully.")
@@ -0,0 +1,81 @@
1
+ import pandas as pd
2
+ from uuid import UUID
3
+ from typing import Optional
4
+ from sempy_labs._helper_functions import (
5
+ _base_api,
6
+ _create_dataframe,
7
+ _update_dataframe_datatypes,
8
+ )
9
+ from sempy._utils._log import log
10
+
11
+
12
+ @log
13
+ def list_sensitivity_labels(user: Optional[str | UUID] = None) -> pd.DataFrame:
14
+ """
15
+ Get a list of `sensitivity label <https://learn.microsoft.com/graph/api/resources/security-sensitivitylabel>`_ objects associated with a user or organization.
16
+
17
+ This is a wrapper function for the following API: `List sensitivityLabels https://learn.microsoft.com/graph/api/security-informationprotection-list-sensitivitylabels>`_.
18
+
19
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
20
+
21
+ Parameters
22
+ ----------
23
+ user : str | uuid.UUID
24
+ The user ID or user principal name.
25
+
26
+ Returns
27
+ -------
28
+ pandas.DataFrame
29
+ A pandas dataframe showing a list of `sensitivity label <https://learn.microsoft.com/graph/api/resources/security-sensitivitylabel>`_ objects associated with a user or organization.
30
+ """
31
+ from sempy_labs.graph import resolve_user_id
32
+
33
+ url = "/security/informationProtection/sensitivityLabels"
34
+
35
+ if user is not None:
36
+ user_id = resolve_user_id(user=user)
37
+ url = f"users/{user_id}{url}"
38
+
39
+ result = _base_api(request=url, client="graph").json()
40
+
41
+ columns = {
42
+ "Sensitivity Label Id": "str",
43
+ "Sensitivity Label Name": "str",
44
+ "Description": "str",
45
+ "Color": "str",
46
+ "Sensitivity": "int",
47
+ "Tooltip": "str",
48
+ "Is Active": "bool",
49
+ "Is Appliable": "bool",
50
+ "Has Protection": "bool",
51
+ "Parent Sensitivity Label Id": "str",
52
+ "Parent Sensitivity Label Name": "str",
53
+ }
54
+ df = _create_dataframe(columns=columns)
55
+
56
+ rows = []
57
+ for item in result.get("value", []):
58
+ row = {
59
+ "Sensitivity Label Id": item.get("id"),
60
+ "Sensitivity Label Name": item.get("name"),
61
+ "Description": item.get("description"),
62
+ "Color": item.get("color"),
63
+ "Sensitivity": item.get("sensitivity"),
64
+ "Tooltip": item.get("tooltip"),
65
+ "Is Active": item.get("isActive"),
66
+ "Is Appliable": item.get("isAppliable"),
67
+ "Has Protection": item.get("hasProtection"),
68
+ "Parent Sensitivity Label Id": (
69
+ item.get("parent", {}).get("id") if item.get("parent") else None
70
+ ),
71
+ "Parent Sensitivity Label Name": (
72
+ item.get("parent", {}).get("name") if item.get("parent") else None
73
+ ),
74
+ }
75
+ rows.append(row)
76
+
77
+ if rows:
78
+ df = pd.DataFrame(rows, columns=list(columns.keys()))
79
+ _update_dataframe_datatypes(dataframe=df, column_map=columns)
80
+
81
+ return df
@@ -301,3 +301,165 @@ def send_mail(
301
301
  if attachments:
302
302
  printout += f" with {len(attachments)} attachment(s)"
303
303
  print(f"{printout}.")
304
+
305
+
306
+ @log
307
+ def create_user(
308
+ display_name: str,
309
+ user_principal_name: str,
310
+ mail_nickname: str,
311
+ password: str,
312
+ account_enabled: bool = True,
313
+ force_change_password_next_sign_in: bool = True,
314
+ ):
315
+ """
316
+ Creates a new user.
317
+
318
+ This is a wrapper function for the following API: `Create User <https://learn.microsoft.com/graph/api/user-post-users>`_.
319
+
320
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
321
+
322
+ Parameters
323
+ ----------
324
+ display_name : str
325
+ The name to display in the address book for the user.
326
+ user_principal_name : str
327
+ The user principal name (someuser@contoso.com).
328
+ mail_nickname : str
329
+ The mail alias for the user.
330
+ password : str
331
+ The initial password for the user.
332
+ account_enabled : bool, default=True
333
+ Whether the account is enabled. Default is True.
334
+ force_change_password_next_sign_in : bool, default=True
335
+ Whether the user must change their password on next sign-in. Default is True.
336
+ """
337
+
338
+ payload = {
339
+ "accountEnabled": account_enabled,
340
+ "displayName": display_name,
341
+ "mailNickname": mail_nickname,
342
+ "userPrincipalName": user_principal_name,
343
+ "passwordProfile": {
344
+ "forceChangePasswordNextSignIn": force_change_password_next_sign_in,
345
+ "password": password,
346
+ },
347
+ }
348
+
349
+ _base_api(
350
+ request="users",
351
+ client="graph",
352
+ status_codes=201,
353
+ payload=payload,
354
+ method="post",
355
+ )
356
+
357
+ print(f"{icons.green_dot} The '{display_name}' user has been created successfully.")
358
+
359
+
360
+ @log
361
+ def delete_user(user: str | UUID):
362
+ """
363
+ Deletes a user.
364
+
365
+ This is a wrapper function for the following API: `Delete User <https://learn.microsoft.com/graph/api/user-delete>`_.
366
+
367
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
368
+
369
+ Parameters
370
+ ----------
371
+ user : str | uuid.UUID
372
+ The user name or ID.
373
+ """
374
+
375
+ user_id = resolve_user_id(user)
376
+
377
+ _base_api(
378
+ request=f"users/{user_id}",
379
+ client="graph",
380
+ status_codes=204,
381
+ method="delete",
382
+ )
383
+
384
+ print(f"{icons.green_dot} The '{user}' user has been deleted successfully.")
385
+
386
+
387
+ @log
388
+ def update_user(
389
+ user: str | UUID,
390
+ display_name: Optional[str] = None,
391
+ user_principal_name: Optional[str] = None,
392
+ given_name: Optional[str] = None,
393
+ surname: Optional[str] = None,
394
+ job_title: Optional[str] = None,
395
+ mail_nickname: Optional[str] = None,
396
+ my_site: Optional[str] = None,
397
+ office_location: Optional[str] = None,
398
+ account_enabled: Optional[bool] = None,
399
+ ):
400
+ """
401
+ Updates a user's properties.
402
+
403
+ This is a wrapper function for the following API: `Update user <https://learn.microsoft.com/graph/api/user-update>`_.
404
+
405
+ Service Principal Authentication is required (see `here <https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Service%20Principal.ipynb>`_ for examples).
406
+
407
+ Parameters
408
+ ----------
409
+ user : str | uuid.UUID
410
+ The user name or ID.
411
+ display_name : str, default=None
412
+ The name displayed in the address book for the user.
413
+ user_principal_name : str, default=None
414
+ The user principal name (UPN) of the user.
415
+ given_name : str, default=None
416
+ The given name (first name) of the user.
417
+ surname : str, default=None
418
+ The user's surname (family name or last name).
419
+ job_title : str, default=None
420
+ The user's job title.
421
+ mail_nickname : str, default=None
422
+ The mail alias for the user. This property must be specified when a user is created.
423
+ my_site : str, default=None
424
+ The URL for the user's personal site.
425
+ office_location : str, default=None
426
+ The office location in the user's place of business.
427
+ account_enabled : bool, default=None
428
+ Whether the account is enabled. If None, the property will not be updated.
429
+ """
430
+
431
+ user_id = resolve_user_id(user)
432
+
433
+ payload = {}
434
+ if display_name is not None:
435
+ payload["displayName"] = display_name
436
+ if mail_nickname is not None:
437
+ payload["mailNickname"] = mail_nickname
438
+ if user_principal_name is not None:
439
+ payload["userPrincipalName"] = user_principal_name
440
+ if given_name is not None:
441
+ payload["givenName"] = given_name
442
+ if job_title is not None:
443
+ payload["jobTitle"] = job_title
444
+ if my_site is not None:
445
+ payload["mySite"] = my_site
446
+ if office_location is not None:
447
+ payload["officeLocation"] = office_location
448
+ if surname is not None:
449
+ payload["surname"] = surname
450
+ if account_enabled is not None and isinstance(account_enabled, bool):
451
+ payload["accountEnabled"] = account_enabled
452
+
453
+ if not payload:
454
+ print(f"{icons.info} No properties to update.")
455
+ return
456
+
457
+ _base_api(
458
+ request=f"users/{user_id}",
459
+ client="graph",
460
+ status_codes=204,
461
+ payload=payload,
462
+ method="patch",
463
+ )
464
+
465
+ print(f"{icons.green_dot} The '{user}' user has been updated successfully.")
@@ -396,17 +396,22 @@ def list_shortcuts(
396
396
  # Cache and use it to getitem type and name
397
397
  source_item_type = None
398
398
  source_item_name = None
399
- dfI = source_items_df[
400
- source_items_df["Workspace Id"] == source_workspace_id
401
- ]
402
- if dfI.empty:
403
- dfI = fabric.list_items(workspace=source_workspace_id)
404
- source_items_df = pd.concat([source_items_df, dfI], ignore_index=True)
405
-
406
- dfI_filt = dfI[dfI["Id"] == source_item_id]
407
- if not dfI_filt.empty:
408
- source_item_type = dfI_filt["Type"].iloc[0]
409
- source_item_name = dfI_filt["Display Name"].iloc[0]
399
+ try:
400
+ dfI = source_items_df[
401
+ source_items_df["Workspace Id"] == source_workspace_id
402
+ ]
403
+ if dfI.empty:
404
+ dfI = fabric.list_items(workspace=source_workspace_id)
405
+ source_items_df = pd.concat(
406
+ [source_items_df, dfI], ignore_index=True
407
+ )
408
+
409
+ dfI_filt = dfI[dfI["Id"] == source_item_id]
410
+ if not dfI_filt.empty:
411
+ source_item_type = dfI_filt["Type"].iloc[0]
412
+ source_item_name = dfI_filt["Display Name"].iloc[0]
413
+ except Exception:
414
+ pass
410
415
 
411
416
  rows.append(
412
417
  {
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": "1.0",
3
+ "remoteArtifacts": [
4
+ {
5
+ "reportId": "6a91c344-dba8-4ebf-bedb-e07134f2a204"
6
+ }
7
+ ],
8
+ "securityBindingsSignature": "AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAMAVu7l76YU6Sl11KOiJdgQAAAAACAAAAAAAQZgAAAAEAACAAAAD1Ty+c7tZLT9/Sjguxhn/5ivfLWfUMNtgudyJ3BKzzZgAAAAAOgAAAAAIAACAAAABAyGf+iKMwvmNtcoRczjgebeIm0nlc9SFYTBYv3N7yvVADAADQN3JsIsvJUcKKc9WMo2EhiE21odezpd35tb+yudHwA/RYhypMA3fwiCTwArLefBZQ3vZ7KYh4MjihXS07i9o1XVqxAmDoli83Yhs/Wei+0HIfYOT5HOVHLUEul5x41Yx/7Bdfhc881SK6IoaJogBdwsiJVxPne+niMYqJQA6qLEPyJ33g6ucUxLA40lwdbN2cMWFzRn6tymmicDPwH0hcGPDMWwseAU+OuUeidkneRWhUGs6lkiiXLiO6kmY5RKq+S4FdtR19/e1B6EjAd94zSw+M5jQzYxn4eCZzWYiB+8Zd/jy07lfyLoGwagNqiQzbcNONqQd5w0n+8/+n4zGkBi2UojfRXoGaYDirQeZMTbt3pfPx2PArxsJ8dF0iT634pHiCF1ZFdtY+79JaFLUUG+Yf7JJv8IxuuuF74tAp4NYmuOij4hTDaf8Jafa5IoRVh7ICkwrjJyVQ8dG7I3tr0VvR+toBPG3Zlbm9BijcaBxhh1AINhnRAIkENOnPFQVH7l3Ml7B60H8Tst6ic3ihCCMYjtmN+NNWqFrJKT2trilh5TAxN+ei4H5fPwM9S7zb2bH5jhExcYTtoe7iCzxOvBsoYoFM+7FMjn9R2FATNICktYdbKDo1Of+u4oZ1+RsvBHQBVaMhSCoZ7+K5T5pZayNK3V2UID3wOuLOYvouxXXr4NVFsdgiV2oMuxTWeqmd/4bLxeqe3uTkGFmQU4mumF2YVsNbdO3IcRXhhrCCZ27ffzXBsH+lE3EhusD37Z0dsVbVVlG8AHXCh7Atgd8n73/eSI5mvj36DCOSRBVauItIATIa2FXueKA7vU6lRDYBSX8FCC2qkeN6dWpMoN5uXXEBsb5Yot1Fgrovcyl5lk7rh772Xon4FaIYFHZpklsY3JK5EXp3bF8UOE6ByN1ZucmkGgYRcTT/up/Uc86TLN6env9XXL4FQYPlReiOGWKBLVi9OoXGRLDshspniULtV3EwQ6WsjF2AyQ+WdLj3bbWKzG5Mg9jvANLrjycZAGWskh4X5JDGiv4TiJmnYQ/xPZAKKiowpVIHikLeG76uXFI+bxtpihV9+DaEJy4UxisHQxwuvUsQs38u3SHgpJmT8CNssZl41+T/IJdoQwJFLUAAAACnUQZGV9DvcOyrj8HBpXBVB5PuOQDxLB4HZOevHqCB5dc5z787E93B51QmN7I15fF6GCdWwN5f94gv1er2dtN3"
9
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/fabric/gitIntegration/platformProperties/2.0.0/schema.json",
3
+ "metadata": {
4
+ "type": "Report",
5
+ "displayName": "BPAReport"
6
+ },
7
+ "config": {
8
+ "version": "2.0",
9
+ "logicalId": "a201f2cd-fd25-465f-bfbc-33b151e38b31"
10
+ }
11
+ }
@@ -935,9 +935,6 @@ class ReportWrapper:
935
935
  """
936
936
  Shows a list of all modified `visual interactions <https://learn.microsoft.com/power-bi/create-reports/service-reports-visual-interactions?tabs=powerbi-desktop>`_ used in the report.
937
937
 
938
- Parameters
939
- ----------
940
-
941
938
  Returns
942
939
  -------
943
940
  pandas.DataFrame
@@ -980,6 +977,56 @@ class ReportWrapper:
980
977
 
981
978
  return df
982
979
 
980
+ def list_visual_calculations(self) -> pd.DataFrame:
981
+ """
982
+ Shows a list of all `visual calculations <https://learn.microsoft.com/power-bi/transform-model/desktop-visual-calculations-overview>`_.
983
+
984
+ Returns
985
+ -------
986
+ pandas.DataFrame
987
+ A pandas dataframe containing a list of all visual calculations within the report.
988
+ """
989
+
990
+ self._ensure_pbir()
991
+
992
+ columns = {
993
+ "Page Display Name": "str",
994
+ "Visual Name": "str",
995
+ "Name": "str",
996
+ "Language": "str",
997
+ "Expression": "str",
998
+ }
999
+
1000
+ df = _create_dataframe(columns=columns)
1001
+ visual_mapping = self._visual_page_mapping()
1002
+
1003
+ rows = []
1004
+ for v in self.__all_visuals():
1005
+ path = v.get("path")
1006
+ payload = v.get("payload")
1007
+ page_name = visual_mapping.get(path)[0]
1008
+ page_display_name = visual_mapping.get(path)[1]
1009
+ visual_name = payload.get("name")
1010
+ matches = parse("$..field.NativeVisualCalculation").find(payload)
1011
+ if matches:
1012
+ for match in matches:
1013
+ m = match.value
1014
+ rows.append(
1015
+ {
1016
+ "Page Display Name": page_display_name,
1017
+ "Page Name": page_name,
1018
+ "Visual Name": visual_name,
1019
+ "Name": m.get("Name"),
1020
+ "Language": m.get("Language"),
1021
+ "Expression": m.get("Expression"),
1022
+ }
1023
+ )
1024
+
1025
+ if rows:
1026
+ df = pd.DataFrame(rows, columns=list(columns.keys()))
1027
+
1028
+ return df
1029
+
983
1030
  def list_pages(self) -> pd.DataFrame:
984
1031
  """
985
1032
  Shows a list of all pages in the report.
@@ -1651,7 +1698,7 @@ class ReportWrapper:
1651
1698
  bookmarks = [
1652
1699
  o
1653
1700
  for o in self._report_definition.get("parts")
1654
- if o.get("path").endswith("/bookmark.json")
1701
+ if o.get("path").endswith(".bookmark.json")
1655
1702
  ]
1656
1703
 
1657
1704
  rows = []
@@ -2069,8 +2116,8 @@ class ReportWrapper:
2069
2116
  df = dfCV[dfCV["Used in Report"] == False]
2070
2117
 
2071
2118
  if not df.empty:
2072
- cv_remove = df["Custom Visual Name"].values()
2073
- cv_remove_display = df["Custom Visual Display Name"].values()
2119
+ cv_remove = df["Custom Visual Name"].values
2120
+ cv_remove_display = df["Custom Visual Display Name"].values
2074
2121
  else:
2075
2122
  print(
2076
2123
  f"{icons.red_dot} There are no unnecessary custom visuals in the '{self._report_name}' report within the '{self._workspace_name}' workspace."