Power BI: Managing report pages
One data model, many reports
Sometimes it is necessary to have many reports that link to a single data model. While there may be good reasons to do so, there is one major difficulty that is presented by working in this mode - that is, updating report pages.
Things that are in code (e.g. Power Query changes, DAX changes, new measures) will propagate through all of the reports once the main data model is published to the workspace (they will even propagate to published Apps without needed to update the App).
However, anything that is "one the page", like visuals, which fields are in the field well, on-page filtering, bookmarks does not. This is relevant when we have a case where the same pages used across multiple reports, such as inthe example below. In this example, the data model .pbip file has all of the pages to be used across the children reports, and these pages are selectively shown in the children reports. Let's call this the "Shared Pages" structure. For the rest of this post, let's assume this is the structure we're working with.
Shared Pages
Main data model |
---|
Home Page |
Hours |
Projects |
Employees |
Customers |
Project Manager's Report |
---|
Customers |
Projects <-- |
Hours <-- |
Staff Manager's Report |
---|
Employees |
Projects <-- |
Hours <-- |
Here, we can see the the pages Projects
and Hours
are used in two different reports, and they should be the same across both reports. When a change is made to the Projects
page in the Project Manager's Report, we will need to see that same change appear in the Staff Manager's report as well.
Doing this by hand will take a long time and will be prone to errors and veriations. To get around this, let's try getting the compter to do the work for us.
Exclusive Pages
The file with the main data model is not displayed to the end users. This makes it a good spot to house diagnostic pages for quick troubleshooting (e.g. number of records, overall sums, overall trends).
Approach
We want to solve the following problems:
- How can we make just one change, in one place?
- How can we make that change appear in all of the other places?
One change, one place
The natural place to make changes is in the main data model file. This houses all of the report pages.
Cloning pages
Once the change is made to the master file, the report page needs to be copied multiple times, as many as there are reports. Then, for for each report, we need to show/hide all of the relevant pages for that particular report.
Setup
Get the pages of the reports
In the first instance, every report needs to be set manually. Once the pages have been set, we will generate a file called pages.json
which lists what the page is, whether it's shown or not, and its position in the array determines its order.
For the Project Manager's Report, we want to generate a pages.json
that looks like this.
[
{"pages": "Customers", "show": true},
{"pages": "Projects", "show": true},
{"pages": "Hours", "show": true},
{"pages": "Employees", "show": false}
]
Note that some pages are shown and other are hidden, and that the ordering of the array is the same as the order of pages we want to see.
How I do this is that I manually arrange the .pbip file, save it, and then use some PowerShell to rip out a pages.json
.
Code
using namespace System.Collections.Generic; # for List
function ripPages ($dest, $report) {
[List[PSCustomObject]] $pages = @()
$report.sections |
ForEach-Object {
# the first page does not have an "ordinal" property, so need to manually add it in so we can sort properly in the sort step
if ($null -eq $_.ordinal) {
$_ | Add-Member -NotePropertyName ordinal -NotePropertyValue 0
$_.ordinal = [int] 0
}
# convert the data type to integer - originally text
try { $_.ordinal = [int] $_.ordinal } catch { }
$_
} |
Sort-Object ordinal |
ForEach-Object {
$config = $_.config | ConvertFrom-Json -Depth 20
$pages.Add(
[ordered] @{
page = $_.displayName
# ordinal = $_.ordinal
show = ($config.visibility -eq 0) -or ($config.visibility -eq $null)
}
)
}
$pages | ConvertTo-Json > "$dest\pages.json" -Compress
Set the pages
Every .pbip project has a file called report.json
that has all of the visual information required to generate the on-screen reports. This file includes what pages are shown and hidden, and what order they are in. We will use pages.json
to programmatically set reports.json
to suit out requirements.
Code
function pbi-setPages ($path) {
$project = $path.split('\')[-1]
$reportFile = ".\$project\$project.Report\report.json"
$report = Get-Content $reportFile | ConvertFrom-Json -Depth 20
$pagesFile = ".\$project\$project.CodeFiles\pages.json"
if (Test-Path -Path $pagesFile) {
$pages = Get-Content $pagesFile | ConvertFrom-Json
$pages |
ForEach-Object {
$_ | Add-Member -NotePropertyName ordinal -NotePropertyValue $pages.IndexOf($_)
$_
}
$show = ($pages | Where-Object { $_.show }).page
$report.sections |
ForEach-Object {
# set hide/show
$config = $_.config | ConvertFrom-Json -Depth 20
if ($_.displayName -in $show) {
Write-Host $_.displayName
# 0 is show, 1 is hide
if ($config.visibility -eq $null) {
$config | Add-Member -NotePropertyName visibility -NotePropertyValue ""
}
$config.visibility = 0
}
else {
$config.visibility = 1
}
$_.config = $config | ConvertTo-Json -Depth 20 -Compress
# set page order
$displayName = $_.displayName
$ordinal = ($pages | Where-Object { $_.page -eq $displayName })[0].ordinal
# Write-Host "$displayName $ordinal"
if ($null -eq $_.ordinal) {
$_ | Add-Member -NotePropertyName ordinal -NotePropertyValue $ordinal
}
$_.ordinal = $ordinal
}
$report | convertto-json -Depth 10 > $reportFile
} else {
Write-Host "No pages.json file found. Rip it out first using pbi-rip"
}
}
Propagating
What I do is the following:
- Make changes in main data model
- Publish to the workspace
- Download the file as just a report (linked to data)
- Save-As the file as each of the children report
- Run
pbi-setPages
on all of them
Can't do report-level measures
One thnig we haven't addressed is the fact that you can have measures in the report, but not the main data model. Sometimes it makes sense to do this, but in the Shared Pages structure, you can't have report-level measures, because they will be overwritten every time.
Report-level measures are only really viable if you have an Exclusive Pages structure, where the report pages will never be overwritten by another file.