Post

Using Git Submodules to Distribute a Common Theme to Power BI Reports

Defining a Theme in a Git Submodule that can be injected into Reports in other repos upon deployment

Using Git Submodules to Distribute a Common Theme to Power BI Reports

As of a couple of months ago PBIR has been added to PBIP. This new format brings a bunch of benefits. As a chance to explore the format more I’ve explored the concept for injecting a report Theme from a Donor Report, defined in a Git Submodule, into Recipient Reports.

Recipient

Lets start by creating a Recipient folder. We create our report and save the report in the PBIP format with PBIR enabled. We enable git and commit our changes.

cd Recipient
git init
git add .
git commit -m "init"
+πŸ“ Recipient
+β”œβ”€β”€ πŸ“ recipient.Report
+β”‚    β”œβ”€β”€ πŸ“ .pbi
+β”‚ Β  Β β”œβ”€β”€ πŸ“ definition
+β”‚ Β  Β β”‚   β”œβ”€β”€ πŸ“ pages
+β”‚ Β  Β β”‚   β”œβ”€β”€ πŸ“„report.json
+β”‚ Β  Β β”‚   └── πŸ“„version.json
+β”‚ Β  Β β”œβ”€β”€ πŸ“ StaticResources
+β”‚ Β  Β β”‚   └── πŸ“ SharedResources
+β”‚ Β  Β β”‚       └── πŸ“ BaseThemes
+β”‚ Β  Β β”‚           └── πŸ“„CY24SU06.json
+β”‚ Β  Β β”œβ”€β”€ πŸ“„ .platform
+β”‚ Β   └── πŸ“„ definition.pbir
+β”œβ”€β”€ πŸ“ recipient.SemanticModel
+β”” .gitignore

Donor

Now we’ll create a Donor folder to host our donor report. We create a blank report, define a custom theme, and save the report in the PBIP format with PBIR enabled. I defined a full PBIP here rather than individual files to allow for easy updates via PBI Desktop.

cd Donor
git init
git add .
git commit -m "init"
+πŸ“ Donor
+β”œβ”€β”€ πŸ“ donor.Report
+β”‚    β”œβ”€β”€ πŸ“ .pbi
+β”‚ Β  Β β”œβ”€β”€ πŸ“ definition
+β”‚ Β  Β β”‚   β”œβ”€β”€ πŸ“ pages
+β”‚ Β  Β β”‚   β”œβ”€β”€ πŸ“„report.json
+β”‚ Β  Β β”‚   └── πŸ“„version.json
+β”‚ Β  Β β”œβ”€β”€ πŸ“ StaticResources
+β”‚ Β  Β β”‚   └── πŸ“ RegisteredResources
+β”‚ Β  Β β”‚   β”‚   └── πŸ“„ donorTheme.json
+β”‚ Β  Β β”‚   └── πŸ“ SharedResources
+β”‚ Β  Β β”‚       β”œβ”€β”€ πŸ“ BaseThemes
+β”‚ Β  Β β”‚       β”‚   └── πŸ“„ CY24SU06.json
+β”‚ Β  Β β”‚       └── πŸ“ BaseThemes
+β”‚ Β  Β β”œβ”€β”€ πŸ“„ .platform
+β”‚ Β   └── πŸ“„ definition.pbir
+β”œβ”€β”€ πŸ“ donor.SemanticModel
+β”” .gitignore

I then pushed this repo to GitHub.

git remote add origin https://github.com/EvaluationContext/Donor.git
git branch -M main
git push -u origin main

Git Submodule

We now need to navigate back to our local Recipient folder and add register our remote Donor repo as a submodule.

cd Recipient
git submodule add https://github.com/EvaluationContext/Donor
 πŸ“ Recipient
 β”œβ”€β”€ πŸ“ recipient.Report
 β”œβ”€β”€ πŸ“ recipient.SemanticModel
+β”œβ”€β”€ πŸ“ Donor
+β”‚   β”œβ”€β”€ πŸ“ donor.Report
+β”‚   β”œβ”€β”€ πŸ“ donor.SemanticModel
+β”‚   β”” .gitignore
+β”œ .gitmodules
 β”” .gitignore

Above you can see the Donor repo is nested within Recipient repo, plus a new .gitmodules file. This means the Recipient repo now has access to files in the Donor Repo. The point being any arbitrary number of Recipient repos can access the files defined once in Donor repo.

Script to Donate Theme

We now need to add and update files in the Recipient Report, so that the Donor theme is applied.

Required Changes

In order for the theme to be applied we need to:

  • Copy Donor/Recipient/recipient.Report/StaticResources/RegisteredResources/donorTheme.json to Donor/donor.Report/StaticResources/RegisteredResources/
{
    "name": "donorTheme",
    "textClasses": {
        "label": {
            "color": "#0D9BDD",
            "fontFace": "'Segoe UI Light', wf_segoe-ui_light, helvetica, arial, sans-serif"
        }
    },
    "dataColors": [
        "#BF1212",
        "#B34545",
        "#4B1818",
        "#6B007B",
        "#E044A7",
        "#D9B300",
        "#D63550"
    ]
}
  • Register the custom theme in Donor/donor.Report/definition/report.json
{
    "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/report/1.0.0/schema.json"
    ,"ThemeCollection": {
        "baseTheme": {},
+        "customTheme": {
+            "name": "donorTheme",
+            "reportVersionAtImport": "5.55",
+            "type": "RegisteredResources"
+        }
    },
    ...
    "resourcePackages": [
        {
            "name": "SharedResources",
            ...
        },
+        {
+            "name": "RegisteredResources",
+            "type": "RegisteredResources",
+            "items": [
+                {
+                    "name": "donorTheme.json",
+                    "path": "donorTheme.json",
+                    "type": "CustomTheme"
+                }
+            ]
+        }
    ]
}

