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

1#!/usr/bin/env python3 

2""" 

3Generate JSM SLA compliance report. 

4 

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""" 

11 

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 

20 

21sys.path.insert(0, str(Path(__file__).parent.parent.parent / 'shared' / 'scripts' / 'lib')) 

22 

23from config_manager import get_jira_client 

24from error_handler import print_error, JiraError, NotFoundError 

25 

26 

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") 

47 

48 # Search for issues 

49 results = client.search_issues(query, max_results=1000) 

50 issues = results.get('issues', []) 

51 

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', []) 

59 

60 # Filter by SLA name if specified 

61 if sla_name: 

62 slas = [s for s in slas if s.get('name') == sla_name] 

63 

64 # Filter breached only if specified 

65 if breached_only: 

66 slas = [s for s in slas if _is_breached(s)] 

67 

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 

78 

79 return { 

80 'total_issues': len(issues), 

81 'total_slas': len(report_data), 

82 'report_data': report_data 

83 } 

84 

85 

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 

95 

96 

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("") 

105 

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) 

112 

113 lines.append(f"{issue_key}: {summary[:60]}") 

114 lines.append(f" SLA: {sla_name} - {'BREACHED' if is_breached else 'OK'}") 

115 

116 return '\n'.join(lines) 

117 

118 

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']) 

124 

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 ]) 

132 

133 return output.getvalue() 

134 

135 

136def format_report_json(report: Dict[str, Any]) -> str: 

137 """Format report as JSON.""" 

138 return json.dumps(report, indent=2) 

139 

140 

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') 

151 

152 args = parser.parse_args() 

153 

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 ) 

163 

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)) 

170 

171 return 0 

172 

173 except Exception as e: 

174 print_error(f"Failed to generate report: {e}") 

175 return 1 

176 

177 

178if __name__ == '__main__': 

179 sys.exit(main())