A Streamlit application that fetches Jira tickets via JQL and renders delivery analytics dashboards for team leads and engineering managers.
- Delayed Tasks Trend — weekly count of tickets past their ETA per user
- Total Delay per User — cumulative delay days per user per week (bar or area chart)
- Delivery Velocity — tickets completed per user per week
- Status Distribution — ticket breakdown by workflow status (pie + bar + per-user stacked bar)
- Cycle Time Analysis — median/mean creation-to-resolution time per user with weekly trend
- Priority Breakdown — tickets by priority over time and per user
- Unassigned Tasks — tickets with no assignee; supports assigning a team member in-app
- Workload Overview — active tasks and story point share per person
- Team Members — directory of configured Jira users with names and emails
- Python 3.10+
- A Jira Cloud instance with API access
- An Atlassian API token — generate one at
https://id.atlassian.com/manage-profile/security/api-tokens
pip install -r requirements.txtcp .env.example .envEdit .env and fill in:
| Variable | Description |
|---|---|
JIRA_BASE_URL |
Your Atlassian instance URL, e.g. https://your-domain.atlassian.net |
JIRA_EMAIL |
Your Atlassian account email |
JIRA_API_TOKEN |
API token from Atlassian (the app encodes it automatically) |
JIRA_USERS |
Comma-separated Jira account IDs to track, e.g. accountId1,accountId2 |
JQL_FILTER |
Default JQL shown in the sidebar, e.g. type = "Story" ORDER BY created DESC |
MAX_RESULTS |
Tickets per page — pagination is automatic (default: 100) |
ETA_FIELD |
Jira field used as the deadline for delay calculation (default: duedate) |
PORT |
Streamlit server port (default: 8501) |
To find a user's account ID: open any ticket in Jira, click the assignee avatar — the profile URL ends with the account ID.
./run.shThe script reads PORT from .env and starts Streamlit. Open http://localhost:8501 in your browser.
app.py # Sidebar nav, data fetch, page routing
config.py # Loads .env into constants
jira_client.py # Paginated Jira POST /search client (cached 5 min)
logger.py # Application logger → application_output.log
utils/
data_processing.py # Shared helpers: delay calc, week bucketing, cycle days
pdf_export.py # PDF download button for charts
dashboards/
delayed_tasks.py # Delayed Tasks Trend
total_delay.py # Total Delay per User
delivery_velocity.py # Delivery Velocity
status_distribution.py # Status Distribution
cycle_time.py # Cycle Time Analysis
priority_breakdown.py # Priority Breakdown
unassigned_tasks.py # Unassigned Tasks
workload.py # Workload Overview
users.py # Team Members
tests/
conftest.py # Shared fixtures and sample data factory
test_data_processing.py # Unit tests for utils/data_processing.py
test_dashboard_logic.py # Unit tests for dashboard business logic
test_ui.py # End-to-end Playwright UI tests
pytest tests/test_data_processing.py tests/test_dashboard_logic.py -v./run.sh &
pytest tests/test_ui.py -v --browser chromiumUI tests use the MCP Playwright server in headed mode. See tests/TEST_CASES.md for a full description of every test case.
Sidebar JQL input
→ jira_client.fetch_tickets(jql) # paginated POST /rest/api/3/search
→ data_processing.to_df(tickets) # normalise + parse date columns
→ st.session_state.df # shared across all dashboards
→ dashboards/<name>.render(df) # each dashboard filters/groups independently
Data is cached for 5 minutes. Click Fetch Data again to force a refresh.
.envis gitignored and never read directly by any dashboard or test.- All credentials are accessed through
config.pyconstants only. - A pre-tool hook (
check_secrets.py) actively blocks any attempt to read.envfiles directly. - The app encodes
JIRA_EMAIL:JIRA_API_TOKENto Base64 automatically — no manual step needed. - No Jira write operations are performed except the optional "Update in Jira" action on the Unassigned Tasks dashboard (requires explicit user interaction).