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

1#!/usr/bin/env python3 

2""" 

3Transition a JSM service request to new status. 

4 

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

11 

12import sys 

13import os 

14import argparse 

15import json 

16from pathlib import Path 

17from typing import Optional, Dict, Any 

18 

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

20 

21from config_manager import get_jira_client 

22from error_handler import print_error, JiraError, NotFoundError 

23from formatters import print_success 

24 

25 

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. 

33 

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 

41 

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] 

51 

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 ) 

58 

59 transition_id = matching[0]['id'] 

60 

61 if not transition_id: 

62 raise ValueError("Either transition_id or transition_name must be provided") 

63 

64 client.transition_request(issue_key, transition_id, comment=comment, public=public) 

65 

66 

67def list_transitions(issue_key: str, profile: Optional[str] = None) -> list: 

68 """ 

69 List available transitions for a request. 

70 

71 Args: 

72 issue_key: Request key 

73 profile: JIRA profile to use 

74 

75 Returns: 

76 List of available transitions 

77 """ 

78 with get_jira_client(profile) as client: 

79 return client.get_request_transitions(issue_key) 

80 

81 

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" 

92 

93 By transition ID: 

94 %(prog)s SD-101 --transition-id 11 

95 

96 With comment: 

97 %(prog)s SD-101 --to "Resolved" --comment "Issue fixed by restarting server" 

98 

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 

102 

103 Show available transitions: 

104 %(prog)s SD-101 --show-transitions 

105 """ 

106 ) 

107 

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

126 

127 args = parser.parse_args() 

128 

129 try: 

130 # Show transitions 

131 if args.show_transitions: 

132 transitions = list_transitions(args.request_key, args.profile) 

133 

134 print(f"\nAvailable transitions for {args.request_key}:\n") 

135 print(f"{'ID':<6} {'Name':<30} {'To Status'}") 

136 print("-" * 60) 

137 

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

143 

144 return 0 

145 

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 

150 

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 

157 

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 

170 

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 ) 

179 

180 print_success(f"Request {args.request_key} transitioned successfully!") 

181 

182 if args.comment: 

183 visibility = "public" if public else "internal" 

184 print(f"Comment added ({visibility}): {args.comment}") 

185 

186 return 0 

187 

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 

197 

198 

199if __name__ == '__main__': 

200 sys.exit(main())