Manifest

We have hosted the entire Donor Report, and we might want to define the donation of other visuals assets in the Recipient repo. Therefore we want to create a file to specifies what assets we want to donate. I have a manifest file (Recipient/.deploymentManifest.json) that I am using for deployments, I extended it to add allow configuration of the required operation.

{
    "repo": {},
    "items": {
        "semanticModels" : {},
        "reports": {
            "recipient.report": {
                "path": "recipient.report",
                "addItems": {
                    "path": "Donor/donor.report",
                    "visuals": {},
                    "images": {},
                    "theme": "Recipient/recipient.Report/StaticResources/RegisteredResources/donorTheme.json"
                }
            }
        }
    }
}

We can save this to the repo.

 πŸ“ Recipient
 β”œβ”€β”€ πŸ“ recipient.Report
 β”œβ”€β”€ πŸ“ recipient.SemanticModel
 β”œβ”€β”€ πŸ“ Donor
+β”œ .deploymentManifest.json
 β”œ .gitmodules
 β”” .gitignore

Script

We now need to read .deploymentManifest.json detect if a custom theme is specified and update the definition of the Recipient Report. As a proof of concept I’ll assume there is no custom theme currently applied in the Recipient Report.

I apologize in advance for this Powershell script, I’m sure there is a nicer way of writing this

$deploymentManifest = Get-Content '.deploymentManifest.json' | Out-String | ConvertFrom-Json -AsHashtable

foreach ($recipientReport in $deploymentManifest.items.reports.GetEnumerator()) {
    foreach($donorReport in $recipientReport.Value.addItems.GetEnumerator()) {

        $recipientPath = $recipientReport.Value.path
        $donorPath = $donorReport.Value.path

        Write-Host "Donating Files"
        $theme = $donorReport.Value.theme
        $donorPath = "$pwd/$donorPath/StaticResources/RegisteredResources/$theme"
        $recipientFolderPath = "$pwd/$recipientPath/StaticResources/RegisteredResources"
        $recipientPath = "$recipientFolderPath/$theme"
        if(-Not (Test-Path $recipientFolderPath)) {New-Item -ItemType "directory" -Path $recipientFolderPath}
        Copy-Item -Path $donorPath -Destination $recipientPath

        Write-Host "Registering Files"
        $recipientReportjson = Get-Content -Path "$pwd/$recipientPath/definition/report.json" | ConvertFrom-Json -AsHashtable
        $themeCollection = @{
            name = $theme;
            reportVersionAtImport = "5.55";
            type = "RegisteredResources"
        }

        $resourcePackages = @{
            name  = "RegisteredResources";
            type  = "RegisteredResources";
            items = @(
                @{
                    name = $theme;
                    path = $theme;
                    type = "CustomTheme"
                }
            )
        }

        $recipientReportjson.themeCollection["customTheme"] = $themeCollection
        $recipientReportjson.resourcePackages += $resourcePackages
        $updatedFile = $recipientReportjson | ConvertTo-Json -Depth 10
        Set-Content -Path "$pwd/$recipientPath/definition/report.json" -Value $updatedFile

Running the script results in the following results in the following.

 πŸ“ Recipient
 β”œβ”€β”€ πŸ“ recipient.Report
 β”‚    β”œβ”€β”€ πŸ“ .pbi
 β”‚ Β  Β β”œβ”€β”€ πŸ“ definition
 β”‚ Β  Β β”‚   β”œβ”€β”€ πŸ“ pages
-β”‚ Β  Β β”‚   β”œβ”€β”€ πŸ“„report.json
+β”‚ Β  Β β”‚   β”œβ”€β”€ πŸ“„report.json
 β”‚ Β  Β β”‚   └── πŸ“„version.json
 β”‚ Β  Β β”œβ”€β”€ πŸ“ StaticResources
+β”‚ Β  Β β”‚   └── πŸ“ RegisteredResources
+β”‚ Β  Β β”‚   β”‚   └── πŸ“„ donorTheme.json
 β”‚ Β  Β β”‚   └── πŸ“ SharedResources
 β”‚ Β  Β β”œβ”€β”€ πŸ“„ .platform
 β”‚ Β   └── πŸ“„ definition.pbir
 β”œβ”€β”€ πŸ“ recipient.SemanticModel
 β”œβ”€β”€ πŸ“ Donor
 β”‚   β”œβ”€β”€ πŸ“ donor.Report
 β”‚   β”œβ”€β”€ πŸ“ donor.SemanticModel
 β”‚   β”” .gitignore
 β”œ .deploymentManifest.json
 β”œ .gitmodules
 β”” .gitignore

When we open the file we can see the theme has changed.

Theme application

Conclusion

In regards to resources it would be nice if their presence would register them as to use to avoid having to register them in report.json. Regardless, this pattern could be quite useful in defining a theme, allow propagation of a standard from a single repo to many reports. This version while rough introduces the concept.

This post is licensed under CC BY 4.0 by the author.