PHASE 11 OF 14
GitHub Projects & Engineering Metrics
GitHub Projects v2 views and custom fields, built-in workflow automation, milestones vs Projects, YAML issue templates, label taxonomy at scale, DORA metrics via GraphQL, the Insights tab, and where third-party analytics tools add value beyond what GitHub provides natively
GitHub Projects
DORA Metrics
Issue Templates
Labels
Engineering Metrics
Roadmap
11.1
GitHub Projects v2: Board, Table & Roadmap Views
GitHub Projects v2 (introduced 2022) is a first-class project management layer on top of issues and PRs. Unlike the original Projects (which were simple kanban boards), v2 supports multiple views, custom fields, group-by, sort, filter, and GraphQL-based automation — all without leaving GitHub.
📋 Table view
Spreadsheet-like grid. Every item is a row; custom fields are columns. Best for engineering leads doing sprint planning, bulk editing fields across multiple issues, or comparing items by priority and size. Supports multi-sort, filter expressions, and in-line editing.
📌 Board view (Kanban)
Cards grouped into columns by a field (typically Status). Best for developers tracking daily work-in-progress, standups, and visible flow. Drag-and-drop between columns updates the field value. Supports swimlane grouping by a second field (e.g. assignee).
🗺️ Roadmap view
Timeline/Gantt chart. Items are bars on a horizontal timeline. Best for communicating quarterly goals to stakeholders, showing feature scheduling, and spotting schedule conflicts. Requires date fields (start date + target date) or iteration fields on each item.
Creating a project and adding repos
$ gh project create --owner acme --title "Q3 2026 Platform Sprint" --format json
$ gh issue list --repo acme/platform --state open --json number \
--jq '.[].number' \
| xargs -I{} gh project item-add 42 --owner acme --url \
"https://github.com/acme/platform/issues/{}"
$ gh project item-list 42 --owner acme --format json | jq '.items[].content.title'
Choosing the right view per audience
| Audience | Best view | Key fields to show |
| Daily standup (team) | Board (Status columns) | Status, Assignee, Priority |
| Sprint planning (lead) | Table | Priority, Size, Iteration, Assignee, Status |
| Quarterly roadmap (stakeholders) | Roadmap | Target Date, Milestone, Status, Team |
| Backlog grooming | Table (filtered: Status=Backlog) | Priority, Size, Labels, Milestone |
| Bug triage | Table (filtered: Label=bug) | Priority, Severity, Assignee, Area |
11.2
Custom Fields: Status, Priority, Story Points, Iteration & Date
Custom fields are the difference between a Projects board that mirrors what's already in issue labels and one that genuinely supports your workflow. Add fields that your team needs to make decisions — not every field from Jira.
Field types
Single select
Dropdown with predefined options. Best for Status (Todo / In Progress / In Review / Done), Priority (P0 / P1 / P2 / P3), Team. Options can have colours.
Text
Free-text string. Best for Notes, Risk description, Blocked by reference, spec link.
Number
Numeric value. Best for Story Points, Complexity, T-shirt size (mapped 1/2/3/5/8). Used in sum aggregations in Table view.
Date
A specific date. Best for Target Date, Due Date. Required for the Roadmap view timeline. Displayed as calendar picker in the UI.
Iteration
A recurring time-box (sprint). GitHub auto-manages iteration start/end dates. Best for Sprint / Iteration. Supports "current iteration" in filter expressions. Built-in "previous/current/next" iteration filtering.
Recommended minimal field set
Status single-select Todo | In Progress | In Review | Blocked | Done
Priority single-select P0-Critical | P1-High | P2-Medium | P3-Low
Size single-select XS | S | M | L | XL
Sprint iteration 2-week cadence
Target Date date for roadmap view
Team single-select Platform | Payments | Auth | Frontend
Area text links to CODEOWNERS area (e.g. services/payments)
Field expressions in Table view
sprint:@current status:In-Progress
assignee:@me priority:P0,P1
-label:blocked is:open
iteration:@previous status:Done
no:assignee status:Todo
11.3
Workflow Automation in Projects
GitHub Projects v2 has built-in workflow automation (no Actions required) for common status transitions. Configure under Project settings → Workflows.
Built-in automation triggers
| Trigger | Default action | Customise to |
| Item added to project | Set Status = Todo | Set any single-select field; add to a specific iteration |
| Item closed (issue closed / PR merged) | Set Status = Done | Also archive the item after N days |
| Item reopened | Set Status = Todo | Set Priority = P1 (reopened items are often urgent) |
| PR opened for issue | Set Status = In Progress | Auto-assign to PR author |
| PR review requested | Set Status = In Review | — |
| PR review approved | — | Set Status = Ready to Merge |
Advanced automation via Actions + GraphQL
For logic the built-in workflows can't express, use a GitHub Actions workflow with the GraphQL API to update project fields:
on:
issues:
types: [labeled]
jobs:
set-priority:
runs-on: ubuntu-latest
if: github.event.label.name == 'P0-critical'
steps:
- name: Set priority field in project
env:
GH_TOKEN: ${{ secrets.PROJECT_TOKEN }}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
ITEM_ID=$(gh api graphql -f query='
query($id: ID!) {
node(id: $id) {
... on Issue {
projectItems(first: 1) {
nodes { id }
}
}
}
}' -F id="$ISSUE_ID" --jq '.data.node.projectItems.nodes[0].id')
gh api graphql -f query='
mutation($project: ID!, $item: ID!, $field: ID!, $value: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $project
itemId: $item
fieldId: $field
value: { singleSelectOptionId: $value }
}) { projectV2Item { id } }
}' \
-F project="$PROJECT_NODE_ID" \
-F item="$ITEM_ID" \
-F field="$PRIORITY_FIELD_ID" \
-F value="$P0_OPTION_ID"
NODE IDs FOR GRAPHQL
The GraphQL API uses opaque node IDs, not numeric IDs. Get them with
gh api graphql -f query='{ viewer { projectsV2(first:10) { nodes { id title } } } }' for projects, and similar queries for field IDs and option IDs. Run these once and store the IDs in Actions variables — they're stable.
11.4
Milestones vs Projects: When Each Wins
Milestones and Projects are complementary, not alternatives. Understanding what each does best prevents duplication of effort.
| Dimension | Milestones | Projects v2 |
| Scope | Single repo | Cross-repo (issues and PRs from any repo) |
| Progress tracking | Built-in: X of Y issues closed (shown as a progress bar) | Custom: filter by Status=Done, group by Sprint |
| Due date | One due date per milestone | Per-item date fields |
| Filtering | Filter issues by milestone in list view | Rich filter expressions across all custom fields |
| Best for | Tracking a specific release: "All issues that must be done for v2.5.0" | Sprint planning, roadmap, cross-repo initiatives |
| Closing keyword integration | Merging a PR with "Closes #42" closes issue #42 and updates milestone progress | Built-in workflow: closed issue → Status=Done |
Using both together
A practical setup for a team shipping versioned releases:
- Milestone per version —
v2.5.0, v2.6.0. All issues that must be fixed for that release are assigned to the milestone. The milestone progress bar gives a simple "% done for this release" view.
- Project for sprint planning — issues from multiple repos, custom fields for priority and size, iteration tracking. Pull from the milestone's issues into the current sprint. A release ships when the milestone hits 100%.
$ gh api repos/acme/platform/milestones \
--method POST \
--field title="v2.5.0" \
--field due_on="2026-07-31T00:00:00Z" \
--field description="July release — new checkout flow + auth improvements"
$ gh api repos/acme/platform/milestones \
--jq '.[] | {title, due: .due_on, open: .open_issues, closed: .closed_issues}'
11.5
Issue Templates: YAML Front Matter & Structured Forms
YAML issue templates (available since 2021) render as structured forms rather than freetext Markdown — they're harder to skip past and produce consistent, machine-readable data.
YAML template structure
name: Bug Report
description: File a bug report for a confirmed defect
title: "[Bug]: "
labels: [bug, needs-triage]
assignees: []
body:
- type: markdown
attributes:
value: Thanks for taking the time to fill this out. Please provide as much detail as possible.
- type: input
id: summary
attributes:
label: Summary
description: A clear one-line summary of the bug
placeholder: e.g. "Payment form throws 500 when amount is null"
validations:
required: true
- type: dropdown
id: severity
attributes:
label: Severity
options:
- Critical — service down / data loss
- High — core feature broken
- Medium — feature degraded
- Low — cosmetic / minor
validations:
required: true
- type: dropdown
id: area
attributes:
label: Affected area
multiple: false
options: [Payments, Auth, Notifications, API, Frontend, Infrastructure]
- type: textarea
id: repro
attributes:
label: Steps to reproduce
description: Numbered steps. Include environment, version, and any relevant config.
render: markdown
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behaviour
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual behaviour
description: Include any error messages, stack traces, or screenshots
- type: checkboxes
id: checklist
attributes:
label: Pre-submission checklist
options:
- label: I have searched existing issues and this is not a duplicate
required: true
- label: I have reproduced this on the latest version
Feature request template
name: Feature Request
description: Propose a new feature or improvement
title: "[Feature]: "
labels: [feature, needs-triage]
body:
- type: textarea
id: problem
attributes:
label: What problem does this solve?
description: Describe the job-to-be-done. "As a [role], I need to [do X] so that [outcome Y]."
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed solution
description: How you envision this working. Wireframes, API sketches, or pseudocode welcome.
- type: dropdown
id: impact
attributes:
label: Business impact
options: [High — affects many users, Medium — affects some users, Low — nice to have]
validations:
required: true
11.6
Labels at Scale: Taxonomy & Cross-Repo Sync
Labels are only useful if they're consistent. Inconsistency across repos — different names for the same concept, missing labels, conflicting colours — makes cross-repo filtering and automation unreliable.
Recommended label taxonomy
bug #d73a4a A confirmed defect
feature #0075ca New functionality
enhancement #a2eeef Improvement to existing feature
refactor #e4e669 Code restructuring, no behaviour change
docs #0075ca Documentation only
chore #ffffff Build, CI, dependency update
security #d73a4a Security fix or vulnerability
priority/critical #b60205 Service down / data loss
priority/high #d93f0b Impacts core workflows
priority/medium #fbca04 Should be in next sprint
priority/low #0e8a16 Nice to have
needs-triage #ededed Not yet reviewed
needs-info #d4c5f9 Waiting on reporter
blocked #fef2c0 Blocked by external dependency
wontfix #ffffff Intentionally not addressing
duplicate #cfd3d7 Duplicate of another issue
area/payments #bfd4f2 Payments service
area/auth #bfd4f2 Authentication
area/frontend #bfd4f2 UI / frontend
area/infra #bfd4f2 Infrastructure / platform
size/xs #c5def5 < 1 day
size/s #c5def5 1–2 days
size/m #c5def5 3–5 days
size/l #c5def5 1–2 weeks
size/xl #c5def5 Needs breaking down
Syncing labels across repos with EndBug/label-sync
- name: bug
color: d73a4a
description: A confirmed defect
- name: feature
color: 0075ca
description: New functionality
on:
push:
paths: ['.github/labels.yml']
schedule:
- cron: '0 4 * * 1'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EndBug/label-sync@v2
with:
config-file: .github/labels.yml
delete-other-labels: true
source-repo: acme/.github
token: ${{ secrets.ORG_PAT }}
11.7
Engineering Metrics: DORA via GraphQL & Cycle Time
The DORA (DevOps Research and Assessment) four metrics are the most research-backed measures of engineering team performance. GitHub's GraphQL API provides the raw data to compute proxies for all four.
1. Deployment Frequency
How often does your team deploy to production?
Elite: Multiple deploys/day Low: Monthly or less
GitHub proxy: Count of merged PRs to main per day, or count of GitHub Deployments created with environment=production per week.
2. Lead Time for Changes
How long from first commit to running in production?
Elite: <1 hour Low: 1–6 months
GitHub proxy: Time from first commit in a PR to PR merge (cycle time). Add deployment lag if you track GitHub Deployments.
3. Change Failure Rate
What percentage of deployments cause a production incident?
Elite: 0–15% Low: >45%
GitHub proxy: Ratio of PRs labelled hotfix or incident to total merged PRs. Imperfect — incidents logged in PagerDuty/Linear are more accurate.
4. Mean Time to Restore (MTTR)
How long to recover from a production incident?
Elite: <1 hour Low: >1 week
GitHub proxy: Time from an issue labelled incident being opened to it being closed. Better tracked in your incident management tool.
Extracting cycle time via GraphQL
gh api graphql -f query='
query($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
pullRequests(
states: MERGED
orderBy: {field: UPDATED_AT, direction: DESC}
first: 30
) {
nodes {
number
mergedAt
commits(first: 1) {
nodes {
commit { authoredDate }
}
}
}
}
}
}' -F owner=acme -F repo=platform \
--jq '.data.repository.pullRequests.nodes | map({
pr: .number,
mergedAt: .mergedAt,
firstCommit: .commits.nodes[0].commit.authoredDate,
cycleHours: (
(.mergedAt | fromdateiso8601) -
(.commits.nodes[0].commit.authoredDate | fromdateiso8601)
) / 3600 | round
})'
Deployment frequency from GitHub Deployments
$ gh api "repos/acme/platform/deployments?environment=production&per_page=100" \
--jq 'group_by(.created_at[:10]) | map({date: .[0].created_at[:10], count: length})'
11.8
Insights Tab: Contribution Graphs, Traffic & Referrers
The repository Insights tab exposes metrics that don't require any API calls — useful for quick checks without writing GraphQL.
What's in each Insights section
| Section | What it shows | Useful for |
| Pulse | PRs opened/merged, issues opened/closed, commits, active contributors — for a configurable period (day/week/month) | Quick weekly team health check; detecting when PR throughput drops |
| Contributors | Commit counts, additions, deletions per contributor over time | Spotting bus-factor risk; checking that contributions are distributed |
| Community Standards | Checklist: README, CONTRIBUTING, SECURITY.md, issue templates, PR template, Code of Conduct, CODEOWNERS | Quickly seeing which health files are missing |
| Commits | Commit frequency calendar (contribution graph) — by day of week and time of day | Understanding team work patterns; seeing productivity trends |
| Code frequency | Lines added vs deleted per week | Spotting refactoring sprints; large deletion spikes may indicate dead code removal |
| Dependency graph | All dependencies and dependents — what depends on this repo's packages | Impact assessment before removing a package; visibility into what you're depending on |
| Network | Branching history visualisation across forks | Understanding fork relationships; rarely used day-to-day |
| Forks | List of all forks with last commit date | Finding active forks; open-source contribution scouting |
| Traffic (private repos: owner/admin only) | Views, unique visitors, clones, referring sites, popular content pages — over the last 14 days | Understanding which docs pages get traffic; where clones come from |
TRAFFIC DATA RETENTION
Traffic data (views, clones) is only retained for
14 days in the Insights tab. If you need historical traffic data, set up a scheduled Actions workflow to export it via the REST API and store it in a time-series database or a simple CSV in a private repo.
Exporting traffic data before it expires
on:
schedule:
- cron: '0 6 * * *'
jobs:
export:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- run: |
DATE=$(date +%Y-%m-%d)
gh api repos/acme/platform/traffic/views \
--jq ".views[] | [\"$DATE\", .timestamp, .count, .uniques] | @csv" \
>> traffic/views.csv
git add traffic/views.csv
git commit -m "chore: export traffic data $DATE" || true
git push
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
11.9
Third-Party Analytics: What They Add Over Native GitHub
GitHub's native Insights are useful but limited — no historical retention beyond 14 days for traffic, no team-level aggregation, no DORA dashboards, no benchmarking against industry data. Third-party tools fill these gaps.
LinearB
Engineering metrics platform focused on DORA and developer experience. Connects to GitHub + Jira/Linear. Provides team-level cycle time, PR review time, deployment frequency, and "developer time" (time spent coding vs reviewing vs in meetings). Has GitStream for automated workflow rules (auto-assign reviewers based on changed files, auto-label PRs). Good for teams that want out-of-the-box DORA dashboards without writing GraphQL.
Jellyfish
Engineering management analytics — connects GitHub, Jira, calendar, and video call data. Shows "investment allocation" (what % of engineering time goes to features vs debt vs incidents). More valuable for VPs of Engineering than individual teams. Requires access to a lot of data sources to be useful.
Swarmia
Developer-first metrics. Focuses on flow efficiency: cycle time breakdown (coding / waiting for review / reviewing / waiting to merge), PR size distribution, meeting load vs focus time. Shows metrics per team and per individual (with privacy options). Strong on actionable recommendations — surfaces "largest contributors to slow cycle time" with root causes.
PullPanda / Axolo
PR collaboration tools. Axolo creates a dedicated Slack channel for each PR, routes review notifications there, and auto-archives when merged. Reduces notification noise and keeps PR discussion in one place. Not a metrics tool — focuses on reviewer experience.
GitHub's own Metrics (beta)
GitHub is building native engineering metrics (visible under Insights → Metrics in some Enterprise plans). Currently shows DORA metrics, PR cycle time, and deployment frequency with historical retention. Watch this space — it may reduce the need for third-party tools for baseline metrics.
BEFORE BUYING A TOOL
Write a GraphQL query first. The GitHub API can answer most DORA questions for free. Tools add value when you need: (1) historical data beyond what the API retains, (2) team-level aggregation across many repos, (3) correlation with calendar/meeting data, or (4) benchmarks against industry data. If you just need cycle time and PR review SLA reports, a 50-line Python script hitting the GraphQL API is often enough.
Up Next — Phase 12: GitHub CLI & API Automation
The gh CLI for scripting, REST vs GraphQL API decision guide, Octokit SDK, building GitHub Apps with webhook handling, and practical automation scripts for common org workflows.
Continue to Phase 12 →
Back to Hub