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
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
toDonor/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.
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.