Coverage for scripts / transition_request.py: 16%
86 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"""
3Transition a JSM service request to new status.
5Usage:
6 python transition_request.py SD-101 --to "In Progress"
7 python transition_request.py SD-101 --transition-id 11
8 python transition_request.py SD-101 --to "Resolved" --comment "Issue fixed" --public
9 python transition_request.py SD-101 --show-transitions
10"""
12import sys
13import os
14import argparse
15import json
16from pathlib import Path
17from typing import Optional, Dict, Any
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
23from formatters import print_success
26def transition_service_request(issue_key: str, transition_id: Optional[str] = None,
27 transition_name: Optional[str] = None,
28 comment: Optional[str] = None,
29 public: bool = True,
30 profile: Optional[str] = None) -> None:
31 """
32 Transition a service request.
34 Args:
35 issue_key: Request key
36 transition_id: Transition ID (optional if transition_name provided)
37 transition_name: Transition name to lookup
38 comment: Optional comment to add
39 public: Whether comment is public (customer-visible)
40 profile: JIRA profile to use
42 Raises:
43 ValueError: If transition not found
44 NotFoundError: If request doesn't exist
45 """
46 with get_jira_client(profile) as client:
47 # Lookup transition ID if name provided
48 if transition_name and not transition_id:
49 transitions = client.get_request_transitions(issue_key)
50 matching = [t for t in transitions if t['name'] == transition_name]
52 if not matching:
53 available = [t['name'] for t in transitions]
54 raise ValueError(
55 f"Transition '{transition_name}' not found. "
56 f"Available: {', '.join(available)}"
57 )
59 transition_id = matching[0]['id']
61 if not transition_id:
62 raise ValueError("Either transition_id or transition_name must be provided")
64 client.transition_request(issue_key, transition_id, comment=comment, public=public)
67def list_transitions(issue_key: str, profile: Optional[str] = None) -> list:
68 """
69 List available transitions for a request.
71 Args:
72 issue_key: Request key
73 profile: JIRA profile to use
75 Returns:
76 List of available transitions
77 """
78 with get_jira_client(profile) as client:
79 return client.get_request_transitions(issue_key)
82def main():
83 """Main entry point."""
84 parser = argparse.ArgumentParser(
85 description='Transition a JSM service request',
86 formatter_class=argparse.RawDescriptionHelpFormatter,
87 epilog="""
88Examples:
89 By transition name:
90 %(prog)s SD-101 --to "In Progress"
91 %(prog)s SD-101 --to "Resolved"
93 By transition ID:
94 %(prog)s SD-101 --transition-id 11
96 With comment:
97 %(prog)s SD-101 --to "Resolved" --comment "Issue fixed by restarting server"
99 Public vs internal comment:
100 %(prog)s SD-101 --to "Waiting for customer" --comment "Please provide more details" --public
101 %(prog)s SD-101 --to "In Progress" --comment "Escalating to L2 support" --internal
103 Show available transitions:
104 %(prog)s SD-101 --show-transitions
105 """
106 )
108 parser.add_argument('request_key',
109 help='Request key (e.g., SD-101)')
110 parser.add_argument('--to', '--transition-name', dest='transition_name',
111 help='Transition name')
112 parser.add_argument('--transition-id',
113 help='Transition ID')
114 parser.add_argument('--comment',
115 help='Comment to add during transition')
116 parser.add_argument('--public', action='store_true', default=None,
117 help='Make comment public (customer-visible)')
118 parser.add_argument('--internal', action='store_true',
119 help='Make comment internal (agent-only)')
120 parser.add_argument('--show-transitions', action='store_true',
121 help='Show available transitions and exit')
122 parser.add_argument('--dry-run', action='store_true',
123 help='Show what would be done without doing it')
124 parser.add_argument('--profile',
125 help='JIRA profile to use from config')
127 args = parser.parse_args()
129 try:
130 # Show transitions
131 if args.show_transitions:
132 transitions = list_transitions(args.request_key, args.profile)
134 print(f"\nAvailable transitions for {args.request_key}:\n")
135 print(f"{'ID':<6} {'Name':<30} {'To Status'}")
136 print("-" * 60)
138 for t in transitions:
139 tid = t.get('id', 'N/A')
140 name = t.get('name', 'N/A')
141 to_status = t.get('to', {}).get('name', 'N/A')
142 print(f"{tid:<6} {name:<30} {to_status}")
144 return 0
146 # Validate transition arguments
147 if not args.transition_name and not args.transition_id:
148 print_error("Either --to or --transition-id must be provided")
149 return 1
151 # Determine comment visibility
152 public = True
153 if args.internal:
154 public = False
155 elif args.public is not None:
156 public = args.public
158 if args.dry_run:
159 print("DRY RUN MODE - No changes will be made\n")
160 print(f"Would transition request {args.request_key}:")
161 if args.transition_name:
162 print(f" To: {args.transition_name}")
163 if args.transition_id:
164 print(f" Transition ID: {args.transition_id}")
165 if args.comment:
166 visibility = "Public (customer-visible)" if public else "Internal (agent-only)"
167 print(f" Comment: {args.comment}")
168 print(f" Visibility: {visibility}")
169 return 0
171 transition_service_request(
172 issue_key=args.request_key,
173 transition_id=args.transition_id,
174 transition_name=args.transition_name,
175 comment=args.comment,
176 public=public,
177 profile=args.profile
178 )
180 print_success(f"Request {args.request_key} transitioned successfully!")
182 if args.comment:
183 visibility = "public" if public else "internal"
184 print(f"Comment added ({visibility}): {args.comment}")
186 return 0
188 except ValueError as e:
189 print_error(str(e))
190 return 1
191 except (JiraError, NotFoundError) as e:
192 print_error(f"Failed to transition request: {e}")
193 return 1
194 except Exception as e:
195 print_error(f"Unexpected error: {e}")
196 return 1
199if __name__ == '__main__':
200 sys.exit(main())