Updates
This commit is contained in:
1
ETB-API/sla_oncall/management/__init__.py
Normal file
1
ETB-API/sla_oncall/management/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Management commands for SLA & On-Call Management
|
||||
Binary file not shown.
1
ETB-API/sla_oncall/management/commands/__init__.py
Normal file
1
ETB-API/sla_oncall/management/commands/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Management commands
|
||||
Binary file not shown.
Binary file not shown.
395
ETB-API/sla_oncall/management/commands/setup_sla_oncall.py
Normal file
395
ETB-API/sla_oncall/management/commands/setup_sla_oncall.py
Normal file
@@ -0,0 +1,395 @@
|
||||
"""
|
||||
Management command to set up SLA & On-Call Management with default configurations
|
||||
"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth import get_user_model
|
||||
from datetime import time, timedelta
|
||||
from django.utils import timezone
|
||||
|
||||
from sla_oncall.models import (
|
||||
BusinessHours,
|
||||
SLADefinition,
|
||||
EscalationPolicy,
|
||||
OnCallRotation,
|
||||
OnCallAssignment,
|
||||
NotificationTemplate,
|
||||
)
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Set up SLA & On-Call Management with default configurations'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--force',
|
||||
action='store_true',
|
||||
help='Force recreation of existing configurations',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.stdout.write('Setting up SLA & On-Call Management...')
|
||||
|
||||
force = options['force']
|
||||
|
||||
# Create default business hours
|
||||
self.create_default_business_hours(force)
|
||||
|
||||
# Create default SLA definitions
|
||||
self.create_default_sla_definitions(force)
|
||||
|
||||
# Create default escalation policies
|
||||
self.create_default_escalation_policies(force)
|
||||
|
||||
# Create default notification templates
|
||||
self.create_default_notification_templates(force)
|
||||
|
||||
# Create sample on-call rotation (if users exist)
|
||||
self.create_sample_oncall_rotation(force)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS('Successfully set up SLA & On-Call Management!')
|
||||
)
|
||||
|
||||
def create_default_business_hours(self, force):
|
||||
"""Create default business hours configurations"""
|
||||
self.stdout.write('Creating default business hours...')
|
||||
|
||||
default_hours, created = BusinessHours.objects.get_or_create(
|
||||
name='Standard Business Hours',
|
||||
defaults={
|
||||
'description': 'Standard 9-5 business hours, Monday to Friday',
|
||||
'timezone': 'UTC',
|
||||
'weekday_start': time(9, 0),
|
||||
'weekday_end': time(17, 0),
|
||||
'weekend_start': time(10, 0),
|
||||
'weekend_end': time(16, 0),
|
||||
'is_active': True,
|
||||
'is_default': True,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {default_hours.name}')
|
||||
|
||||
# 24/7 business hours
|
||||
twenty_four_seven, created = BusinessHours.objects.get_or_create(
|
||||
name='24/7 Operations',
|
||||
defaults={
|
||||
'description': '24/7 operations - always business hours',
|
||||
'timezone': 'UTC',
|
||||
'weekday_start': time(0, 0),
|
||||
'weekday_end': time(23, 59),
|
||||
'weekend_start': time(0, 0),
|
||||
'weekend_end': time(23, 59),
|
||||
'is_active': True,
|
||||
'is_default': False,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {twenty_four_seven.name}')
|
||||
|
||||
def create_default_sla_definitions(self, force):
|
||||
"""Create default SLA definitions"""
|
||||
self.stdout.write('Creating default SLA definitions...')
|
||||
|
||||
# Critical incidents SLA
|
||||
critical_sla, created = SLADefinition.objects.get_or_create(
|
||||
name='Critical Incident Response',
|
||||
defaults={
|
||||
'description': 'SLA for critical and emergency incidents',
|
||||
'sla_type': 'RESPONSE_TIME',
|
||||
'incident_severities': ['CRITICAL', 'EMERGENCY'],
|
||||
'incident_priorities': ['P1'],
|
||||
'target_duration_minutes': 15,
|
||||
'business_hours_only': False,
|
||||
'escalation_enabled': True,
|
||||
'escalation_threshold_percent': 75.0,
|
||||
'is_active': True,
|
||||
'is_default': False,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {critical_sla.name}')
|
||||
|
||||
# High priority incidents SLA
|
||||
high_sla, created = SLADefinition.objects.get_or_create(
|
||||
name='High Priority Response',
|
||||
defaults={
|
||||
'description': 'SLA for high priority incidents',
|
||||
'sla_type': 'RESPONSE_TIME',
|
||||
'incident_severities': ['HIGH'],
|
||||
'incident_priorities': ['P2'],
|
||||
'target_duration_minutes': 30,
|
||||
'business_hours_only': False,
|
||||
'escalation_enabled': True,
|
||||
'escalation_threshold_percent': 80.0,
|
||||
'is_active': True,
|
||||
'is_default': False,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {high_sla.name}')
|
||||
|
||||
# Medium priority incidents SLA
|
||||
medium_sla, created = SLADefinition.objects.get_or_create(
|
||||
name='Medium Priority Response',
|
||||
defaults={
|
||||
'description': 'SLA for medium priority incidents during business hours',
|
||||
'sla_type': 'RESPONSE_TIME',
|
||||
'incident_severities': ['MEDIUM'],
|
||||
'incident_priorities': ['P3'],
|
||||
'target_duration_minutes': 120,
|
||||
'business_hours_only': True,
|
||||
'escalation_enabled': True,
|
||||
'escalation_threshold_percent': 85.0,
|
||||
'is_active': True,
|
||||
'is_default': False,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {medium_sla.name}')
|
||||
|
||||
# Resolution time SLA
|
||||
resolution_sla, created = SLADefinition.objects.get_or_create(
|
||||
name='Critical Incident Resolution',
|
||||
defaults={
|
||||
'description': 'SLA for resolving critical incidents',
|
||||
'sla_type': 'RESOLUTION_TIME',
|
||||
'incident_severities': ['CRITICAL', 'EMERGENCY'],
|
||||
'incident_priorities': ['P1'],
|
||||
'target_duration_minutes': 240, # 4 hours
|
||||
'business_hours_only': False,
|
||||
'escalation_enabled': True,
|
||||
'escalation_threshold_percent': 90.0,
|
||||
'is_active': True,
|
||||
'is_default': False,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {resolution_sla.name}')
|
||||
|
||||
def create_default_escalation_policies(self, force):
|
||||
"""Create default escalation policies"""
|
||||
self.stdout.write('Creating default escalation policies...')
|
||||
|
||||
# Critical escalation policy
|
||||
critical_escalation, created = EscalationPolicy.objects.get_or_create(
|
||||
name='Critical Incident Escalation',
|
||||
defaults={
|
||||
'description': 'Escalation policy for critical and emergency incidents',
|
||||
'escalation_type': 'TIME_BASED',
|
||||
'trigger_condition': 'SLA_THRESHOLD',
|
||||
'incident_severities': ['CRITICAL', 'EMERGENCY'],
|
||||
'trigger_delay_minutes': 0,
|
||||
'escalation_steps': [
|
||||
{
|
||||
'level': 1,
|
||||
'delay_minutes': 5,
|
||||
'actions': ['notify_oncall', 'notify_manager'],
|
||||
'channels': ['email', 'sms']
|
||||
},
|
||||
{
|
||||
'level': 2,
|
||||
'delay_minutes': 15,
|
||||
'actions': ['notify_director', 'page_oncall'],
|
||||
'channels': ['email', 'sms', 'phone']
|
||||
},
|
||||
{
|
||||
'level': 3,
|
||||
'delay_minutes': 30,
|
||||
'actions': ['notify_executive', 'escalate_to_vendor'],
|
||||
'channels': ['email', 'phone', 'webhook']
|
||||
}
|
||||
],
|
||||
'notification_channels': ['email', 'sms', 'phone'],
|
||||
'is_active': True,
|
||||
'is_default': False,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {critical_escalation.name}')
|
||||
|
||||
# Standard escalation policy
|
||||
standard_escalation, created = EscalationPolicy.objects.get_or_create(
|
||||
name='Standard Escalation',
|
||||
defaults={
|
||||
'description': 'Standard escalation policy for most incidents',
|
||||
'escalation_type': 'TIME_BASED',
|
||||
'trigger_condition': 'SLA_THRESHOLD',
|
||||
'incident_severities': ['LOW', 'MEDIUM', 'HIGH'],
|
||||
'trigger_delay_minutes': 5,
|
||||
'escalation_steps': [
|
||||
{
|
||||
'level': 1,
|
||||
'delay_minutes': 15,
|
||||
'actions': ['notify_oncall'],
|
||||
'channels': ['email']
|
||||
},
|
||||
{
|
||||
'level': 2,
|
||||
'delay_minutes': 30,
|
||||
'actions': ['notify_manager'],
|
||||
'channels': ['email', 'sms']
|
||||
}
|
||||
],
|
||||
'notification_channels': ['email', 'sms'],
|
||||
'is_active': True,
|
||||
'is_default': True,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {standard_escalation.name}')
|
||||
|
||||
def create_default_notification_templates(self, force):
|
||||
"""Create default notification templates"""
|
||||
self.stdout.write('Creating default notification templates...')
|
||||
|
||||
# Email escalation template
|
||||
email_escalation, created = NotificationTemplate.objects.get_or_create(
|
||||
name='Email Escalation Alert',
|
||||
template_type='ESCALATION',
|
||||
channel_type='EMAIL',
|
||||
defaults={
|
||||
'subject_template': 'URGENT: Incident #{incident_id} Escalated - {incident_title}',
|
||||
'body_template': '''
|
||||
Incident #{incident_id} has been escalated to Level {escalation_level}.
|
||||
|
||||
Details:
|
||||
- Title: {incident_title}
|
||||
- Severity: {incident_severity}
|
||||
- Status: {incident_status}
|
||||
- Created: {incident_created_at}
|
||||
- SLA Target: {sla_target_time}
|
||||
- Current On-Call: {current_oncall}
|
||||
|
||||
Please respond immediately.
|
||||
|
||||
View incident: {incident_url}
|
||||
''',
|
||||
'variables': [
|
||||
'incident_id', 'incident_title', 'incident_severity',
|
||||
'incident_status', 'incident_created_at', 'sla_target_time',
|
||||
'current_oncall', 'incident_url', 'escalation_level'
|
||||
],
|
||||
'is_active': True,
|
||||
'is_default': True,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {email_escalation.name}')
|
||||
|
||||
# SMS escalation template
|
||||
sms_escalation, created = NotificationTemplate.objects.get_or_create(
|
||||
name='SMS Escalation Alert',
|
||||
template_type='ESCALATION',
|
||||
channel_type='SMS',
|
||||
defaults={
|
||||
'subject_template': '',
|
||||
'body_template': 'URGENT: Incident #{incident_id} escalated to L{escalation_level}. {incident_title}. Respond now.',
|
||||
'variables': ['incident_id', 'incident_title', 'escalation_level'],
|
||||
'is_active': True,
|
||||
'is_default': True,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {sms_escalation.name}')
|
||||
|
||||
# SLA breach template
|
||||
sla_breach, created = NotificationTemplate.objects.get_or_create(
|
||||
name='SLA Breach Alert',
|
||||
template_type='SLA_BREACH',
|
||||
channel_type='EMAIL',
|
||||
defaults={
|
||||
'subject_template': 'SLA BREACH: Incident #{incident_id} - {incident_title}',
|
||||
'body_template': '''
|
||||
SLA BREACH ALERT
|
||||
|
||||
The SLA for incident #{incident_id} has been breached.
|
||||
|
||||
Details:
|
||||
- Incident: {incident_title}
|
||||
- SLA Type: {sla_type}
|
||||
- Target Time: {sla_target_time}
|
||||
- Breach Time: {breach_time}
|
||||
- Breach Duration: {breach_duration}
|
||||
|
||||
Immediate action required.
|
||||
''',
|
||||
'variables': [
|
||||
'incident_id', 'incident_title', 'sla_type',
|
||||
'sla_target_time', 'breach_time', 'breach_duration'
|
||||
],
|
||||
'is_active': True,
|
||||
'is_default': True,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {sla_breach.name}')
|
||||
|
||||
def create_sample_oncall_rotation(self, force):
|
||||
"""Create a sample on-call rotation if users exist"""
|
||||
self.stdout.write('Creating sample on-call rotation...')
|
||||
|
||||
# Check if we have any users
|
||||
if not User.objects.exists():
|
||||
self.stdout.write(' ⚠ No users found. Skipping on-call rotation creation.')
|
||||
return
|
||||
|
||||
# Get first user as admin
|
||||
admin_user = User.objects.first()
|
||||
|
||||
# Create sample rotation
|
||||
sample_rotation, created = OnCallRotation.objects.get_or_create(
|
||||
name='Primary On-Call Rotation',
|
||||
defaults={
|
||||
'description': 'Primary on-call rotation for incident response',
|
||||
'rotation_type': 'WEEKLY',
|
||||
'status': 'ACTIVE',
|
||||
'team_name': 'Incident Response Team',
|
||||
'team_description': 'Primary team responsible for incident response',
|
||||
'schedule_config': {
|
||||
'rotation_length_days': 7,
|
||||
'handoff_time': '09:00',
|
||||
'timezone': 'UTC'
|
||||
},
|
||||
'timezone': 'UTC',
|
||||
'external_system': 'INTERNAL',
|
||||
'created_by': admin_user,
|
||||
}
|
||||
)
|
||||
|
||||
if created or force:
|
||||
self.stdout.write(f' ✓ Created/Updated: {sample_rotation.name}')
|
||||
|
||||
# Create a sample assignment for the next week
|
||||
if User.objects.count() >= 2:
|
||||
users = list(User.objects.all()[:2])
|
||||
start_time = timezone.now()
|
||||
end_time = start_time + timedelta(days=7)
|
||||
|
||||
assignment, created = OnCallAssignment.objects.get_or_create(
|
||||
rotation=sample_rotation,
|
||||
user=users[0],
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
defaults={
|
||||
'status': 'ACTIVE',
|
||||
}
|
||||
)
|
||||
|
||||
if created:
|
||||
self.stdout.write(f' ✓ Created assignment for {users[0].username}')
|
||||
else:
|
||||
self.stdout.write(f' ⚠ Rotation already exists: {sample_rotation.name}')
|
||||
Reference in New Issue
Block a user