Coverage for scripts / get_request_status.py: 18%
101 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"""
3Get JSM request status history.
5Usage:
6 python get_request_status.py SD-101
7 python get_request_status.py SD-101 --show-durations
8 python get_request_status.py SD-101 --output json
9"""
11import sys
12import os
13import argparse
14import json
15from pathlib import Path
16from typing import Optional, Dict, Any, List
17import time
19sys.path.insert(0, str(Path(__file__).parent.parent.parent / 'shared' / 'scripts' / 'lib'))
21from config_manager import get_jira_client
22from error_handler import print_error, JiraError, NotFoundError
25def get_status_history(issue_key: str, profile: Optional[str] = None) -> Dict[str, Any]:
26 """
27 Get request status history.
29 Args:
30 issue_key: Request key
31 profile: JIRA profile to use
33 Returns:
34 Status history data
36 Raises:
37 NotFoundError: If request doesn't exist
38 """
39 with get_jira_client(profile) as client:
40 return client.get_request_status(issue_key)
43def format_duration(millis: int) -> str:
44 """Format milliseconds to human-readable duration."""
45 hours = millis // 3600000
46 minutes = (millis % 3600000) // 60000
48 if hours > 0:
49 return f"{hours}h {minutes}m"
50 else:
51 return f"{minutes}m"
54def calculate_durations(statuses: List[Dict[str, Any]]) -> Dict[str, int]:
55 """
56 Calculate time spent in each status.
58 Args:
59 statuses: List of status history entries
61 Returns:
62 Dictionary mapping status names to duration in milliseconds
63 """
64 durations = {}
65 current_time = int(time.time() * 1000)
67 for i, status in enumerate(statuses):
68 status_name = status['status']
69 start_time = status['statusDate']['epochMillis']
71 if i + 1 < len(statuses):
72 end_time = statuses[i + 1]['statusDate']['epochMillis']
73 else:
74 # Current status - use now
75 end_time = current_time
77 duration = end_time - start_time
78 durations[status_name] = duration
80 return durations
83def calculate_metrics(statuses: List[Dict[str, Any]]) -> Dict[str, Any]:
84 """
85 Calculate status change metrics.
87 Args:
88 statuses: List of status history entries
90 Returns:
91 Dictionary with metrics
92 """
93 if not statuses:
94 return {}
96 durations = calculate_durations(statuses)
97 total_time = sum(durations.values())
99 # Time to first response (first status change)
100 time_to_first_response = None
101 if len(statuses) > 1:
102 time_to_first_response = statuses[1]['statusDate']['epochMillis'] - \
103 statuses[0]['statusDate']['epochMillis']
105 # Time to resolution (if resolved)
106 time_to_resolution = None
107 last_status = statuses[-1]
108 if last_status.get('statusCategory') == 'DONE':
109 time_to_resolution = total_time
111 return {
112 'total_time': total_time,
113 'status_changes': len(statuses),
114 'time_to_first_response': time_to_first_response,
115 'time_to_resolution': time_to_resolution
116 }
119def format_timeline(statuses: List[Dict[str, Any]], show_durations: bool = False) -> str:
120 """Format status history as timeline."""
121 if not statuses:
122 return "No status history found."
124 lines = []
125 header = f"\n{'Status':<25} {'Category':<15} {'Changed':<25}"
126 if show_durations:
127 header += f" {'Duration'}"
128 lines.append(header)
130 lines.append("-" * 80)
132 durations = calculate_durations(statuses) if show_durations else {}
134 for status in statuses:
135 status_name = status.get('status', 'N/A')
136 category = status.get('statusCategory', 'N/A')
137 changed = status.get('statusDate', {}).get('friendly', 'N/A')
139 line = f"{status_name:<25} {category:<15} {changed:<25}"
141 if show_durations and status_name in durations:
142 duration = format_duration(durations[status_name])
143 line += f" {duration}"
145 lines.append(line)
147 # Add metrics
148 if show_durations:
149 metrics = calculate_metrics(statuses)
151 lines.append('')
152 lines.append(f"Total Time: {format_duration(metrics['total_time'])}")
153 if metrics.get('time_to_first_response'):
154 lines.append(f"Time to First Response: {format_duration(metrics['time_to_first_response'])}")
155 if metrics.get('time_to_resolution'):
156 lines.append(f"Time to Resolution: {format_duration(metrics['time_to_resolution'])}")
157 lines.append(f"Status Changes: {metrics['status_changes']}")
159 return '\n'.join(lines)
162def format_json(statuses: List[Dict[str, Any]]) -> str:
163 """Format status history as JSON."""
164 return json.dumps(statuses, indent=2)
167def main():
168 """Main entry point."""
169 parser = argparse.ArgumentParser(
170 description='Get JSM request status history',
171 formatter_class=argparse.RawDescriptionHelpFormatter,
172 epilog="""
173Examples:
174 Basic usage:
175 %(prog)s SD-101
177 With durations:
178 %(prog)s SD-101 --show-durations
180 JSON output:
181 %(prog)s SD-101 --output json
182 """
183 )
185 parser.add_argument('request_key',
186 help='Request key (e.g., SD-101)')
187 parser.add_argument('--show-durations', action='store_true',
188 help='Show time spent in each status')
189 parser.add_argument('--output', choices=['text', 'json'], default='text',
190 help='Output format (default: text)')
191 parser.add_argument('--profile',
192 help='JIRA profile to use from config')
194 args = parser.parse_args()
196 try:
197 history = get_status_history(args.request_key, args.profile)
199 statuses = history.get('values', [])
201 if args.output == 'json':
202 print(format_json(statuses))
203 else:
204 print(f"Status History for {args.request_key}:")
205 print(format_timeline(statuses, show_durations=args.show_durations))
207 return 0
209 except NotFoundError as e:
210 print_error(f"Request not found: {e}")
211 return 1
212 except JiraError as e:
213 print_error(f"Failed to get status history: {e}")
214 return 1
215 except Exception as e:
216 print_error(f"Unexpected error: {e}")
217 return 1
220if __name__ == '__main__':
221 sys.exit(main())