Coverage for scripts / get_approvals.py: 17%

77 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-25 18:08 -0500

1#!/usr/bin/env python3 

2""" 

3Get approvals for a JSM request. 

4 

5This script retrieves approval requests for JSM requests, showing approval status, 

6approvers, and action hints for pending approvals. 

7""" 

8 

9import sys 

10import argparse 

11import json 

12from pathlib import Path 

13from datetime import datetime 

14 

15# Add shared lib to path 

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

17 

18from config_manager import get_jira_client 

19from error_handler import handle_errors 

20 

21 

22def get_jira_client(profile=None): 

23 """Get JIRA client (overridable for testing).""" 

24 from config_manager import get_jira_client as _get_client 

25 return _get_client(profile) 

26 

27 

28def format_approvals_table(approvals: list, issue_key: str) -> None: 

29 """Format approvals as table.""" 

30 if not approvals: 

31 print("No approvals found.") 

32 return 

33 

34 # Count by status 

35 pending_count = sum(1 for a in approvals if a.get('finalDecision') == 'pending') 

36 approved_count = sum(1 for a in approvals if a.get('finalDecision') == 'approve') 

37 declined_count = sum(1 for a in approvals if a.get('finalDecision') == 'decline') 

38 

39 print(f"\nApprovals for {issue_key} ({pending_count} pending, {approved_count} approved, {declined_count} declined):\n") 

40 

41 print(f"{'ID':<10} {'Name':<25} {'Status':<12} {'Approvers':<25} {'Created':<20} {'Completed'}") 

42 print("─" * 120) 

43 

44 for approval in approvals: 

45 approval_id = approval.get('id', 'unknown') 

46 name = approval.get('name', 'Unknown')[:24] 

47 decision = approval.get('finalDecision', 'unknown').upper() 

48 

49 # Format approvers 

50 approvers = approval.get('approvers', []) 

51 if len(approvers) == 1: 

52 approvers_str = approvers[0].get('displayName', 'Unknown')[:24] 

53 elif len(approvers) > 1: 

54 approvers_str = f"{approvers[0].get('displayName', 'Unknown')[:15]} (+{len(approvers)-1})" 

55 else: 

56 approvers_str = "None" 

57 

58 # Format dates 

59 created = approval.get('createdDate', '') 

60 completed = approval.get('completedDate') or '-' 

61 

62 try: 

63 created_dt = datetime.fromisoformat(created.replace('Z', '+00:00')) 

64 created_str = created_dt.strftime('%Y-%m-%d %H:%M') 

65 except: 

66 created_str = created[:16] if created else 'Unknown' 

67 

68 if completed != '-': 

69 try: 

70 completed_dt = datetime.fromisoformat(completed.replace('Z', '+00:00')) 

71 completed_str = completed_dt.strftime('%Y-%m-%d %H:%M') 

72 except: 

73 completed_str = completed[:16] 

74 else: 

75 completed_str = '-' 

76 

77 print(f"{approval_id:<10} {name:<25} {decision:<12} {approvers_str:<25} {created_str:<20} {completed_str}") 

78 

79 # Show action hints for pending approvals 

80 if pending_count > 0: 

81 print("\nTo approve/decline:") 

82 print(f" python approve_request.py {issue_key} --approval-id <APPROVAL-ID>") 

83 print(f" python decline_request.py {issue_key} --approval-id <APPROVAL-ID>") 

84 

85 

86@handle_errors 

87def main(args=None): 

88 """Main function.""" 

89 parser = argparse.ArgumentParser( 

90 description='Get approvals for JSM request', 

91 formatter_class=argparse.RawDescriptionHelpFormatter, 

92 epilog=""" 

93Examples: 

94 # Pending approvals (default) 

95 %(prog)s REQ-123 

96 

97 # All approvals (including completed) 

98 %(prog)s REQ-123 --all 

99 

100 # Specific approval 

101 %(prog)s REQ-123 --id 10050 

102 

103 # JSON output 

104 %(prog)s REQ-123 --output json 

105 """ 

106 ) 

107 

108 parser.add_argument('issue_key', 

109 help='Request key (e.g., REQ-123)') 

110 parser.add_argument('--id', 

111 help='Get specific approval by ID') 

112 parser.add_argument('--pending', action='store_true', 

113 help='Show only pending approvals (default)') 

114 parser.add_argument('--all', action='store_true', dest='show_all', 

115 help='Show all approvals (pending, approved, declined)') 

116 parser.add_argument('--output', choices=['text', 'json'], default='text', 

117 help='Output format (default: text)') 

118 parser.add_argument('--profile', 

119 help='JIRA profile to use') 

120 

121 parsed_args = parser.parse_args(args) 

122 

123 # Get JIRA client 

124 jira = get_jira_client(parsed_args.profile) 

125 

126 # Get specific approval by ID 

127 if parsed_args.id: 

128 approval = jira.get_request_approval(parsed_args.issue_key, parsed_args.id) 

129 if parsed_args.output == 'json': 

130 print(json.dumps(approval, indent=2)) 

131 else: 

132 format_approvals_table([approval], parsed_args.issue_key) 

133 return 0 

134 

135 # Get all approvals 

136 response = jira.get_request_approvals(parsed_args.issue_key, start=0, limit=100) 

137 approvals = response.get('values', []) 

138 

139 # Filter to pending only if not --all 

140 if not parsed_args.show_all: 

141 approvals = [a for a in approvals if a.get('finalDecision') == 'pending'] 

142 

143 # Output 

144 if parsed_args.output == 'json': 

145 print(json.dumps(approvals, indent=2)) 

146 else: 

147 format_approvals_table(approvals, parsed_args.issue_key) 

148 

149 return 0 

150 

151 

152if __name__ == '__main__': 

153 sys.exit(main())