Simple local dashboard to display Jira sprint tasks sorted by priority with Python Flask backend.
- โ Dynamic Sprint Selection - Choose any sprint from dropdown (2025Q1, 2025Q2, etc.)
- โ Smart Sprint Detection - Auto-selects current quarter on load
- โ Intelligent Caching - Sprint list cached for 24 hours, task data for 5 minutes; manual refresh bypasses all caches
- โ Sort by Priority - Tasks sorted Highest โ Lowest
- โ Status filters - Toggle Done/Killed and use stat cards (In Progress, To Do/Pending/Accepted, High Priority)
- โ Project Filtering - Separate Tech and Product tasks
- โ Clean, Minimalist UI - Beautiful typography with smooth animations
- โ Auto-refresh - Reload button for tasks and sprints
- โ Secure Credentials - Local secrets stay outside git; DB/OAuth user tokens are encrypted in database storage
- โ Team-aware filtering - Multi-team JQL plus UI dropdown to slice per team and see team name on each story
- โ Team groups - Define workspace-shared department groups (1-12 teams), choose a shared default, and let each user choose which groups appear in dashboard controls
- โ Epic grouping - Stories grouped under their epic with assignee and story-point totals
- โ Dependency focus - Click Depends On/Dependents to highlight related tasks and show missing deps inline
- โ Planning rollups - Selected story points summarized per team, project, and overall
- โ Capacity planning - Team capacity vs planning capacity (exclusions via epic toggle)
- โ Scoped planning persistence - Planning selections persist locally per sprint and team group, then reconcile on refresh
- โ Scoped team persistence - Team dropdown selection persists locally per sprint and team group, then falls back to All Teams if unavailable
- โ Planning bulk actions - Quickly include Accepted, To Do, Postponed, and Awaiting Validation stories
- โ Compact sticky controls - Sticky Sprint/Group/Teams controls keep the same dropdown behavior while using a smaller compact layout
- โ Scenario planner - Feature guide
- โ Alerts - Feature guide
- โ Sprint statistics - Feature guide
- โ EPM view - Feature guide
- Scenario Planner technical rules are included in Scenario Planner
- Local install, DB mode, migrations, and Home token setup are covered in INSTALL.md
- Agent work artifact rules are defined in docs/AGENTS.md
- Product analytics rules are defined in docs/README_ANALYTICS.md
jira_server.py- Python Flask backend serverjira-dashboard.html- Frontend dashboard pagefrontend/- Frontend source (src/) and compiled bundle (dist/)docs/features/- User-facing feature guides for alerts, statistics, and scenario planningdocs/AGENTS.md- Rules for agent-created work artifacts.env.example- Template for environment variablesINSTALL.md- Local install, PostgreSQL, DB migration, and Home token setup flow.gitignore- Git ignore file (keeps secrets safe)requirements.txt- Python dependenciesAGENTS.md- Contributor guide and workflow conventionspostmortem/- Postmortems and incident learnings (index inpostmortem/README.md)
For the current DB/OAuth path, use INSTALL.md. The short version is:
- Create a virtualenv with Python 3.10+ linked against OpenSSL 1.1.1+ and install
requirements.txt. - Copy
.env.exampleto.env. - Configure PostgreSQL, Atlassian OAuth, and token-encryption values.
- Run Alembic migrations.
- Run
.venv/bin/python scripts/check_startup_preflight.py. - Start the backend with
.venv/bin/python jira_server.py. - Sign in, then connect the current user's Home/Townsquare token in
Settings -> Connectionsif you need EPM.
Starting .venv/bin/python jira_server.py does not start PostgreSQL, create the database, or run migrations. More detailed single-user/legacy setup guidance remains below if you need it.
If you want the fastest setup with no frontend build step:
- Download the latest release asset (e.g.
jira-execution-planner-latest.zip) from GitHub Releases. - Unzip it anywhere.
- Configure
.envfrom.env.example. - Install backend deps:
chmod +x install.sh && ./install.sh - Start the backend:
.venv/bin/python jira_server.py - Open
jira-dashboard.htmlin your browser (or visithttp://localhost:5050/).
The server binds to 127.0.0.1 by default. Use APP_BIND_HOST=0.0.0.0 only for intentional network exposure, with ALLOW_NETWORK_BIND=true; Basic auth network exposure also requires ALLOW_BASIC_AUTH_ON_NETWORK=true and APP_ENVIRONMENT_KEY=local. Dev diagnostics require ALLOW_DEV_DIAGNOSTIC_ENDPOINTS=true and loopback access.
Prebuilt release zips omit source tests and development plans. Use a source checkout when you need to run the unit test suite.
The release zip is the runnable package for normal installs. Editable installs assume the source checkout or extracted release directory is still present because Flask serves jira-dashboard.html and frontend/dist from sibling files. Do not treat pip install . by itself as a self-contained wheel distribution.
The UI shows a โNew Version Availableโ badge when a newer release is detected. Release zips include release-info.json so update checks work without git. Download the latest zip and replace your folder to update.
If you want to develop UI changes without hitting Jira, keep JSON snapshots locally (untracked) and use them as data fixtures in your tooling/tests.
git clone <your-repo-url>
cd jira-dashboardUse Python 3.10+ linked against OpenSSL 1.1.1+; macOS system Python builds linked against LibreSSL are not supported because the security-patched HTTP dependency stack requires Python 3.10+ and OpenSSL.
Option A - Using install script (recommended):
chmod +x install.sh
./install.shOption B - Manual installation:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
python -m pip install -e .Option C - Standard local targets:
make install
make test
make runCreate .env file from template:
cp .env.example .envEdit .env and choose the run mode.
nano .env # or use any text editorDB/OAuth mode is the production-like local path. It requires JIRA_URL, JIRA_AUTH_MODE=atlassian_oauth, Atlassian OAuth client values, CONFIG_STORAGE_BACKEND=db, DATABASE_URL, and token-encryption settings. Full setup, migrations, and Home token connection steps live in INSTALL.md.
Server-side API-token auth remains a legacy compatibility mode. Do not use legacy Jira/Home Basic credentials for the DB/OAuth EPM path.
.venv/bin/python scripts/check_startup_preflight.py.venv/bin/python jira_server.pyYou can override the environment values at launch time instead of editing .env:
.venv/bin/python jira_server.py \
--server_port 5050 \
--jira_url https://your-company.atlassian.netThe dashboard can run with Atlassian OAuth 2.0 (3LO) for user Jira access instead of server-side Jira Basic auth.
- Create an OAuth 2.0 app in the Atlassian Developer Console.
- Add the required User Identity API and Jira API scopes documented in docs/SUPPORT-atlassian-oauth-setup.md.
- Set the callback URL to
http://localhost:5050/api/auth/atlassian/callback, or to an HTTPS tunnel URL if your Atlassian app requires HTTPS. - In
.env, setJIRA_AUTH_MODE=atlassian_oauth,APP_ENVIRONMENT_KEY=local,JIRA_URL, the Atlassian OAuth client settings,FLASK_SECRET_KEY, DB storage settings, token-encryption settings, andOAUTH_LOCAL_TOKEN_STORE_ALLOWED=truefor local single-process testing.JIRA_URLselects the matching Atlassian cloud site after login. - Start the server and open
/login. TheSign in with Atlassianaction starts Atlassian OAuth; for managed Atlassian accounts backed by Microsoft Entra SSO, Atlassian redirects through Microsoft automatically before returning to the local callback. - Confirm
/api/auth/statusreportsauthenticated: true, then use/api/testto verify Jira REST access. In DB/OAuth mode, EPM Home/Townsquare metadata becomes available after the signed-in user connects a Home token inSettings -> Connections.
Jira still receives Atlassian OAuth tokens, not Microsoft Entra tokens. Direct Microsoft access tokens cannot be used as Jira REST API bearer tokens.
Product analytics are disabled by default. To enable the internal employee analytics path for a deployment, set:
GA4_ENABLED=true
GTM_CONTAINER_ID=GTM-NZJW2CFN
GA4_MEASUREMENT_ID=G-6QERX19WB0
GA4_USER_ID_PEPPER=<deployment-secret>GA4_ENABLED=false is the deployment kill switch. It prevents GTM from loading on new page loads, disables app-owned analytics sends, and open dashboard tabs re-check the server switch every 60 seconds so collection stops without clearing the GTM ID, measurement ID, or pepper variables. GA4_DEBUG_MODE=true adds GA4 DebugView metadata for validation only; leave it false outside controlled testing. The event contract and forbidden payload rules are in docs/README_ANALYTICS.md, and the GA4/GTM operator runbook is docs/plans/SUPPORT-ga4-user-configuration.md.
Open jira-dashboard.html in your browser (or visit http://localhost:5050/).
On first launch (or when local config files do not exist), open Dashboard Settings and configure:
- Scope projects
- Add Jira projects and assign each to Product or Tech
- Jira source
- Select Sprint Field
- Optionally select Sprint Board (faster sprint loading)
- Field mapping
- Set Issue Type, Parent Name Field, Story Points Field, Team Field
- Capacity (used by the Planning module)
- Select Capacity Project
- Select Capacity Field (numeric team capacity field)
- Team groups
- Create at least one group and choose which teams it contains
- Click Save to persist configuration locally
Saved local JSON files:
dashboard-config.json(projects, Jira source, field mapping, capacity, priority weights)team-groups.json(team groups and team catalog metadata)
The app now relies on Dashboard Settings for supported runtime configuration. Keep .env focused on credentials and local server settings.
Use the ENG | EPM switch in the dashboard header to browse project rollups rendered as Initiative -> Epic -> Story/Task hierarchies.
In DB/OAuth mode, EPM is hidden until the signed-in user connects a Home/Townsquare token in Settings -> Connections. Jira REST reads use the user's OAuth session; Home/Townsquare metadata reads use only the connected atlassian_user_api_token stored encrypted in DB auth_tokens.
For setup details, see INSTALL.md. For EPM behavior and configuration rules, see docs/features/epm-view.md.
The repo commits the compiled frontend output so normal users donโt need Node.
If you edit the UI, rebuild and commit frontend/dist:
npm ci
npm run buildOptional during development:
npm run watchCI will fail if frontend/dist is out of sync. We precompile JSX so production does not transform JSX in the browser.
Run the local CI-style verification path before preparing a PR:
make verifyThis repo now ships a GitLab CI skeleton for verification and container image build only. It does not include production registry push, release, or deployment automation.
Can prepare now:
- Build the container from
Dockerfile, keepfrontend/distcurrent, run startup preflight, and expose/healthas shallow liveness. - Run GitLab verify/container stages that install backend/frontend dependencies, run tests/builds, and tag the image with
CI_COMMIT_SHA. - Keep runtime env ownership explicit: app secrets, Atlassian OAuth settings, DB URL, token encryption, GA4 toggles, and local defaults remain outside the image.
Container env contract:
APP_ENVIRONMENT_KEY=production
PORT=5050
APP_BIND_HOST=0.0.0.0
ALLOW_NETWORK_BIND=true
JIRA_URL=https://your-company.atlassian.net
JIRA_AUTH_MODE=atlassian_oauth
CONFIG_STORAGE_BACKEND=db
DATABASE_URL=<postgresql-url-from-secret>
ATLASSIAN_CLIENT_ID=<from-secret>
ATLASSIAN_CLIENT_SECRET=<from-secret>
ATLASSIAN_REDIRECT_URI=<https-origin>/api/auth/atlassian/callback
SESSION_COOKIE_SECURE=true
APP_ALLOWED_ORIGINS=<https-origin>
FLASK_SECRET_KEY=<from-secret>
TOKEN_ENCRYPTION_KEY_SOURCE=env
TOKEN_ENCRYPTION_MASTER_KEY_B64=<from-secret>
TOKEN_ENCRYPTION_KEY_ID=<key-id>
OAUTH_LOCAL_TOKEN_STORE_ALLOWED=false
LOCAL_FILE_STATE_ENABLED=false
SCENARIO_DRAFT_LEGACY_IMPORT_ENABLED=false
RUN_DB_MIGRATIONS=false
GA4_ENABLED=falsePORT=5050 is the container/Gunicorn listener port. SERVER_PORT remains a local Flask compatibility setting for source-checkout runs. The Docker image defaults to APP_BIND_HOST=0.0.0.0 and ALLOW_NETWORK_BIND=true so GCP service networking can reach Gunicorn; startup preflight still fails closed unless hosted OAuth sets SESSION_COOKIE_SECURE=true, a real FLASK_SECRET_KEY, and an explicit APP_ALLOWED_ORIGINS value matching the HTTPS origin.
For migrations, use RUN_DB_MIGRATIONS=true only for a single-instance internal deployment where the app container is the agreed migration owner. For multi-replica deployments, run Alembic as a separate release job or init step before rolling out app replicas, and keep RUN_DB_MIGRATIONS=false on the web containers.
OAuth2 Proxy is perimeter access only. The app still requires Atlassian OAuth 2.0 (3LO) because Jira REST reads use the signed-in user's Atlassian OAuth session, and Home/Townsquare EPM reads require the user's connected Home token stored encrypted in DB.
Needs SRE ownership or confirmation:
- GitLab project path, registry/image path, runner container-build method, branch/tag policy, and when
PUSH_IMAGE=truemay be enabled. - Hosted HTTPS origin, Atlassian OAuth redirect/callback registration, OAuth2 Proxy boundary, TLS termination, secure cookie settings, and
APP_ALLOWED_ORIGINS. - PostgreSQL provisioning, migration owner, persistence/backups, log retention, resource limits, and production readiness probe policy.
Blocked until deployment reference:
- Production registry credentials/path, secret references, migration release strategy, Helm or Kubernetes values, namespace/routing/VPN/proxy details, TLS secret names, and real release jobs.
-
Backend (
jira_server.py):- Runs on
localhost:5050by default (overridable viaSERVER_PORTor--server_port) - Reads local app settings from
.env; in DB/OAuth mode, user tokens are stored encrypted in DB - Makes secure READ-ONLY API requests to Jira
- Caches sprint list for 24 hours, task data for 5 minutes (reduces API load)
- Refresh button bypasses all server caches for immediate Jira updates
- Returns filtered data to frontend
- Runs on
-
Frontend (
jira-dashboard.html):- Displays tasks in a clean, animated interface
- Sprint Selector - Dropdown to choose sprint (2025Q1, 2025Q2, etc.)
- Team Groups + Sticky Controls - Workspace-shared Group, Sprint, and Teams controls stay available in a compact sticky header while scrolling
- Auto-selects current quarter on first load
- Sorted by priority (Highest โ Lowest)
- Filter toggles for Done/Killed/Tech/Product tasks
- Dependency focus mode (chips highlight related tasks, missing deps shown inline)
- Click stat cards to filter by status
- Planning selections persist locally per
sprint + groupand prune stories that move out of scope on refresh - Planning module includes bulk actions for Accepted, To Do, Postponed, and Awaiting Validation
- Refresh buttons for tasks and sprints
- Ready-to-close alert uses all-time story data (no sprint filter)
- Statistics panel for active/closed sprints (derived from loaded sprint tasks)
- Click Depends On or Dependents on a story to enter focus mode (dims unrelated cards and highlights related ones).
- Relationship direction is shown via right-side pills (โโ BLOCKED BYโ / โBLOCKS โโ).
- Missing dependencies (not in the current sprint load) are shown inline under the focused story.
- Missing dependency details are fetched via
/api/issues/lookupand cached client-side.
The dashboard supports dynamic sprint selection:
- Auto-detection: Automatically selects current quarter (e.g., 2025Q4)
- Dropdown: Choose from available sprints starting from 2025Q1
- Caching: Sprint list cached for 24 hours for fast loading
- Refresh button: Manually update sprint list from Jira
- Two fetch methods:
- Fast: Via Board API (set Sprint Board in Dashboard Settings โ Jira source)
- Fallback: Via Issues API (uses your dashboard configuration and selected projects)
The Statistics panel covers sprint execution and lead-time views for active or completed sprints.
See the full guide:
Capacity planning uses the loaded sprint tasks plus a separate Jira project for team capacity:
- Team capacity: pulled from the configured Capacity project/field (Dashboard Settings โ Capacity).
- Planning capacity: team capacity minus excluded epic story points (same epic include/exclude toggle).
- Split: estimated capacity split is 70% Product / 30% Tech.
Priority weights used for weighted delivery:
- Blocker 0.40
- Critical 0.30
- Major 0.20
- Minor 0.06
- Low 0.03
- Trivial 0.01
See the full guide:
โ ๏ธ Never commit.envfile to Git! It contains your secrets- โ
The
.envfile is already in.gitignore - โ
Sprint cache file (
sprints_cache.json) is also in.gitignore - โ
Always use
.env.exampleas a template for others - โ
DB/OAuth EPM stores each user's Home token encrypted in DB
auth_tokens; shared service credentials are not used as an EPM fallback - โ All API requests are READ-ONLY (no modifications to Jira)
- โ No hardcoded company-specific URLs or IDs in code
"Connection refused" error:
- Make sure the Python server is running (
.venv/bin/python jira_server.py)
"ModuleNotFoundError" when starting server:
- Install dependencies from the repo root:
.venv/bin/python -m pip install -r requirements.txt
DATABASE_URL is required when CONFIG_STORAGE_BACKEND=db:
- Follow INSTALL.md: start PostgreSQL, create the database, set
DATABASE_URL, run Alembic migrations, then restart Flask.
TOKEN_ENCRYPTION_MASTER_KEY_B64 or TOKEN_ENCRYPTION_KEY_ID is required:
- Generate and set the token-encryption values from INSTALL.md. DB/OAuth token storage will not start without them.
"Required Jira Basic auth settings must be set" error:
- Make sure you created
.envfile from.env.example - Check that you filled in the required Basic auth settings for legacy API-token mode, or switch to DB/OAuth mode with
JIRA_AUTH_MODE=atlassian_oauth
"401 Unauthorized" error:
- In DB/OAuth mode, sign in again through
/login. - In legacy Basic mode, check that your API token settings are correct in
.env.
EPM tab is missing in DB/OAuth mode:
- Connect the signed-in user's Home/Townsquare token in
Settings -> Connections; EPM is intentionally hidden until that encrypted user token exists.
"No tasks found":
- Verify your configured projects and shared team groups match your Jira setup
- Check that the sprint exists and has tasks
- If you use a custom JQL override, try simplifying it
"Team name missing" in tasks output:
- Open Dashboard Settings โ Field mapping and set the Team Field to your Jira Team[Team] custom field
"No sprints available" in dropdown:
- Option 1: Set Sprint Board in Dashboard Settings โ Jira source (faster method)
- Find your board ID: go to
/api/boardsendpoint or check Jira board URL
- Find your board ID: go to
- Option 2: Leave Sprint Board empty (fallback method works automatically)
- Check that your selected projects return tasks with sprint information
Planning panel shows no capacity / capacity comparison looks wrong:
- Open Dashboard Settings โ Capacity
- Select both Capacity Project and Capacity Field
- Click Save (changes are applied only after saving)
Sprints loading slowly:
- First load fetches from Jira (may take a few seconds)
- Subsequent loads use 24-hour cache (instant)
- To find board ID for faster loading: visit
http://localhost:5050/api/boards
Want to refresh sprint list manually:
- Click "Refresh Sprints" button in the dashboard
- Or visit:
http://localhost:5050/api/sprints?refresh=true
Browser shows old errors after fixing:
- Do a hard refresh:
Ctrl+Shift+R(Windows/Linux) orCmd+Shift+R(Mac) - Or open in incognito/private mode
- Tasks: Click the refresh button in the header โ bypasses server cache and fetches fresh data from Jira (also refreshes sprints and ready-to-close data)
- Sprints: Also refreshed by the header refresh button, or CMD+R/CTRL+R (page reload uses server cache bypass on first load)
- Auto-reload: Tasks reload automatically when you change sprint selection
- Stats: Teams/Priority update from loaded tasks; Burnout refreshes when sprint/team scope changes and when opening Burnout
jira-dashboard/
โโโ AGENTS.md # Contributor guidelines
โโโ jira_server.py # Flask entrypoint and compatibility shims
โโโ jira-dashboard.html # Served dashboard shell
โโโ backend/
โ โโโ routes/ # Flask blueprints by route surface
โ โโโ auth/ # OAuth, DB auth context, CSRF, token crypto
โ โโโ config/ # JSON/DB config repositories and validation
โ โโโ epm/ # Home/Townsquare access, EPM scope, rollups
โ โโโ security/ # Endpoint policy and security headers
โ โโโ services/ # Service extraction namespace
โโโ frontend/ # Frontend source + compiled bundle
โ โโโ src/
โ โ โโโ api/ # Frontend endpoint wrappers
โ โ โโโ eng/ # ENG dashboard view modules
โ โ โโโ epm/ # EPM controls, settings, and rollup views
โ โ โโโ scenario/ # Scenario planner helpers/components
โ โ โโโ settings/ # Settings modal modules
โ โ โโโ stats/ # Statistics views and utilities
โ โ โโโ ui/ # Shared UI primitives
โ โ โโโ dashboard.jsx # App shell and remaining legacy wiring
โ โโโ dist/ # Compiled JS + CSS output (committed)
โโโ docs/ # Documentation
โ โโโ plans/ # EXEC/DONE/FUTURE/SUPPORT/GATE plans
โ โโโ features/ # User-facing feature guides
โโโ planning/ # Scenario planner core logic
โโโ tests/ # Python, Node, source-guard, and UI tests
โโโ postmortem/ # Postmortems and incident learnings
โโโ requirements.txt # Python dependencies
โโโ .env.example # Environment variables template
โโโ .gitignore # Git ignore file (includes .env and cache)
โโโ install.sh # Installation script
โโโ README.md # This file
โโโ .env # Your credentials (NOT in git!)
โโโ dashboard-config.json # Dashboard settings (auto-generated, NOT in git!)
โโโ team-groups.json # Team groups config (auto-generated, NOT in git!)
โโโ scenario-overrides.json # Scenario planner overrides (auto-generated, NOT in git!)
โโโ sprints_cache.json # Sprint cache (auto-generated, NOT in git!)
โโโ tasks.test.local.json # Local task snapshots (optional, NOT in git!)
The dashboard uses a few different cache layers to keep Jira traffic reasonable:
- Sprint list: cached on disk in
sprints_cache.jsonfor 24 hours. Use Refresh Sprints or?refresh=trueto force a reload from Jira. - Tasks and epics: cached in server memory for 5 minutes for repeated requests with the same sprint/group/project scope. The refresh button bypasses the server cache and also clears browser-side group state, so changes made in Jira appear immediately after clicking refresh.
- Statistics:
- Teams and Priority views are computed from the currently loaded sprint tasks in the browser.
- Burnout loads on demand from
/api/stats/burnoutand is reused in the browser for the same sprint/team/task scope during the session. - Lead Times loads on demand from
/api/stats/epic-cohortand is cached both in the browser and on the server for repeated scope requests. - Completed-sprint delivery stats from
/api/statsare persisted instats_cache.json.
- Reference data: Jira projects, components, epic search results, labels, and update-check results use short-lived caches to avoid repeated lookup calls while editing settings.
- Request shaping: heavy Jira fetches are paginated and capped per endpoint instead of trying to pull everything in one call.
- Timeout protection: Jira requests use bounded timeouts, typically between 10 and 30 seconds depending on the endpoint.
- Keep
README.md,AGENTS.md, andpostmortem/README.mdaligned with structural or workflow changes. - Agent-created work artifacts live under
docs/agents/and follow docs/AGENTS.md. - Postmortems live in
postmortem/and followMRTXXX-short-title.mdnaming in creation order (oldest first). - Postmortem-specific agent instructions live in postmortem/AGENTS.md.
- Capture misses and fixes in postmortems and update the index when adding one.
Feel free to open issues or submit pull requests!
MIT License - feel free to use this project however you'd like!
Built with Flask, Python, React, and esbuild.