A comprehensive Jira team management dashboard built with Streamlit. JiraHub provides real-time visibility into team workload, ticket status, and project progress across multiple Jira projects and boards with advanced filtering, caching, and automated team member syncing.
- π Jira Cloud Integration - Connect to your Jira Cloud instance via REST API v3 + Agile v1.0
- π Multi-Project Dashboard - View and manage tickets across multiple projects simultaneously
- π― Board Filtering - Filter boards per project to focus on specific team areas
- π Advanced Filtering - Filter by:
- Assignee & Team Labels
- Status (include/exclude)
- Created & Due Date ranges
- Jira Issue Labels
- Story Points
- π₯ Team Member Management - Auto-sync team members from Jira assignees, assign labels for organization
- π Workload Analysis - View workload distribution, status breakdown, and overdue tickets
- πΎ Smart Caching - Redis-powered caching for fast data retrieval
- π Session Persistence - Cookie-based session management survives browser refresh
- ποΈ Multi-Tenant Ready - Role-based access control (Admin, User)
- π³ Docker Support - Fully containerized for easy deployment
| Layer | Technology |
|---|---|
| Frontend | Streamlit 1.41+ |
| Backend | Python 3.13+ async |
| Database | SQLite (development) + Alembic migrations |
| ORM | SQLAlchemy 2.0+ async |
| Caching | Redis 5.2+ |
| API Client | httpx 0.28+ (async) |
| Auth | Fernet encryption + pwdlib argon2 |
| Container | Docker + Docker Compose |
- Installation
- Configuration
- Running the Application
- Project Structure
- Usage Guide
- Jira API Setup
- Troubleshooting
- Development & Contributing
- Python 3.13+ (download)
- Jira Cloud Account with API token access
- Git (optional, for cloning)
- uv package manager (faster than pip) - install guide
-
Clone or Download the Project
git clone https://github.com/eslam5464/JiraHub.git cd JiraHub -
Create & Activate Virtual Environment
# Using uv (recommended) uv venv source .venv/bin/activate # macOS/Linux # or .\.venv\Scripts\Activate.ps1 # Windows PowerShell
-
Install Dependencies
uv sync
-
Configure Environment (See Configuration section)
cp .env.example .env # Edit .env with your settings -
Initialize Database (Automatic on first run)
uv run streamlit run app/main.py
-
Access the App
- Open browser to:
http://localhost:8501 - Login with your registered account
- Open browser to:
- Python 3.13+
- uv package manager
- Redis (local or Docker)
- Git
- Docker + Docker Compose (optional, for containerized development)
-
Clone and Setup Repository
git clone https://github.com/eslam5464/JiraHub.git cd JiraHub # Create virtual environment with uv uv venv source .venv/bin/activate # or .\.venv\Scripts\Activate.ps1 on Windows # Install all dependencies including dev tools (with dev group) uv sync --all-groups
-
Set Up Environment Variables
cp .env.example .env # Edit .env with local development settings -
Start Redis (Required for Caching)
# Option 1: Using Docker (recommended) docker run -d -p 6379:6379 redis:latest # Option 2: Install Redis locally and run redis-server
-
Initialize Database with Alembic
# Create initial migration uv run alembic upgrade head -
Run Application in Dev Mode
# With auto-reload on file changes uv run streamlit run app/main.py --logger.level=debug -
Pre-commit Hooks (Optional but recommended)
uv run pre-commit install uv run pre-commit run --all-files # Run formatters
JiraHub/
βββ app/
β βββ core/ # Configuration & constants
β β βββ config.py # Settings via pydantic-settings
β β βββ constants.py # Roles, enums
β β βββ exceptions/ # Domain & HTTP exceptions
β βββ models/ # SQLAlchemy ORM models
β β βββ base.py # Base model with auto-timestamp
β β βββ user.py # User model
β β βββ team_member.py # Team members with labels
β β βββ user_project.py # Multi-project assignments
β β βββ session.py # Auth sessions
β β βββ ignored_*.py # User preferences (ignored tickets/types)
β βββ schemas/ # Pydantic models for validation
β β βββ user.py # Request/response schemas
β β βββ jira/ # Jira API response models
β βββ repos/ # SQLAlchemy repositories
β β βββ base.py # Base CRUD repo
β βββ services/ # Business logic
β β βββ auth_service.py # User auth & session mgmt
β β βββ jira_client.py # Jira API client (httpx)
β β βββ cache.py # Redis caching layer
β βββ pages/ # Streamlit multi-page app
β β βββ login.py # Authentication
β β βββ register.py # User registration
β β βββ dashboard.py # Main team dashboard
β β βββ project_setup.py # Project/board selection
β β βββ member_detail.py # Team member details
β β βββ ticket_detail.py # Jira issue details
β β βββ admin.py # Admin panel
β β βββ settings.py # User settings & logout
β β βββ insights.py # Analytics & reporting
β βββ utils/ # Helper functions
β β βββ async_helpers.py # Event loop management
β β βββ metrics.py # Calculation utilities
β β βββ cookies.py # Session cookie management
β βββ main.py # Entry point & navigation
βββ alembic/ # Database migrations
βββ scripts/ # Utility scripts
βββ tests/ # Unit & integration tests (optional)
βββ docker-compose.yml # Development DB + Redis
βββ Dockerfile # Production container
βββ pyproject.toml # Project metadata & dependencies
βββ README.md # This file
Create a .env file in the project root (copy from .env.example):
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# DATABASE
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
DATABASE_URL=sqlite+aiosqlite:///./data/jirahub.db
# For production, use PostgreSQL:
# DATABASE_URL=postgresql+asyncpg://user:password@localhost/jirahub
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# REDIS CACHE
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
REDIS_URL=redis://localhost:6379/0
# For Docker containers:
# REDIS_URL=redis://redis:6379/0
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# STREAMLIT CONFIGURATION
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
STREAMLIT_SERVER_PORT=8501
STREAMLIT_SERVER_HEADLESS=true
# Session timeout in hours
SESSION_EXPIRY_HOURS=72
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECURITY
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ENCRYPTION_KEY=your-secret-key-here # Change this!
# Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# JIRA API (obtainable from Jira account settings)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# These are user-provided per account during "Jira Connect" setup
# Not set here - configured in the web UI
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# LOGGING
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
LOG_LEVEL=INFO
# Use DEBUG for development| Variable | Purpose | Default | Required |
|---|---|---|---|
DATABASE_URL |
SQLAlchemy async DB connection string | SQLite local | Yes |
REDIS_URL |
Redis connection for caching | redis://localhost:6379/0 |
Yes |
SESSION_EXPIRY_HOURS |
User session lifetime in hours | 72 | No |
ENCRYPTION_KEY |
Fernet key for token encryption | Generated if missing | Yes (auto) |
STREAMLIT_SERVER_PORT |
Web UI port | 8501 | No |
LOG_LEVEL |
Logging verbosity | INFO | No |
uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# Copy output to ENCRYPTION_KEY in .envTerminal 1 - Start Redis:
docker run -d -p 6379:6379 redis:latest
# or if Redis installed locally:
redis-serverTerminal 2 - Run Streamlit App:
uv run streamlit run app/main.pyAccess at: http://localhost:8501
# Start all services (app + Redis + SQLite)
docker-compose -f docker-compose.yml up -d
# View logs
docker-compose logs -f app
# Stop services
docker-compose down# Build image
docker build -t jirahub:latest .
# Run container
docker run -d \
-p 8501:8501 \
-e DATABASE_URL="sqlite+aiosqlite:///./data/jirahub.db" \
-e REDIS_URL="redis://redis:6379/0" \
-v jirahub_data:/app/data \
--link redis:redis \
jirahub:latest
# Access at http://localhost:8501# Create new migration (after model changes)
uv run alembic revision --autogenerate -m "descriptive message"
# Apply migrations
uv run alembic upgrade head
# Downgrade
uv run alembic downgrade -1βββββββββββββββββββββββββββββββββββββββ
β Streamlit Frontend (Multi-page) β
β ββ Login / Register β
β ββ Dashboard (Main) β
β ββ Project Setup β
β ββ Member Detail β
β ββ Ticket Detail β
β ββ Settings / Admin β
ββββββββββββββββ¬βββββββββββββββββββββββ
β
ββββββββββΌββββββββββ
β Streamlit State β (Session mgmt)
β + Cookies β
ββββββββββ¬ββββββββββ
β
ββββββββββββ΄βββββββββββ
β β
βββββΌβββββββββ ββββββββΌβββββββ
β Services β β Repositoriesβ
ββββββββββββββ€ βββββββββββββββ€
β AuthServiceββββββΆβUser/Session β
βJiraClient ββββββΆβTeamMember β
βCacheServiceββββββΆβUserProject β
ββββββ¬ββββββββ βIgnored* β
β ββββββββ¬βββββββ
β β
ββββββΌβββββββββββββββββββββΌβββββββ
β SQLAlchemy ORM Models β
β ββ User β
β ββ Session β
β ββ TeamMember β
β ββ UserProject β
β ββ IgnoredTicket/Type β
ββββββ¬ββββββββββββββββββββββββββ¬ββ
β β
ββββββΌββββββββββ ββββββββββΌββββ
β SQLite DB β β Redis Cache β
β (async) β β (httpx) β
ββββββββββββββββ βββββββββββββββ
- User logs in β AuthService validates credentials & creates session
- User connects Jira β Token stored encrypted in database
- JiraClient fetches issues via Jira API (httpx async)
- Data cached in Redis for 24 hours (manual refresh available)
- Dashboard displays filtered, cached data with real-time updates
- Team members auto-synced from Jira assignees β TeamMemberRepo upsert
- Click Register to create an account
- Fill email & password (will be hashed with Argon2)
- Login with credentials
- Go to Jira Connect page
- Provide:
- Jira URL:
https://your-domain.atlassian.net - Email: Your Jira account email
- API Token: Generated in Jira account settings (see Jira API Setup)
- Jira URL:
- Click Connect β Token encrypted & stored securely
- Go to Project Setup
- Discover Projects: Fetches your Jira projects
- Select Projects: Choose which to manage
- Select Boards per Project: Choose which boards' issues to track
- Save Configuration
- Go to Dashboard β Issues automatically sync assignees to team members
- Go to Team Members to add custom labels (e.g., "Backend", "Frontend", "QA")
- Use labels in Dashboard filters for quick team filtering
- Filter by Project: Select specific projects (top)
- Filter by Board: Per-project board selection
- Status Filters: Include/Exclude statuses
- Date Ranges: Created & Due dates
- Assignee/Labels: Team member filtering
- Story Points: Estimate range filtering
- Metrics: Total tickets, overdue, missing story points
- Workload Distribution: Bar chart of assignee workload
- Status Distribution: Pie/bar chart of status breakdown
- Overdue Tickets: Red-flagged table
- All Tickets: Full filterable table with details
- Click ticket Key β Ticket Detail page
- Full issue info
- Worklogs & time tracking
- Linked issues & subtasks
- Sprint info
- Click Team Member name β Member Detail page
- Member profile
- Assigned tickets summary
- Custom labels
- Member Detail Page: View all member assignments, labels, reporter status
- Ignore Tickets: Hide noisy issues from dashboard (per user)
- Ignore Issue Types: Hide story, epic, etc. globally
- Settings Page: Update password, manage ignored items, logout
- Session persists across browser refresh (cookie-based)
API tokens allow scripts and applications (like JiraHub) to authenticate with your Jira Cloud account using HTTP Basic Authentication. They're more secure than using passwords because:
- You can create tokens with limited permissions (scopes)
- Tokens can expire automatically
- You can revoke them without changing your password
- Multiple tokens for different purposes
Reference: Atlassian API Token Documentation
Why with scopes? Scopes limit what the token can do, improving security by following the principle of least privilege.
- Atlassian account with access to Jira Cloud
- Verify your identity (you'll receive a one-time passcode via email)
-
Go to API Token Management
- Navigate to: https://id.atlassian.com/manage-profile/security/api-tokens
- Log in if prompted
-
Create Token with Scopes (Recommended)
- Click Create API token with scopes
- Provide a descriptive name (e.g.,
JiraHub-Production) - Select expiration date: 1 to 365 days (default: 1 year)
β οΈ Tokens expire after this period and need renewal
- Click Next
-
Select App Access
- Choose Jira (required for JiraHub)
- Click Next
-
Configure Scopes (Permissions)
-
Required scopes for JiraHub:
read:jira-work (Read issues, boards, sprints) read:jira-user (Read user/assignee data) read:jira.user (Read user info for filtering) -
Optional scopes (if managing/creating issues):
write:jira-work (Modify issues) write:jira-project (Manage project settings)
-
-
Review & Create
- Review scope information
- Click Create
-
Copy & Save Token
β οΈ CRITICAL: Copy the token immediately - it won't be shown again- Save in password manager (LastPass, 1Password, etc.)
- Click Done
If your app doesn't support scoped tokens:
- Go to https://id.atlassian.com/manage-profile/security/api-tokens
- Click Create API token
- Give it a descriptive name
- Set expiration (1-365 days)
- Click Create
- Copy token and save securely
| Setting | Details |
|---|---|
| Token Name | Descriptive name for tracking purpose (e.g., "JiraHub-Production") |
| Expiration | Default: 1 year. Range: 1 day to 365 days. |
| Scopes | Permissions the token can use (recommended security practice) |
| Created Date | Shows when token was created |
- Copy your API token
- Open JiraHub β Jira Connect page
- Fill in:
- Jira URL:
https://your-domain.atlassian.net - Email: Your Atlassian account email
- API Token: Paste your generated token
- Jira URL:
- Click Connect
- Token is encrypted and stored securely
Test if your token works before configuring JiraHub:
# Replace with your details
JIRA_URL="https://your-domain.atlassian.net"
JIRA_EMAIL="your.email@company.com"
JIRA_TOKEN="your_api_token_here"
# Test basic connectivity
curl -v "$JIRA_URL" --user "$JIRA_EMAIL:$JIRA_TOKEN"
# Expected response: 200 or 403 (not 401)
# 401 = Invalid token/email
# 200 = SuccessIf token is compromised or no longer needed:
- Go to https://id.atlassian.com/manage-profile/security/api-tokens
- Find the token in the list
- Click Revoke next to it
- Confirm - token is permanently deleted
Alternative: Revoke all tokens at once for account reset (rare)
| Component | Details |
|---|---|
| Base URL | https://{domain}.atlassian.net |
| REST API | v3 for issue operations |
| Agile API | v1.0 for board/sprint operations |
| Authentication | HTTP Basic Auth: email:token in header |
| Rate Limits | 60 requests/minute (Jira Cloud) |
| Rate Limit Headers | Response includes: X-RateLimit-Limit, X-RateLimit-Remaining |
JiraHub auto-discovers custom fields:
- Story Points Field: Searches common names (Story Points, Points, Estimates, etc.)
- Team Field: Custom "Team" field if configured in Jira
- Sprint Field: Standard Jira Sprint field
If fields not auto-discovered:
- Check field names in Jira: Project Settings β Fields
- Contact Jira admin if fields are hidden from your account
- Manually specify field IDs in
app/core/config.pyif needed
β DO:
- Use scoped tokens (limit permissions)
- Store token in password manager
- Set reasonable expiration (90-365 days)
- Create separate tokens per tool/integration
- Rotate tokens periodically
β DON'T:
- Commit tokens to Git
- Share tokens via email or chat
- Use tokens directly in code (use environment variables)
- Use account password instead of token
- Create tokens with unnecessary scopes
| Problem | Solution |
|---|---|
| 401 Unauthorized | Token invalid or expired. Regenerate new token |
| 403 Forbidden | Token valid but lacks required scopes. Check token scopes match requirements |
| Token Expired | Recreate token. Old token automatically revokes after expiration |
| Can't Find API Token Page | Ensure you're logged in. Go to https://id.atlassian.com/manage-profile/security/api-tokens |
| One-Time Passcode Never Arrives | Check spam folder. Request new passcode from Atlassian |
| Can't Create More Tokens | Account limit reached (varies by org). Revoke unused tokens and try again |
- Official Docs: Manage API Tokens - Atlassian Support
- Jira Scopes: Jira Platform Scopes
- Account Security: Keep Your Atlassian Account Secure
# Error: ModuleNotFoundError: No module named 'streamlit'
β Make sure uv dependencies are synced: uv sync
# Error: Port 8501 already in use
β Kill process: lsof -ti:8501 | xargs kill -9
Or run on different port: uv run streamlit run app/main.py --server.port=8502# Error: Connection refused (Redis not running)
β Start Redis: docker run -d -p 6379:6379 redis:latest
Or check if running: redis-cli ping (should return PONG)
# Error: REDIS_URL wrong format
β Check .env: Should be redis://localhost:6379/0
For Docker: redis://redis:6379/0 (use container DNS)# Error: 401 Unauthorized
β Check Jira URL, email, API token are correct
Verify token hasnt expired (regenerate if > 1 year old)
# Error: 404 Not Found (project/board missing)
β Verify project key & board ID in Jira
Ensure user has permission to access them# Error: database is locked
β Only one Streamlit dev server can access SQLite at a time
Stop other Streamlit processes or switch to PostgreSQL for production
# Error: table 'user' already exists
β Delete data/jirahub.db and restart (fresh DB created)
Or run: uv run alembic downgrade base && uv run alembic upgrade head# User logged out after Ctrl+R
β This should NOT happen (cookie persistence is configured)
Check: .env SESSION_EXPIRY_HOURS is not 0
Clear browser cookies, try again
If persistent, check app logs: uv run streamlit run app/main.py --logger.level=debug# Old data showing in dashboard
β Click "Refresh All Projects" button (top right)
Or: Clear Redis cache: redis-cli FLUSHDB
# Issues not auto-syncing with Jira
β Manual refresh required (no webhook integration yet)
β Future: Add Jira webhooks for real-time updates# Dashboard takes 10+ seconds to load
β Likely causes:
1. Redis not running or slow network β Check redis-cli ping
2. Jira API rate limited β Check rate limit in response headers
3. Large project (1000+ issues) β Consider filtering by board/status
# Solutions:
β Restart Redis: docker restart <redis_container_id>
β Use board/status filters to reduce load
β Scale Redis if many concurrent users# Install pre-commit hooks
uv run pre-commit install
# Run formatters before commit
uv run black .
uv run isort .
uv run ruff check . --fix
# Run tests (if tests exist)
uv run pytest tests/- Python: PEP 8, type hints recommended
- Git: Conventional commits (
feat:,fix:,docs:, etc.) - Style: Black (line length 100)
- Linting: Ruff (E, F, I, UP rules)
# Unit tests (create in tests/ directory)
uv run pytest tests/ -v
# Test Jira connectivity
uv run python -c "from app.services.jira_client import JiraClient; print('Import OK')"
# Test database
uv run python -c "from app.models.db import engine; print('DB OK')"# app/pages/new_feature.py
import streamlit as st
def render():
st.title("New Feature")
st.write("Your feature here")
render() # Streamlit auto-discovers as pageThen reference in app/main.py navigation.
# app/models/my_model.py
from app.models.base import Base
from sqlalchemy import Column, String
class MyModel(Base):
__tablename__ = "my_model"
name = Column(String, nullable=False)
# Then run:
uv run alembic revision --autogenerate -m "add my_model"
uv run alembic upgrade head# In app/services/jira_client.py
async def get_custom_endpoint(self, endpoint: str) -> dict:
return await self._request("GET", f"/rest/api/3/{endpoint}")# In your service/page
cache = get_cache_service()
cached_data = run_async(cache.get_cached(user_email, "MyKey"))
if not cached_data:
# Fetch from API
await cache.set_cached(user_email, "MyKey", data)# Create feature branch
git checkout -b feat/my-feature
# Make changes, test locally
# ...
# Commit with conventional message
git add .
git commit -m "feat: add new filter to dashboard"
# Push and create PR
git push origin feat/my-feature
# Create Pull Request on GitHub- Jira webhooks for real-time issue updates
- Team velocity & sprint burndown charts
- Scheduled reports & email delivery
- Slack integration for notifications
- Time tracking insights & billing
- Custom KPI dashboards
- Dark mode support
- Multi-language support
This project is licensed under the MIT License. See LICENSE file for details.
- Issues: Create a GitHub issue for bugs or feature requests
- Questions: Check Troubleshooting section first
- Contributions: See Development & Contributing section
Built with:
- Streamlit - Web app framework
- SQLAlchemy - Database ORM
- Jira Cloud REST API - Issue tracking data
- httpx - Async HTTP client
- Redis - In-memory cache