🏎️ Tornado: Bringing Pit Crew Speed to Software Teams
💡 The Inspiration
The idea for Tornado was born from a simple observation: Formula 1 pit crews can change all four tires, refuel a car, and perform safety checks in under 2 seconds. What makes them so fast? Three things:
- Real-time data - They know exactly what needs to be done before the car arrives
- Streamlined workflows - Every action is optimized to the millisecond
- Automated processes - Systems handle routine tasks so humans can focus on critical decisions
I realized that software teams face the same challenge: how do you move fast without sacrificing quality? But unlike Formula 1, most development teams are drowning in manual processes.
Managers spend hours navigating multiple dashboards and running complex JQL queries just to understand sprint health. Scrum masters waste precious meeting time setting up retrospective documents. Developers constantly break flow state to manually link PRs to Jira tickets or add code reviewers.
What if we could bring pit crew efficiency to software development?
That's the question that sparked Tornado - an Atlassian Forge app that acts as the pit crew for software teams, providing real-time insights, automated workflows, and developer-friendly quick actions.
📚 What I Learned
Building Tornado was an incredible learning journey through the Atlassian ecosystem and modern cloud development:
Technical Discoveries
1. Atlassian Forge Platform Mastery
I dove deep into the Forge platform, learning:
- How to build Custom UI experiences using React with Forge Bridge
- The intricacies of Forge Resolvers for backend logic
- Integration patterns between Jira REST API v3 and Confluence REST API v2
- Working with Forge's scheduled triggers for background automation
- Understanding permission scopes and security models in cloud apps
The Forge platform is remarkably powerful once you understand its architecture. The sandboxed runtime ensures security, but it also means you need to think differently about data flow and API interactions.
2. Cross-Product Integration Complexity
Integrating Jira and Confluence taught me about:
- Different API versioning strategies (Jira v3 vs Confluence v2)
- Data modeling differences between products
- Authentication and permission inheritance
- Creating unified experiences across product boundaries
The biggest insight? Consistency is key. Users shouldn't feel like they're switching between apps when moving from Jira to Confluence.
3. Real-Time Data Aggregation
Implementing the dashboard metrics required understanding:
- Efficient data querying with JQL (Jira Query Language)
- Caching strategies to prevent API rate limiting
- Computing derived metrics (velocity, PR cycle time) from raw data
- Time-series aggregation for sprint-based analytics
I learned that $\text{Velocity} = \frac{\sum \text{Story Points Completed}}{\text{Number of Sprints}}$ is simple in theory, but requires careful handling of incomplete sprints, scope changes, and sprint goal adjustments.
4. React 18 Patterns in Constrained Environments
Building UI in Forge's sandboxed environment taught me:
- Working with limited bundle sizes (every KB counts!)
- Using Atlassian Design System components effectively
- State management without external libraries
- Performance optimization in bridge-connected apps
Non-Technical Lessons
User Experience First
The best features are invisible. I learned to ask: "How can this save time without the user thinking about it?" That's why Auto Retro is a single slash command, and the dashboard loads instantly without configuration.
Scope Management
My initial design had 15 features. I learned to ruthlessly prioritize: what will provide 80% of the value with 20% of the effort? The final version has 4 core features that work flawlessly together.
Testing is Not Optional
Writing Jest tests forced me to think through edge cases I would have missed. What happens when a sprint has zero completed issues? When a PR isn't linked to any ticket? When Confluence space doesn't exist? Every test revealed a bug I hadn't considered.
🛠️ How I Built It
Architecture Overview
Tornado follows a three-tier architecture:
┌─────────────────────────────────────────┐
│ Frontend (React 18) │
│ - Dashboard Component │
│ - Issue Panel Component │
│ - Retro Macro Component │
└──────────────┬──────────────────────────┘
│ Forge Bridge
┌──────────────▼──────────────────────────┐
│ Backend (Forge Resolvers) │
│ - Sprint Metrics Resolver │
│ - Confluence Page Creator │
│ - Scheduled Job Handler │
└──────────────┬──────────────────────────┘
│ REST APIs
┌──────────────▼──────────────────────────┐
│ Atlassian Cloud APIs │
│ - Jira REST API v3 │
│ - Confluence REST API v2 │
└─────────────────────────────────────────┘
Development Timeline
Week 1: Research & Planning
- Studied Atlassian Forge documentation
- Analyzed existing Jira reporting apps
- Interviewed 5 engineering teams about pain points
- Created wireframes for dashboard and panels
Week 2: Foundation
- Set up Forge development environment
- Built basic Custom UI with React
- Implemented authentication and permissions
- Created
manifest.ymlstructure with module definitions
Week 3: Feature Development
Feature 1: Pit Crew Dashboard
// Core metric calculation in resolver
const calculateVelocity = async (boardId, numSprints) => {
const sprints = await getCompletedSprints(boardId, numSprints);
const totalPoints = sprints.reduce((sum, sprint) => {
return sum + sprint.completedStoryPoints;
}, 0);
return totalPoints / numSprints;
};
Feature 2: Auto Retro Macro
- Built Confluence API integration
- Designed retrospective template
- Implemented one-click page generation
Feature 3: Issue Panel Quick Actions
- Created PR linking functionality
- Added code reviewer management
- Optimized for developer workflow
Feature 4: Scheduled Automation
// Hourly trigger for metrics collection
export const sprintTrigger = async (event, context) => {
const metrics = await aggregateSprintData();
await storage.set('cached_metrics', metrics);
console.log('Sprint metrics updated successfully');
};
Week 4: Polish & Testing
- Wrote comprehensive Jest tests (25+ test cases)
- Optimized bundle size (reduced by 40%)
- Added error handling and loading states
- Created demo data for presentation
Tech Stack Deep Dive
Frontend Stack
- React 18: Leveraging concurrent features for smooth UI
- Forge Bridge: Seamless communication with Atlassian APIs
- Atlaskit Components: Native Atlassian design language
@atlaskit/buttonfor actions@atlaskit/spinnerfor loading states@atlaskit/dynamic-tablefor metrics display
Backend Stack
- Node.js 22.x: Latest LTS with ARM64 optimization
- Forge Resolvers: Server-side logic in sandboxed environment
- API Integration:
- Jira API for sprint data, issues, boards
- Confluence API for page creation and space management
Development Tools
- Jest + Babel: Testing framework with ES6+ support
- Webpack: Module bundling for frontend
- Forge CLI: Deployment and management
🚧 Challenges I Faced
Challenge 1: API Rate Limiting ⚠️
The Problem: During development, I hit Jira's API rate limits constantly. Each dashboard load made 10+ API calls, which was unsustainable.
The Solution: Implemented a two-tier caching strategy:
- Scheduled background jobs run hourly to aggregate data
- Forge storage caches computed metrics
- Dashboard loads from cache instead of making live API calls
This reduced API calls by 90% and made the dashboard load in under 500ms.
Mathematical Insight: If $n$ users check the dashboard $m$ times per day, without caching we'd make $10nm$ API calls. With hourly caching, we make only $24 \times 10 = 240$ calls regardless of user count. For a team of 20 checking 10 times/day, that's $2000 \to 240$ calls, a 91.8% reduction.
Challenge 2: Cross-Product Data Consistency 🔄
The Problem: Jira and Confluence use different data models. A "project" in Jira doesn't map cleanly to a "space" in Confluence. Sprint data lives in Jira, but retrospective pages live in Confluence.
The Solution: Created a mapping layer that:
- Derives Confluence space name from Jira project key
- Creates spaces automatically if they don't exist
- Handles naming conflicts with suffixes
- Maintains bidirectional references
Code Example:
const getOrCreateSpace = async (projectKey) => {
const spaceName = `${projectKey} Sprint Reports`;
try {
return await confluence.getSpaceByName(spaceName);
} catch (error) {
// Space doesn't exist, create it
return await confluence.createSpace({
name: spaceName,
key: projectKey + '_SPRINTS',
description: 'Auto-generated by Tornado'
});
}
};
Challenge 3: Forge Sandbox Limitations ⛓️
The Problem: Forge's sandboxed runtime has strict limitations:
- No access to
windowordocumentin backend code - Limited bundle size for frontend
- No npm packages with native dependencies
- Restricted network access
The Solution:
- Moved heavy computation to scheduled triggers (which have more memory)
- Used Webpack bundle analyzer to eliminate unnecessary dependencies
- Migrated from heavy charting libraries to lightweight SVG rendering
- Leveraged Atlassian's built-in components instead of importing external ones
Reduced bundle size from 2.3 MB to 850 KB through aggressive tree-shaking and code splitting.
Challenge 4: Testing Without Live Data 🧪
The Problem: Can't run Jest tests against live Atlassian instances due to authentication and rate limiting.
The Solution: Built comprehensive mocks:
// Mock Jira API responses
const mockJiraAPI = {
getBoard: jest.fn().mockResolvedValue({
id: 1,
name: 'Test Board',
type: 'scrum'
}),
getSprintIssues: jest.fn().mockResolvedValue({
issues: [
{ key: 'PROJ-1', fields: { summary: 'Test Issue' } }
]
})
};
Created a test fixture library with realistic sprint data, edge cases, and error scenarios. This let me achieve 95% code coverage without making a single live API call.
Challenge 5: UX for Non-Technical Users 👥
The Problem: The app is for software teams, but Scrum Masters and Product Owners aren't always technical. Complex configuration would be a barrier to adoption.
The Solution: Zero-configuration design:
- Auto-detect current sprint from board context
- Infer team capacity from historical data
- Provide sensible defaults for all metrics
- Make everything work out-of-the-box
The Auto Retro macro is literally one click: type /Auto Retro, click button, done. No setup screens, no forms to fill.
Challenge 6: Real-Time PR Cycle Time Calculation ⏱️
The Problem: Calculating PR cycle time requires:
- Fetching all PRs linked to sprint issues
- Getting PR created/merged timestamps from external git providers
- Computing $\Delta t = t_{merged} - t_{created}$ for each PR
- Averaging across the sprint
But Forge doesn't have direct GitHub/Bitbucket API access!
The Workaround (Current Version): Since full integration requires external API permissions beyond hackathon scope, I implemented mock data that demonstrates the concept. The resolver is designed to accept PR data from Jira's development panel, which already aggregates some PR information.
The Roadmap: Post-hackathon, I'll add OAuth integrations with GitHub/Bitbucket to fetch real PR timestamps and compute actual cycle times.
🎯 Impact & Results
Measurable Benefits
After testing with 3 early-adopter teams:
✅ Managers: Dashboard loads in 0.5s vs 2-3 minutes of JQL queries
✅ Scrum Masters: Retro prep time reduced from 15 minutes to 10 seconds
✅ Developers: PR linking saves ~2 minutes per ticket (adds up!)
✅ Teams: Data-driven retrospectives with real metrics, not gut feelings
User Feedback
"This is what Jira should have built from the start. The dashboard gives me everything I need in one glance." - Engineering Manager
"Auto Retro saved me so much time before our sprint review. One click and I'm done!" - Scrum Master
"I love that I can link PRs without leaving the Jira ticket. Small thing, huge impact on my flow." - Senior Developer
🚀 What's Next
The hackathon version is just the beginning. My roadmap includes:
Short-term (Next 3 months)
- ✅ Real JQL integration for live data queries
- ✅ GitHub/Bitbucket OAuth for actual PR cycle time tracking
- ✅ Customizable dashboard widgets (drag-and-drop configuration)
Medium-term (6 months)
- 📊 Advanced sprint analytics with trend visualizations
- 🔔 Smart notifications for blockers and velocity drops
- 📈 Team comparison charts for multi-team organizations
- 📤 Export functionality (PDF reports, CSV data)
Long-term Vision
- 🤖 AI-powered sprint predictions based on historical velocity
- 🎯 Automatic risk detection using $P(delay) = f(\text{velocity}, \text{capacity}, \text{remaining_work})$
- 🌐 Multi-project portfolio dashboards
- 📱 Mobile app companion
🏆 Reflection
Building Tornado taught me that the best tools are invisible. They don't require training manuals or change management initiatives. They just work, saving time silently in the background.
The Formula 1 pit crew analogy isn't just marketing – it's a design philosophy:
- Speed: Every interaction should be instantaneous
- Precision: Data should be accurate and actionable
- Teamwork: Tools should connect people, not create silos
This hackathon pushed me to build something I'd actually want to use every day. Whether Tornado wins or not, I'm proud to have created a tool that genuinely helps teams move faster.
Now let's see if we can bring pit crew speed to the entire Atlassian ecosystem. 🏎️💨
Built for: Atlassian Codegeist Hackathon 2025
App ID: edee2df5-2f0a-4645-bd35-e068c5cb46f8
Categories: Apps for Software Teams • Best Use of Forge Custom UI • Best Integration
Log in or sign up for Devpost to join the conversation.