Coverage for scripts / sla_report.py: 19%
96 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-25 18:08 -0500
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-25 18:08 -0500
1#!/usr/bin/env python3
2"""
3Generate JSM SLA compliance report.
5Usage:
6 python sla_report.py --project SD
7 python sla_report.py --service-desk 1
8 python sla_report.py --jql "project = SD AND created >= -7d"
9 python sla_report.py --project SD --output csv > sla_report.csv
10"""
12import sys
13import os
14import argparse
15import json
16import csv
17from io import StringIO
18from pathlib import Path
19from typing import Optional, Dict, Any, List
21sys.path.insert(0, str(Path(__file__).parent.parent.parent / 'shared' / 'scripts' / 'lib'))
23from config_manager import get_jira_client
24from error_handler import print_error, JiraError, NotFoundError
27def generate_sla_report(project: Optional[str] = None,
28 service_desk_id: Optional[int] = None,
29 jql: Optional[str] = None,
30 sla_name: Optional[str] = None,
31 breached_only: bool = False,
32 profile: Optional[str] = None) -> Dict[str, Any]:
33 """Generate SLA compliance report."""
34 with get_jira_client(profile) as client:
35 # Build JQL query
36 if jql:
37 query = jql
38 elif project:
39 query = f"project = {project}"
40 elif service_desk_id:
41 # Get service desk to find project key
42 sd = client.get_service_desk(str(service_desk_id))
43 project_key = sd.get('projectKey')
44 query = f"project = {project_key}"
45 else:
46 raise ValueError("Must specify --project, --service-desk, or --jql")
48 # Search for issues
49 results = client.search_issues(query, max_results=1000)
50 issues = results.get('issues', [])
52 # Collect SLA data for each issue
53 report_data = []
54 for issue in issues:
55 issue_key = issue.get('key')
56 try:
57 sla_data = client.get_request_slas(issue_key)
58 slas = sla_data.get('values', [])
60 # Filter by SLA name if specified
61 if sla_name:
62 slas = [s for s in slas if s.get('name') == sla_name]
64 # Filter breached only if specified
65 if breached_only:
66 slas = [s for s in slas if _is_breached(s)]
68 if slas:
69 for sla in slas:
70 report_data.append({
71 'issue_key': issue_key,
72 'summary': issue.get('fields', {}).get('summary'),
73 'sla': sla
74 })
75 except Exception:
76 # Skip issues without SLA data
77 pass
79 return {
80 'total_issues': len(issues),
81 'total_slas': len(report_data),
82 'report_data': report_data
83 }
86def _is_breached(sla: Dict[str, Any]) -> bool:
87 """Check if SLA is breached."""
88 ongoing = sla.get('ongoingCycle')
89 if ongoing and ongoing.get('breached'):
90 return True
91 completed = sla.get('completedCycles', [])
92 if completed and completed[-1].get('breached'):
93 return True
94 return False
97def format_report_text(report: Dict[str, Any]) -> str:
98 """Format report as text."""
99 lines = []
100 lines.append("\nSLA Compliance Report")
101 lines.append("=" * 80)
102 lines.append(f"Total Issues: {report['total_issues']}")
103 lines.append(f"Total SLA Metrics: {report['total_slas']}")
104 lines.append("")
106 for entry in report['report_data'][:10]: # Show top 10
107 issue_key = entry['issue_key']
108 summary = entry['summary']
109 sla = entry['sla']
110 sla_name = sla.get('name')
111 is_breached = _is_breached(sla)
113 lines.append(f"{issue_key}: {summary[:60]}")
114 lines.append(f" SLA: {sla_name} - {'BREACHED' if is_breached else 'OK'}")
116 return '\n'.join(lines)
119def format_report_csv(report: Dict[str, Any]) -> str:
120 """Format report as CSV."""
121 output = StringIO()
122 writer = csv.writer(output)
123 writer.writerow(['Request Key', 'Summary', 'SLA Name', 'Breached'])
125 for entry in report['report_data']:
126 writer.writerow([
127 entry['issue_key'],
128 entry['summary'],
129 entry['sla'].get('name'),
130 'Yes' if _is_breached(entry['sla']) else 'No'
131 ])
133 return output.getvalue()
136def format_report_json(report: Dict[str, Any]) -> str:
137 """Format report as JSON."""
138 return json.dumps(report, indent=2)
141def main():
142 """Main entry point."""
143 parser = argparse.ArgumentParser(description='Generate JSM SLA compliance report')
144 parser.add_argument('--project', help='Project key')
145 parser.add_argument('--service-desk', type=int, help='Service desk ID')
146 parser.add_argument('--jql', help='Custom JQL query')
147 parser.add_argument('--sla-name', help='Filter to specific SLA')
148 parser.add_argument('--breached-only', action='store_true', help='Only breached SLAs')
149 parser.add_argument('--output', choices=['text', 'csv', 'json'], default='text')
150 parser.add_argument('--profile', help='JIRA profile to use')
152 args = parser.parse_args()
154 try:
155 report = generate_sla_report(
156 project=args.project,
157 service_desk_id=args.service_desk,
158 jql=args.jql,
159 sla_name=args.sla_name,
160 breached_only=args.breached_only,
161 profile=args.profile
162 )
164 if args.output == 'json':
165 print(format_report_json(report))
166 elif args.output == 'csv':
167 print(format_report_csv(report))
168 else:
169 print(format_report_text(report))
171 return 0
173 except Exception as e:
174 print_error(f"Failed to generate report: {e}")
175 return 1
178if __name__ == '__main__':
179 sys.exit(main())