Programmatically Apply Organizational Theme To Multiple Power BI Reports Using Semantic Link Labs

Sandeep PawarSandeep Pawar
4 min read

Power BI Core Visuals team led by Miguel Myers published a huge update last week : Organizational Themes. It’s been a long standing ask by the users. It allows Power BI admins to manage and distribute centrally stored themes to all developers in the organization. Read the blog post for details. Currently, as the above blog explains, org themes don’t update existing reports automatically, which makes sense. But what if you want to bulk update many published reports with the org themes? The latest version of Semantic Link Labs to the rescue (v 0.11.0) !!!

💡
Before you proceed, please note that Organizational Theme is a Preview feature and is available only to admins. Semantic Link Labs needs the report to be in PBIR format for below function to work. You can read about PBIR, its use cases & limitations here. Also, Semantic Link Labs currently uses an internal API which will be updated when the public API for org themes is available.

Organizational Theme

I have three report themes published in the tenant.

Using Semantic Link Labs, I want to apply one of the themes (the Sales Dark Theme for illustration purposes) to be applied to all reports in a workspace. To show that this works for reports in Pro workspaces as well, I published three reports to a Pro workspace. Using Semantic Link Labs, I will:

  • Install SLL version >= 0.11.0

  • Get the JSON of the Sales Dark Theme from the org themes

  • Get a list of all the reports in the workspace

  • If the report theme is different from the org theme, apply the theme to each report

    • else, skip

The base code is very simple - get org theme, apply the theme:

  •           theme_json = theme.get_org_theme_json(theme='MyTheme')
              with connect_report(report=report, workspace=workspace,readonly=False) as rpt:
                  rpt.set_theme(theme_json=theme_json)
    

    Below, I make it a bit more robust with error handling, catch a few edge cases and do some comparison etc:

%pip install semantic-link-labs --q

from sempy_labs.report import connect_report
from sempy_labs.theme import list_org_themes, get_org_theme_json
import sempy.fabric as fabric
import json

def theme_content(theme_dict):
    # remove the 'name' field for content comparison
    return {k: v for k, v in theme_dict.items() if k != "name"}

def apply_org_theme(workspace, org_theme_name=None):
    """
    For each report in the workspace, applies the org theme if not already set or if only the name is different.

    """
    # get org themes and select the desired one
    org_themes = list_org_themes()
    if org_themes.empty:
        raise ValueError("org themes nt found.")
    if org_theme_name:
        row = org_themes[org_themes["Theme Name"] == org_theme_name]
        if row.empty:
            raise ValueError(f"Org theme '{org_theme_name}' not found.")
        theme_id = row["Theme Id"].iloc[0]
    else:
        theme_id = org_themes["Theme Id"].iloc[0]
        org_theme_name = org_themes["Theme Name"].iloc[0]
    org_theme_json = get_org_theme_json(theme_id)
    if isinstance(org_theme_json, str):
        org_theme_json = json.loads(org_theme_json) #if theme returned is not json, just in case

    org_theme_content = theme_content(org_theme_json)
    org_theme_name_val = org_theme_json.get("name", org_theme_name)

    # all reports in the workspace; can be Fabric or Pro workspace
    reports = fabric.list_reports(workspace=workspace)
    updated = []

    for ix, row in reports.iterrows():
        report_id = row["Id"]
        report_name = row["Name"]
        try:
            with connect_report(report=report_id, workspace=workspace, readonly=False, show_diffs=False) as rpt:
                # get custom theme fallback to base theme.
                try:
                    # custom theme
                    report_theme = rpt.get_theme("customTheme")
                except Exception:
                    try:
                        report_theme = rpt.get_theme("baseTheme")
                    except Exception:
                        report_theme = {}

                if isinstance(report_theme, str):
                    report_theme = json.loads(report_theme)

                report_theme_content = theme_content(report_theme)
                report_theme_name_val = report_theme.get("name", "")

                # compare different scenarios
                # sometime the theme details could be same but name could be different
                content_match = json.dumps(report_theme_content, sort_keys=True) == json.dumps(org_theme_content, sort_keys=True)
                name_match = report_theme_name_val == org_theme_name_val

                if content_match and name_match:
                    print(f"Report '{report_name}' already has the '{org_theme_name}' org theme.")
                    continue
                elif content_match and not name_match:
                    print(f"Report theme details are the same but the theme name is different. Updated the report with the new theme '{org_theme_name}'.")
                    rpt.set_theme(theme_json=org_theme_json)
                    updated.append(report_name)
                else:
                    print(f"Applying org theme '{org_theme_name}' to '{report_name}'...")
                    rpt.set_theme(theme_json=org_theme_json)
                    updated.append(report_name)
        except Exception as err:
            print(f"failed to update '{report_name}': {err}")
    return updated

Example:

There is no API, yet, to update the Org theme but I am sure it will be made available in the future.

Note again that I executed the notebook in a Fabric workspace but the reports are in a Pro workspace ! Michael Kovalsky and other contributors have been adding many new functions to the ReportWrapper class, you should take a look at it for all the possibilities.

💡
If you are a Power BI developer with no prior experience in notebooks and Python, I recommend checking out Kurt Buhler’s new notebook tutorial to get started. Also bookmark these example notebooks.
1
Subscribe to my newsletter

Read articles from Sandeep Pawar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sandeep Pawar
Sandeep Pawar

Microsoft MVP with expertise in data analytics, data science and generative AI using Microsoft data platform.