Lab 12: Pull request, CI, and Copilot code review
At a glance
Section titled “At a glance”Why this matters
Section titled “Why this matters”Model-as-code work should end in a pull request, not in a local folder. CI proves the model parses, report bindings resolve, scripts pass, and the lab site still builds. Copilot code review adds another reviewer that reads the diff and the repo instructions. You also compare it with the local /review-model-pr prompt so you can see what each surface catches.
Concepts
Section titled “Concepts”- Pull request: the review boundary for semantic model, report, and AI metadata changes. See Branching and pull requests.
- CI: this repo’s tenant-free GitHub Actions workflow. It runs on pull requests without Fabric, Power BI, or secrets. See CI workflows.
- Copilot code review: a paid GitHub Copilot feature that you request from the PR sidebar. It leaves comments like a reviewer, usually as a comment review.
- Local model review: the
/review-model-prprompt with the model-reviewer agent. It can run local validation and focus on Power BI model conventions. - Ruleset review: optional repository settings can request Copilot review automatically on pull requests.
Before you begin
Section titled “Before you begin”- You have your own GitHub copy of the repo from Lab 01.
- You are on the feature branch that contains Labs 07 to 09, usually
feature/ai-ready-names. gh auth statussucceeds, or you know how to open a PR in the GitHub web UI.- You have a paid Copilot plan or org access for Copilot code review. Needs a paid Copilot plan
Step 1. Read what CI and review will check
Section titled “Step 1. Read what CI and review will check”Open the repo review files before you push.
- Open Quick Open: Ctrl+P (macOS: Cmd+P), or Go > Go to File.
- Open
.github/workflows/ci.yml. - Open
.github/pull_request_template.md. - Open
.github/prompts/review-model-pr.prompt.mdand.github/agents/model-reviewer.agent.md. - Open
.github/instructions/tmdl.instructions.mdand.github/instructions/pbir.instructions.md.
The workflow’s real job names are in the file:
jobs: validate: name: Validate (${{ matrix.os }}) strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} timeout-minutes: 20 defaults: run: shell: pwsh steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12"
# The same bootstrap a learner runs. It installs the hashed requirements and runs every # validation step (data, expected, questions, runs, tmdl, bindings, customizations, hygiene). - name: Initialize and validate run: ./scripts/Initialize-DevEnvironment.ps1
- name: pytest run: | $python = $IsWindows ? '.venv/Scripts/python.exe' : '.venv/bin/python' & $python -m pytest tools/python/tests if ($LASTEXITCODE) { exit $LASTEXITCODE }
- name: ruff if: runner.os == 'Linux' run: | .venv/bin/python -m ruff check tools/python if ($LASTEXITCODE) { exit $LASTEXITCODE } .venv/bin/python -m ruff format --check tools/python if ($LASTEXITCODE) { exit $LASTEXITCODE }
- name: Pester run: | $pester = Get-Module -ListAvailable Pester | Sort-Object Version -Descending | Select-Object -First 1 if (-not $pester -or $pester.Version -lt [version]'5.5.0') { Install-Module Pester -MinimumVersion 5.5.0 -Scope CurrentUser -Force -SkipPublisherCheck } Import-Module Pester -MinimumVersion 5.5.0 $config = New-PesterConfiguration $config.Run.Path = 'scripts/tests' $config.Run.Exit = $true $config.Output.Verbosity = 'Detailed' Invoke-Pester -Configuration $config
- name: PSScriptAnalyzer run: | if (-not (Get-Module -ListAvailable PSScriptAnalyzer)) { Install-Module PSScriptAnalyzer -Scope CurrentUser -Force } $findings = Invoke-ScriptAnalyzer -Path scripts -Recurse -Settings ./PSScriptAnalyzerSettings.psd1 $findings | Format-Table -AutoSize if ($findings) { exit 1 }
# Warns instead of failing until the maintainer has pushed the checkpoint tags. - name: Checkpoint tags if: runner.os == 'Linux' run: ./scripts/lab/Test-CheckpointTags.ps1 -AllowMissing
site: name: Lab site build runs-on: ubuntu-latest timeout-minutes: 15 defaults: run: working-directory: site steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: "22" cache: npm cache-dependency-path: site/package-lock.json
- run: npm ci --no-audit --no-fund - run: npm run check - run: npm run build
dependency-review: name: Dependency review if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 with: fail-on-severity: moderateThe model reviewer is stricter about report bindings and lineage than a generic code review:
## What to look for
**Blocking**- Validation or lint failures.- A renamed table or column that the report or a measure still uses by its old name.- A changed `sourceColumn`, `lineageTag`, or Power Query partition that wasn't part of the request.- Bidirectional or many-to-many relationships without a written reason.- Edits under `data/generated/`, `evaluation/expected/`, `*.SemanticModel/Copilot/`, or `.pbi/`.- Anything that looks like real patient, customer, or tenant data.
**Suggestions**- Descriptions that repeat the name instead of saying what the object means.- Measures without a format string or display folder.- DAX that works but is hard to read, for example nested `CALCULATE` where a variable is clearer.- AI instructions that are vague, contradict a measure description, or are longer than they need to be.You should see: CI jobs named Validate (ubuntu-latest), Validate (windows-latest), Lab site build, and Dependency review on pull requests.
Step 2. Run local validation before you push
Section titled “Step 2. Run local validation before you push”pwsh ./scripts/Invoke-Validation.ps1pwsh ./scripts/Test-ReportBindings.ps1pwsh ./scripts/Invoke-Validation.ps1pwsh ./scripts/Test-ReportBindings.ps1You should see: validation passes and report bindings pass. If either fails, fix it before opening a PR. A PR is for review, not for discovering basic parse failures.
Step 3. Push your feature branch to your copy
Section titled “Step 3. Push your feature branch to your copy”Push to origin, which should be your own repo, not upstream.
git remote -vgit branch --show-currentgit push -u origin feature/ai-ready-namesgit remote -vgit branch --show-currentgit push -u origin feature/ai-ready-namesIf your branch has a different name, use the name from git branch --show-current.
You should see: origin points at https://github.com/<your-github-user>/<your-repo>.git, and Git prints a URL for opening a pull request.
Step 4. Open the pull request
Section titled “Step 4. Open the pull request”Use GitHub CLI or the web UI.
gh pr create --fillgh pr create --fillFor the web UI:
- Open your repo on GitHub.com.
- Select Pull requests > New pull request.
- Set base to
mainand compare tofeature/ai-ready-names. - Select Create pull request.
- Fill the template. Paste the Lab 10 comparison output in Business question results if you have it.
The template tells reviewers what evidence to look for:
## What changed
<!-- One or two sentences. What does this PR change and why? -->
## Type of change
- [ ] Semantic model (TMDL) or report (PBIR)- [ ] Synthetic data, questions, or expected results- [ ] Copilot customization (instructions, agents, skills, prompts, hooks, MCP)- [ ] Scripts, CI, or tooling- [ ] Lab site content
## Checks
- [ ] `pwsh ./scripts/Invoke-Validation.ps1` passes locally- [ ] If I renamed model objects, `pwsh ./scripts/Test-ReportBindings.ps1` passes- [ ] If I changed data or questions, I ran `pwsh ./scripts/Update-ExpectedResults.ps1` and committed the results- [ ] No credentials, tenant IDs, workspace IDs, or real customer details- [ ] No `.pbi/localSettings.json`, `.pbi/cache.abf`, or `.pbix` files
## Business question results (model changes only)
<!-- Paste the output of: pwsh ./scripts/Compare-QuestionRuns.ps1 -Before <label> -After <label> -->
## Lab pages (site changes only)
- [ ] Ten sections in order; `npm run check` passes in `site/`- [ ] Code shown with `RepoFile`- [ ] Screenshots follow the checklist in CONTRIBUTING.mdYou should see: a pull request from your feature branch into main, with the template sections filled in and no .pbi or local output files in the file list.
Step 5. Watch the CI checks
Section titled “Step 5. Watch the CI checks”Open the PR’s Checks tab, or use GitHub CLI.
gh pr checks --watchgh pr checks --watch- In the GitHub web UI, open the pull request.
- Select Checks.
- Open Validate (ubuntu-latest) and find Initialize and validate.
- Open Validate (windows-latest) and find Pester plus PSScriptAnalyzer.
- Open Lab site build if you changed site files.
You should see: green checks for Validate (ubuntu-latest), Validate (windows-latest), Lab site build, and Dependency review. The validate job runs TMDL lint, report bindings, expected answers, questions, runs, customization checks, and hygiene through Initialize-DevEnvironment.ps1.
Step 6. Request Copilot code review
Section titled “Step 6. Request Copilot code review”GitHub Docs say Copilot code review is requested from the PR sidebar.
- Open the pull request on GitHub.com.
- In the right sidebar, find Reviewers.
- Next to Copilot, select Request.
- Wait for the review to finish. GitHub Docs say it usually takes less than 30 seconds, but busy PRs can take longer.
- Read each comment. Look for severity labels such as High, Medium, or Low.
- If you push more commits later, use the re-request button next to Copilot in Reviewers.
You should see: a Copilot review on the PR and passing checks from step 5. This satisfies the Lab 12 check when the PR comes from a feature branch, not main or master.
Step 7. Compare with the local model-reviewer prompt
Section titled “Step 7. Compare with the local model-reviewer prompt”Run the local reviewer from VS Code.
- Open Copilot Chat: Ctrl+Alt+I (macOS: Ctrl+Cmd+I), or View > Chat.
- Pick model-reviewer from the agent dropdown.
- Type
/review-model-pr base=mainand press Enter. - Approve the validation commands after reading them.
The prompt is short because the agent file carries the review criteria:
---name: review-model-prdescription: Review semantic model and report changes on this branch before opening a pull request.agent: model-reviewerargument-hint: Base branch (default main)---Review the semantic model and report changes on this branch against `${input:base:main}`.
Run the validation, read the diff under `fabric/`, and return your review in the format from your instructions. Put anything that would break the model, the report, or the lab checks under **Blocking**.Path instructions shape both local and GitHub review. For example, TMDL review should preserve lineageTag, and PBIR review should update every Entity, Property, and queryRef after a rename:
- Indent with tabs, one level per nesting depth. Properties of an object sit one tab deeper than the object line.- Put descriptions on the line directly above the object as `/// text`. Keep them under 200 characters, because Power BI Copilot reads only the first 200.- Quote names that contain spaces or special characters with single quotes: `column 'Store Key'`, `measure 'MoM %'`. In DAX, refer to columns as `'Table Name'[Column Name]` and to measures as `[Measure Name]`.- Never change or remove a `lineageTag`. When you add an object, leave `lineageTag` out; Desktop adds one when it saves.- When you rename a column, keep `sourceColumn` pointing at the CSV header. Rename references in `relationships.tmdl`, measures, sort-by columns, hierarchies, `cultures/en-US.tmdl`, and the report.- Set `summarizeBy: none` on keys, IDs, years, month numbers, and other numbers that must not be added up. Only the columns listed as `additive` in `rules/naming-conventions.yaml` may summarize.- Hide keys and sort helpers with `isHidden`.- Every measure needs a description, a `formatString`, and a `displayFolder` from the list in `rules/naming-conventions.yaml`.- Relationships to Date, Store, and Medication are many-to-one and single direction. Don't add `crossFilteringBehavior: bothDirections`.- Don't edit `expressions.tmdl` unless the task is about the data source. The `DataFolderPath` parameter must stay a placeholder path, never a personal path.- After editing, run `pwsh ./scripts/Invoke-Validation.ps1 -Only tmdl`. Load the `tmdl-authoring` skill for syntax details.- The report is PBIR: one `visual.json` per visual under `definition/pages/<page>/visuals/<visual>/`. Folder names are stable IDs; don't rename them.- A field reference has an `Entity` (table name) and a `Property` (column or measure name). Both must match the semantic model exactly, including spaces and case.- When the model renames a table, column, or measure, update every `Entity`, `Property`, and `queryRef` that uses the old name. Also update `nativeQueryRef` and any `displayName` that shows the old name.- Don't change visual positions, sizes, or formatting unless the task asks for it.- Keep JSON valid and keep the `$schema` line. Two-space indentation, as Desktop writes it.- After editing, run `pwsh ./scripts/Test-ReportBindings.ps1`. The `pbir-rebinding` skill has the full procedure.You should see: a local review with Blocking, Suggestions, Questions, and Validation sections when relevant. Compare it with Copilot’s GitHub review. The local agent is usually better at running repo scripts; GitHub code review is better at PR conversation and inline comments.
Step 8. Break report bindings on purpose
Section titled “Step 8. Break report bindings on purpose”Now create a controlled failure so you can see CI catch it. You will fix it in the next step.
- Open
fabric/ContosoPharmacy.SemanticModel/definition/tables/Store.tmdl. - Rename the
Regioncolumn toSales Region. - Do not update the report PBIR files.
- Commit and push the broken change.
git add fabric/ContosoPharmacy.SemanticModel/definition/tables/Store.tmdlgit commit -m "Test CI report binding failure"git pushgit add fabric/ContosoPharmacy.SemanticModel/definition/tables/Store.tmdlgit commit -m "Test CI report binding failure"git pushYou should see: the PR checks rerun. Validate (ubuntu-latest) or Validate (windows-latest) fails inside the validate job because Test-ReportBindings.ps1 finds report fields that still point at Store[Region].
Step 9. Fix the break and merge
Section titled “Step 9. Fix the break and merge”Restore the column name, push the fix, and wait for checks to turn green again.
git revert --no-edit HEADgit pushgh pr checks --watchgit revert --no-edit HEADgit pushgh pr checks --watchWhen checks pass and you’ve read the review, merge the PR from GitHub.com with Merge pull request, or use:
gh pr merge --merge --delete-branchgh pr merge --merge --delete-branchYou should see: the PR returns to green, the deliberate failure commit is neutralized by the fix commit, and the merged PR records CI plus Copilot review evidence.
Step 10. Optional: enable automatic Copilot review
Section titled “Step 10. Optional: enable automatic Copilot review”If this is your own repo and your plan allows it, configure automatic reviews so future PRs don’t need a manual request.
- On GitHub.com, open your repo.
- Select Settings > Rules > Rulesets.
- Create or edit a ruleset for pull requests into
main. - Add Copilot code review as an automatic review requirement if your UI exposes it.
- If the UI offers Review new pushes, enable it only if you want Copilot to re-review after each push.
You should see: future pull requests to main request Copilot review automatically. If you don’t see the option, your plan or organization policy may not allow it.
Verify your work
Section titled “Verify your work”Run this from the repo root in a PowerShell 7 terminal (Terminal > New Terminal in VS Code):
pwsh ./scripts/lab/Test-LabProgress.ps1 -Lab 12pwsh ./scripts/lab/Test-LabProgress.ps1 -Lab 12Checks marked Windows only print SKIP on macOS. SKIP doesn't count as a failure.
When everything is done you should see:
PASS L12-pull-request A pull request from a feature branch exists with passing checks and a Copilot review
Lab 12: 1 of 1 checks passed.Each FAIL line prints a hint and a link back to the step on this page that fixes it. The command exits with code 0 when every required check passes.
| Check | What it looks at | If it fails |
|---|---|---|
L12-pull-request | A pull request from a feature branch exists with passing checks and a Copilot review | Push your branch, open a PR, and request a review from Copilot. Needs gh signed in. Go to step 6 |
Troubleshooting
Section titled “Troubleshooting”gh pr create opens against the upstream repo
Check git remote -v. origin should be your copy. If origin points at sabajamalian/fabric-powerbi-ai-devops-demo, rename it to upstream, add your copy as origin, and push again. Lab 01 step 3 shows the intended remote setup.
Dependency review is skipped or missing
The dependency-review job runs only on pull requests. If you are looking at a push to main or a local workflow run, it won’t run. The Lab 12 check only needs a PR with green checks and a Copilot review.
Copilot is not shown under Reviewers
Copilot code review needs a paid plan or organization access, and admins can disable it. Check your GitHub Copilot settings and repository policies. If you’re in an organization, ask an owner whether Copilot code review is enabled.
The deliberate break fails a different validation first
If TMDL lint fails before report bindings, restore the file and make a smaller rename: only change the column object name from Region to Sales Region. Don’t change sourceColumn, lineageTag, indentation, or DAX. Then push again.
Copilot review comments look generic
Check that the PR includes the instruction and skill files on the head branch. GitHub Docs say Copilot code review reads custom instructions, agent instructions, and skills from the PR head branch. Also make sure the PR description explains the model changes and includes the question comparison.
Knowledge check
Section titled “Knowledge check”Recap and next
Section titled “Recap and next”You pushed the AI-ready branch, opened a PR, watched the real CI jobs, requested Copilot code review, compared it with the local model-reviewer prompt, and proved CI catches a broken report binding. The next lab moves the same style of scoped work to Copilot cloud agent.
Go further
Section titled “Go further”- Add a short note to the PR description that maps each Lab 09 AI prep file to the business question it helps.
- Re-request Copilot review after the fix commit and compare whether it comments on the deliberate failure history.
- Configure automatic Copilot review with a ruleset in your own copy, then open a tiny documentation PR to test it.
Something wrong or unclear on this page? Open a lab feedback issue. The form is prefilled with this page's name. Nothing is sent until you submit it, and please don't include screenshots of real accounts.