pbip-killer
v0.5.0
Published
Audit Power BI PBIP / PBIR reports from the command line or CI/CD, with fully customizable rules.
Maintainers
Readme
pbip-killer
Audit Power BI PBIP / PBIR reports from the command line or CI/CD — and fail the build when a report breaks your team's standards.
This is the report-side sibling of dataflow-killer. Where the
dataflow CLI gates your data layer, this gates your reports: visuals,
accessibility, governance and consistency. It is audit-only — it never
modifies your .pbip.
Status: v1 foundation. Report (PBIR) analysis with a fully declarative, customizable rule layer. The semantic-model (TMDL) side is intentionally out of scope for now.
Why PBIR
When you save a Power BI Desktop project as PBIP with the enhanced report format (PBIR) enabled, the report is stored as small per-page / per-visual JSON files. That makes it git-versionable and lintable at the visual level — which is exactly what this tool needs. Mandate "PBIP + PBIR on" for your team and you unlock CI gating without needing workspace Git integration.
Usage
# From the terminal
npx pbip-killer audit ./MyProject.pbip
npx pbip-killer audit ./MyProject.Report --format=md
npx pbip-killer audit ./repo --config=pbipkiller.config.json --fail-on=warningExit code is 1 when any finding meets the failOn gate — that is what
reddens a pipeline.
Use in GitHub Actions
The action/ wrapper installs the CLI and runs the audit. Drop this in
.github/workflows/report-audit.yml:
name: Report audit
on:
pull_request:
paths: ['**/*.Report/**']
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Jesusveiga/DataflowKiller/pbip/[email protected]
with:
path: . # a .pbip, a *.Report folder, or a repo root
# config: pbipkiller.config.json # optional; auto-detected if omitted
# fail-on: warning # optional; overrides config's failOn
format: md| Input | Default | Notes |
|-----------|---------|-------|
| path | . | .pbip file, *.Report folder, or a repo folder containing one |
| config | — | Path to pbipkiller.config.json (auto-detected in cwd if omitted) |
| fail-on | — | Override the gate; if omitted, the config's failOn wins |
| format | human | human · json · md |
The model rules see
Rules never touch raw PBIR JSON. The parser normalizes it into clean, stable fields, and those field names are the contract for both filtering and message templating:
| Scope | Fields available |
|-----------|------------------|
| report | name, theme, exportDataMode, pageCount, visualCount, bookmarkCount |
| page | name, displayName, hidden, type, width, height, visualCount |
| visual | page, name, type, isCustomVisual, title, hasTitle, altText, hasAltText, hidden, width, height, x, y, tabOrder, fieldCount, measureCount, filterCount |
Configuration — pbipkiller.config.json
Three levels of customization, all in one file:
1. Tune the built-in rules
{
"failOn": "critical",
"rules": {
"PERF001": { "severity": "warning", "max": 12 }, // override a threshold
"A11Y001": { "severity": "critical" }, // raise severity
"CONS001": "off" // disable
}
}Built-in rules:
| Id | Category | What it flags | Config keys | Default |
|-----------|---------------|---------------|-------------|---------|
| PERF001 | Performance | More than max data visuals on a page | max (15) | on |
| PERF002 | Performance | More than max visuals report-wide | max (60) | on |
| PERF003 | Performance | More than max slicers on a page | max (4) | on |
| PERF004 | Performance | A visual binding more than max fields | max (10) | on |
| PERF005 | Performance | Page total field load above max | max (60) | on |
| PERF006 | Performance | A table/matrix with no visual-level filter (Top N) | — | on |
| PERF009 | Performance | More than max pages | max (20) | off |
| DATA001 | Data | A visual with no fields bound (renders blank) | — | on |
| GOV001 | Governance | An uncertified / custom (third-party) visual | — | on |
| GOV002 | Governance | A visual whose type is in your block-list | types ([]) | off |
| GOV003 | Governance | A page still named "Page N" | — | on |
| GOV005 | Governance | A visual reading from a restricted table | entities ([]) | off |
| GOV006 | Governance | End-user data export is enabled | — | on |
| GOV008 | Governance | Theme not in your approved list | allowed ([]) | off |
| CONS001 | Consistency | A hidden page that still carries visuals | — | off |
| CONS002 | Consistency | An empty page (0 visuals) | — | on |
| CONS003 | Consistency | Mixed canvas sizes across the report | — | on |
| CONS004 | Consistency | Data visuals that overlap or sit off-canvas | minOverlapPct (25) | on |
| CONS005 | Consistency | Slicers placed inconsistently across pages | tolerancePx (10) | on |
| CONS010 | Consistency | A bookmark group nothing navigates to | — | on |
| A11Y001 | Accessibility | A data visual with no alt text | — | on |
| A11Y002 | Accessibility | A visual with no explicit tab order | — | off |
| A11Y003 | Accessibility | A decorative shape/image left in the tab order | — | on |
| A11Y005 | Accessibility | A title containing banned jargon/acronyms | terms ([]) | off |
Rules marked "off" need configuration (a list/threshold) to do anything; enable
them by adding their config entry. Rules PERF004/005/006, A11Y003/005,
GOV006 and CONS005 implement official Microsoft Learn guidance (optimization
and accessibility checklists).
2. The gate
"failOn" ∈ critical (default) · warning · optimization · none.
3. Write your own rules — no code
Declare rules as data. They run in CI exactly like a built-in:
{
"customRules": [
{
"id": "team/no-image-visuals",
"title": "No static images as visuals",
"category": "Governance",
"severity": "warning",
"scope": "visual",
"when": { "field": "type", "op": "eq", "value": "image" },
"message": "Visual '{{name}}' on page '{{page}}' is a static image."
},
{
"id": "team/dense-pages",
"category": "Performance",
"severity": "critical",
"scope": "page",
"when": [
{ "field": "visualCount", "op": "gt", "value": 20 },
{ "field": "hidden", "op": "eq", "value": false }
],
"message": "Page '{{displayName}}' has {{visualCount}} visuals — split it."
}
]
}whenis one predicate or an array (AND-ed).- Operators:
eqnegtgteltlteinexistsmissingmatches(regex). messagesupports{{field}}placeholders from the table above.
Roadmap
- ~~Vitest suite pinning fixture findings.~~ ✅
tests/report.test.ts - ~~A GitHub Action wrapper.~~ ✅
action/ - Phase 2: extract a shared
coreengine so dataflow + pbip share one dispatcher. - Phase 3: TMDL semantic-model rules (reuses the M folding engine).
