JIRA Integration Architecture¤
Overview¤
The RAID module provides comprehensive bidirectional synchronization between Impact incidents and JIRA tickets, ensuring data consistency across both platforms.
Synchronization Architecture¤
Core Components¤
-
Sync Engine (
src/firefighter/raid/sync.py
)- Central synchronization logic
- Loop prevention mechanism
- Field mapping and validation
-
Signal Handlers (
src/firefighter/raid/signals/
)incident_updated_sync.py
- Handles Impact → JIRA syncincident_created.py
- Handles incident creation events
-
JIRA Client (
src/firefighter/raid/client.py
)- Extended JIRA client with RAID-specific methods
- Issue creation, updates, and transitions
- Attachment handling
JIRA Ticket Creation¤
Initial Ticket Creation¤
Function: prepare_jira_fields()
in src/firefighter/raid/forms.py
Centralizes all JIRA field preparation for both P1-P3 and P4-P5 workflows.
P1-P3 (Critical): - Trigger: incident_channel_done
signal - Handler: src/firefighter/raid/signals/incident_created.py
- Flow: Create Incident → Create Slack channel → Signal triggers JIRA ticket
P4-P5 (Normal): - Trigger: Form submission - Handler: UnifiedIncidentForm._trigger_normal_incident_workflow()
- Flow: Direct call to jira_client.create_issue()
Custom Fields Mapping¤
Always Passed: - customfield_11049
(environments): List of env values (PRD, STG, INT) - P1-P3: First environment only - P4-P5: All selected environments - customfield_10201
(platform): Platform value (platform-FR, platform-All, etc.) - customfield_10936
(business_impact): Computed from impacts_data
Impact-Specific: - Customer: zendesk_ticket_id
- Seller: seller_contract_id
, zoho_desk_ticket_id
, is_key_account
, is_seller_in_golden_list
- P4-P5: suggested_team_routing
Bug Fix GT-1334 (October 2025): P4-P5 incidents were not passing custom fields. Fixed by creating prepare_jira_fields()
to centralize field preparation for both workflows.
Bidirectional Sync Flows¤
Impact → JIRA Sync¤
Trigger: Incident field updates in Impact Handler: sync_incident_changes_to_jira()
Syncable Fields: - title
→ summary
- description
→ description
- priority
→ priority
(with value mapping) - status
→ status
(with transitions) - commander
→ assignee
Process: 1. Check if RAID is enabled 2. Validate update_fields parameter 3. Filter for syncable fields only 4. Apply loop prevention cache 5. Call sync_incident_to_jira()
JIRA → Impact Sync¤
Trigger: JIRA webhook updates Handler: handle_jira_webhook_update()
Process: 1. Parse webhook changelog data 2. Identify changed fields 3. Apply appropriate sync functions: - sync_jira_status_to_incident()
- sync_jira_priority_to_incident()
- sync_jira_fields_to_incident()
Field Mapping¤
Status Mapping¤
JIRA → Impact:
JIRA_TO_IMPACT_STATUS_MAP = {
"Open": IncidentStatus.INVESTIGATING,
"To Do": IncidentStatus.INVESTIGATING,
"In Progress": IncidentStatus.MITIGATING,
"In Review": IncidentStatus.MITIGATING,
"Resolved": IncidentStatus.MITIGATED,
"Done": IncidentStatus.MITIGATED,
"Closed": IncidentStatus.POST_MORTEM,
"Reopened": IncidentStatus.INVESTIGATING,
"Blocked": IncidentStatus.MITIGATING,
"Waiting": IncidentStatus.MITIGATING,
}
Impact → JIRA:
IMPACT_TO_JIRA_STATUS_MAP = {
IncidentStatus.OPEN: "Open",
IncidentStatus.INVESTIGATING: "In Progress",
IncidentStatus.MITIGATING: "In Progress",
IncidentStatus.MITIGATED: "Resolved",
IncidentStatus.POST_MORTEM: "Closed",
IncidentStatus.CLOSED: "Closed",
}
Priority Mapping¤
JIRA → Impact:
JIRA_TO_IMPACT_PRIORITY_MAP = {
"Highest": 1, # P1 - Critical
"High": 2, # P2 - High
"Medium": 3, # P3 - Medium
"Low": 4, # P4 - Low
"Lowest": 5, # P5 - Lowest
}
Loop Prevention¤
Cache-Based Mechanism¤
Function: should_skip_sync()
Cache Key Format: sync:{entity_type}:{entity_id}:{direction}
Timeout: 30 seconds
Process: 1. Check if sync recently performed 2. Set cache flag during sync 3. Automatic expiration prevents permanent blocks
Sync Directions¤
class SyncDirection(Enum):
IMPACT_TO_JIRA = "impact_to_jira"
JIRA_TO_IMPACT = "jira_to_impact"
IMPACT_TO_SLACK = "impact_to_slack"
SLACK_TO_IMPACT = "slack_to_impact"
Error Handling¤
Transaction Management¤
- All sync operations wrapped in
transaction.atomic()
- Rollback on any failure
- Detailed error logging with context
Graceful Degradation¤
- Missing JIRA tickets: Log warning, continue
- Field validation errors: Skip invalid fields
- Network failures: Retry mechanism via Celery
IncidentUpdate Integration¤
Dual Sync Paths¤
-
Direct Incident Updates
- Uses
update_fields
parameter - More efficient for bulk operations
- Uses
-
IncidentUpdate Records
- Tracks individual field changes
- Preserves change history
- Anti-loop detection for JIRA-originated updates
Loop Detection for IncidentUpdates¤
Pattern: Updates created by sync have: - created_by = None
(system update) - message
contains "from Jira"
if instance.created_by is None and "from Jira" in (instance.message or ""):
logger.debug("Skipping sync - appears to be from Jira sync")
return
Configuration¤
Required Settings¤
ENABLE_RAID = True
- Enable RAID moduleRAID_JIRA_PROJECT_KEY
- Default JIRA projectRAID_JIRA_INCIDENT_CATEGORY_FIELD
- Custom field mappingRAID_TOOLBOX_URL
- Integration URL
Environment Variables¤
- Database:
POSTGRES_DB
,POSTGRES_SCHEMA
- Feature flags:
ENABLE_JIRA
,ENABLE_RAID
- Slack:
FF_SLACK_SKIP_CHECKS
(for testing)
Testing Sync Functionality¤
Key Test Files¤
tests/test_raid/test_sync.py
- Core sync logictests/test_raid/test_sync_signals.py
- Signal handlerstests/test_raid/test_webhook_serializers.py
- Webhook processing
Test Patterns¤
@patch("firefighter.raid.sync.sync_incident_to_jira")
@override_settings(ENABLE_RAID=True)
def test_sync_incident_changes(self, mock_sync):
mock_sync.return_value = True
# Test sync triggering and field filtering
Performance Considerations¤
Optimization Strategies¤
- Field Filtering: Only sync changed fields
- Batch Operations: Group related updates
- Async Processing: Use Celery for heavy operations
- Cache Warming: Pre-load frequently accessed data
Monitoring¤
- Sync success/failure rates
- Performance metrics per sync type
- Error categorization and alerting