A web and desktop application to track and visualize GitHub pull request metrics over time.
- Track open PRs, unassigned PRs, and PRs older than 30 days
- Traffic light indicators (🟢🟡🔴) based on approval status
- Compare snapshots to see what changed between two points in time
- Auto-loads the most recent snapshot on startup
- Store snapshots in DuckDB with automatic checkpoint on shutdown
- Visualize trends with interactive charts
- Reviewer workload breakdown with comments and approvals
- Deep linking URLs for sharing specific views
- Approval status filters (All, Ready, Partial, Needs Review)
- React frontend with Vite
- Flask backend with REST API
- Electron desktop app with bundled backend and persistent database
.
├── track_open_prs.py # Main script to fetch and store PR data
├── backend/
│ ├── db/
│ │ └── schema.sql # Database schema
│ ├── local/
│ │ ├── server.py # Flask API server
│ │ ├── test_server.py # Backend tests
│ │ └── test_server_properties.py # Property-based tests
│ └── requirements.txt
├── frontend/
│ ├── src/
│ │ ├── App.jsx # Main React component
│ │ ├── App.test.jsx # Frontend tests
│ │ ├── api.js # API fetch helper (web + Electron)
│ │ ├── main.jsx
│ │ └── index.css
│ ├── package.json
│ ├── vite.config.js
│ └── index.html
├── electron/
│ ├── main.js # Electron main process
│ ├── preload.js # Context bridge for renderer
│ └── package.json # Electron + electron-builder config
└── skills/
└── pr_tracker_skill.md # AI agent skill for Claude Code and Kiro
This application requires sensitive credentials that should never be committed to git:
-
Copy the example environment file:
cp .env.example .env
-
Edit
.envand add your credentials:GITHUB_TOKEN- Create a personal access token at https://github.com/settings/tokens- For public repos:
public_reposcope - For private repos:
reposcope
- For public repos:
- Never commit your
.envfile (it's already in.gitignore)
-
Load environment variables:
# Option 1: Export manually export GITHUB_TOKEN="your_token_here" # Option 2: Use a tool like direnv or python-dotenv source .env
- ✅ Use read-only GitHub tokens when possible
- ✅ Rotate tokens regularly
- ✅ Never commit tokens or
.envfiles to git - ✅ Use environment-specific tokens (dev vs prod)
⚠️ In production, disable Flask debug mode⚠️ In production, restrict CORS to specific origins⚠️ Use HTTPS in production deployments
- Install Python dependencies:
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt-
Set up environment variables (see Security section above)
-
Start the local server:
cd local
python server.pyThe API will be available at http://localhost:5001
- Install Node dependencies:
cd frontend
npm install- Start the development server:
npm run devThe frontend will be available at http://localhost:5173
The Electron app bundles both the frontend and backend into a single distributable application. The Flask server runs as a child process inside the app.
cd electron
npm install
npm run devIn dev mode, Electron will check if the Vite dev server is running on localhost:5173. If it is, it uses that (with hot reload). If not, it falls back to the pre-built frontend in electron/frontend-dist/. The Flask backend is spawned using the project's backend/.venv/bin/python3.
To rebuild the frontend for Electron after making changes:
cd frontend
npx vite build --mode electronPrerequisites:
- Node.js and npm
- Python 3.12+ with PyInstaller (
pip install pyinstaller)
cd electron
npm install
# Build everything: frontend + backend binary + packaged app
npm run buildThe output will be in electron/release/.
If you prefer to run each step individually:
cd electron
# 1. Build the React frontend
npm run build:frontend
# 2. Bundle the Flask backend into a standalone executable
npm run build:backend
# 3. Package the Electron app
npm run distTo test the packaged app without creating a distributable installer:
npm run pack # creates unpacked app in electron/release/# Standard build (current architecture only)
npm run build
# Universal binary (Intel + Apple Silicon)
npm run build:backend:mac
npm run distOutput in electron/release/:
.dmg— disk image installer.zip— zipped app bundle
To open the unpacked app: open electron/release/mac*/Pull\ Request\ Tracker\ Dashboard.app
Note: The app is not code-signed by default. On first launch, right-click the app and select "Open" to bypass Gatekeeper, or sign it with your Apple Developer certificate.
npm run buildOutput in electron/release/:
.exe(NSIS installer) — standard Windows installer- Portable
.exe— no installation required
Note: Building Windows targets on macOS/Linux requires Wine. For best results, build on a Windows machine. PyInstaller must also run on Windows to produce a Windows binary.
npm run buildOutput in electron/release/:
.AppImage— portable, runs on most distros without installation.deb— Debian/Ubuntu package
To run the AppImage:
chmod +x electron/release/*.AppImage
./electron/release/*.AppImageNote: PyInstaller must run on Linux to produce a Linux binary. Cross-compilation is not supported.
- Electron's main process finds an available port (starting at 5001)
- Spawns the Flask backend as a child process (PyInstaller binary in production, venv Python in dev)
- Waits for the backend to respond to health checks
- Injects
window.__BACKEND_PORT__into the HTML so the frontend knows where to send API calls - Loads the React frontend in a BrowserWindow
- On startup, the most recent snapshot is automatically loaded and displayed
- On quit, DuckDB is checkpointed (
CHECKPOINTcommand) to flush all data to disk
The database (pr_tracker.duckdb) is stored in the OS user data directory and persists across app restarts:
- macOS:
~/Library/Application Support/Pull Request Tracker Dashboard/ - Linux:
~/.config/Pull Request Tracker Dashboard/ - Windows:
%APPDATA%/Pull Request Tracker Dashboard/
To start fresh, use File → Reset Database... from the menu bar. This deletes the database file, restarts the backend, and reloads the frontend with a clean state.
Set up environment variables:
export GITHUB_TOKEN="your-github-token"
export GITHUB_REPO="awslabs/aws-athena-query-federation"Run the script to store a snapshot:
python track_open_prs.py --storeThis will:
- Fetch all open PRs from GitHub
- Get reviewer information for each PR
- Store the snapshot in the database via the API
- Make sure both backend and frontend servers are running (or use the Electron app)
- Open
http://localhost:5173in your browser - The most recent snapshot loads automatically on startup
- Click on any snapshot to see the PRs from that point in time
- Use approval filters (🟢🟡🔴) to focus on PRs by review status
- Use the Compare Snapshots tab to diff two snapshots
To publish a report to Quip instead of storing:
export QUIP_TOKEN="your-quip-token"
python track_open_prs.pyTo update an existing Quip doc:
export QUIP_DOC_ID="doc-id"
python track_open_prs.pySet up a cron job to collect data regularly:
# Run every 6 hours
0 */6 * * * cd /path/to/pr-tracker && /path/to/.venv/bin/python track_open_prs.py --storeThe skills/pr_tracker_skill.md file defines an agent skill named pr-tracker. The skill builds an HTML report of open PRs for a GitHub repository. The report includes summary cards, an age histogram, and a table color-coded by approval status. The skill can also publish the report to a SharePoint page.
The skill calls GitHub and SharePoint through Model Context Protocol (MCP) tools. Configure these MCP servers in your agent before you use the skill:
- GitHub MCP server (required). The skill lists open PRs and reads their reviews.
- SharePoint MCP server (optional). The skill uses it only to publish the report.
The skill file names tools such as github_pat__list_pull_requests and run_python. Your agent might expose these tools under different names. If a tool name does not match, edit the skill to use the names that your agent exposes.
Claude Code loads each skill from <skills-dir>/<skill-name>/SKILL.md. Install the skill for all your projects:
mkdir -p ~/.claude/skills/pr-tracker
cp skills/pr_tracker_skill.md ~/.claude/skills/pr-tracker/SKILL.mdTo install it for this project only, use .claude/skills/pr-tracker/SKILL.md in the repository root instead.
Start a new Claude Code session and run /skills to confirm that pr-tracker appears. Invoke the skill with /pr-tracker, or ask for it directly:
/pr-tracker https://github.com/awslabs/aws-athena-query-federation
Kiro uses the same SKILL.md layout. Install the skill for all your workspaces:
mkdir -p ~/.kiro/skills/pr-tracker
cp skills/pr_tracker_skill.md ~/.kiro/skills/pr-tracker/SKILL.mdTo install it for this workspace only, use .kiro/skills/pr-tracker/SKILL.md in the repository root instead.
Restart Kiro (IDE or kiro-cli chat) so that it discovers the skill. Then ask Kiro to run it:
Use the pr-tracker skill to report on open PRs in https://github.com/awslabs/aws-athena-query-federation
The copy in your skills directory does not track changes to this repository. Re-run the cp command after you pull updates. To track changes automatically, replace the copy with a symbolic link:
ln -sf "$(pwd)/skills/pr_tracker_skill.md" ~/.claude/skills/pr-tracker/SKILL.md
ln -sf "$(pwd)/skills/pr_tracker_skill.md" ~/.kiro/skills/pr-tracker/SKILL.mdGET /api/stats- Get latest snapshot, 30-day trend, and reviewer workloadGET /api/stats?threshold=14- Custom age threshold for "old PRs" metricGET /api/snapshots?days=30- Get snapshots from last N daysGET /api/snapshots/<id>/prs- Get PRs for a specific snapshotGET /api/snapshots/<id>/reviewers- Get reviewer workload for a snapshotGET /api/snapshots/compare?snapshot1=X&snapshot2=Y- Compare two snapshotsPOST /api/snapshots- Create a new snapshotDELETE /api/snapshots/<id>- Delete a snapshot and its associated dataPOST /api/import- Trigger a live data import
The database has three main tables:
pr_snapshots- Stores summary metrics for each snapshotprs- Stores individual PR details linked to snapshotspr_comments- Stores comment counts per reviewer per PR
DuckDB is used instead of SQLite for better analytical query performance. The schema uses CREATE IF NOT EXISTS so it is safe to run on an existing database.
Backend tests (22 tests):
cd backend/local
python -m pytest test_server.py -vFrontend tests (20 tests):
cd frontend
npm test
# or for UI mode
npm run test:ui
# or for coverage
npm run test:coverageThe Flask server supports these environment variables:
| Variable | Default | Description |
|---|---|---|
DB_PATH |
pr_tracker.duckdb |
Path to the DuckDB database file |
FLASK_PORT |
5001 |
Port for the Flask server |
FLASK_DEBUG |
1 |
Enable/disable debug mode (1/0) |
SCHEMA_PATH |
../db/schema.sql |
Path to the database schema file |
RUNNING_IN_ELECTRON |
(unset) | When set, disables Flask's auto-reloader |
- Backend: Modify
backend/local/server.py - Frontend: Edit React components in
frontend/src/ - Database: Update
backend/db/schema.sqland reinitialize - Electron: Modify
electron/main.jsfor main process changes
Apache-2.0