1 from jira.client import JIRA
2 from jira.exceptions import JIRAError
3 from requests import ConnectionError, HTTPError
4
5
7 """
8 Helper class to connect and manipulate the data in Jira
9 """
10
11 client = None
12 clientOptions = {'server': 'https://issues.ypg.com'}
13 username = "ypgAutoTester"
14 password = "@ut0t3$t3r"
15 logger = None
16 project = None
17
19 """
20 Constructor for class
21
22 Args :
23 logger : (logger) instance of a logging object configured in testing project
24
25 """
26 self.logger = logger
27
29 """
30 Establishes a connection to our YPG Jira instance and assignes it to self.client
31
32 Returns : (bool) True if we are connected to Jira and False if we are not
33
34 """
35
36 try:
37 self.client = JIRA(self.clientOptions, basic_auth=(self.username, self.password))
38 self.client.session()
39 success = True
40
41 except ConnectionError, e:
42 self.logger.error("Error Connection to Jira :")
43 self.logger.error(e)
44 self.client = None
45 success = False
46 except HTTPError, e:
47 self.logger.error("Error Connection to Jira :")
48 self.logger.error(e)
49 self.client = None
50 success = False
51
52 return success
53
55 """
56 Returns a list of issues that were returned by Jira given the query you specified
57
58 Args : (str) A string representation of a JQL query. anything you can enter in Jira advanced search can be
59 entered here.
60
61 Returns : (ResultList) a list of jira issues
62 """
63 result_list = None
64 try:
65 result_list = self.client.search_issues(query)
66
67 except JIRAError, e:
68 self.logger.error("Could not search what you are looking for because " + str(e))
69
70 if len(result_list) < 1:
71 issues = None
72 elif len(result_list) > 1:
73 self.logger.warn(
74 '''Jira found more than one issue with the search %s . You may want to manually verify
75 the automated process updated the correct issue." % query)''')
76 issues = result_list[0]
77 else:
78 issues = result_list[0]
79 return issues
80
82 """
83 Confirms that the project version supplied actually exists in Jira for the specified project
84
85 Args :
86 projKey: (str) the Jira project acronym
87 buildVersion : (str) the version of the application you are testing
88
89 Returns : (bool) True if the version was found in Jira. False if the version was not found
90 """
91
92 try:
93
94 project = self.client.project(proj_key)
95 proj_versions = project.versions
96
97
98 for version in proj_versions:
99
100 if str(version.name) == str(build_version):
101 self.logger.debug("Matched the specidied buildVersion runtime parameter to version in Jira")
102 if not version.released and not version.archived:
103 self.logger.debug(
104 "We are going to start to test build version " + version.name + " for project " + proj_key)
105 return True
106 else:
107 self.logger.warn(
108 '''The buildVersion you are searching for has been released or archived in Jira
109 and is not a valid testable build version''')
110 return False
111 except JIRAError:
112 self.logger.error(
113 "Could not retrieve the projects versions. Check that the project exists or that Jira is not down")
114 return False
115
117 """
118 Fetches a list of fields that require values to open a new issue in a specified project
119
120 Args : (str) The Jira project acronym for the project you are querying
121
122 Returns : (dict) a dictionary containing the required fields and None values for each. Returns empty dict if
123 search failed.
124 """
125
126 req_fields = {}
127
128 try:
129
130 meta = self.client.createmeta(projectKeys=project_key, expand='projects.issuetypes.fields')
131
132 fields = meta['projects'][0]['issuetypes'][0]['fields']
133
134 for field in fields:
135 if fields[field]['required']:
136 req_fields[field] = None
137
138 except JIRAError, e:
139 self.logger.error("Could not get required fields for Jira project " + project_key + " because " + str(e))
140 except IndexError, e:
141 self.logger.error("Could not get required fields for Jira project " + project_key + " because " + str(e))
142
143 return req_fields
144
146 """
147 Creates an issue in Jira with the dictionary of data supplied. Be sure that data contains all required
148 fields before using this.
149
150 Args : (dict) dictionary of required fields and valid values
151
152 Returns : (bool) returns True if issues was created and False if there was a failure
153 """
154
155 try:
156 self.client.create_issue(fields=data)
157 success = True
158
159 except JIRAError, e:
160 success = False
161 self.logger.error("Issue was not created :" + str(e))
162
163 return success
164
166 """
167 Transitions a specified jira Bug from any resolved state back to In Review and assigns it to the
168 project lead with comments
169
170 Args :
171 issue : (issue) an issue object that came from Jira. Use searchForIssue first before reopening issues
172 proj_key : (str) the Jira project acronym from Jira
173 version : (str) the build number currently under test where the bug was rediscovered
174
175 Returns : (bool) returns False if we could not reopen issue and True if we could
176 """
177 cur_state = issue.fields.status.name
178
179 transitions = self.client.transitions(issue)
180
181 project = self.client.project(proj_key)
182 proj_lead = project.lead.name
183
184 version = version
185
186 comment = "This issue has reoccured in the latest version %s" % version
187
188 try:
189 if cur_state == "Closed":
190 self.client.transition_issue(issue, self.get_transition_id(transitions, "Re-Open"),
191 assignee={'name': proj_lead})
192 self.client.add_comment(issue, comment)
193 elif cur_state == "Ready for QA":
194 self.client.transition_issue(issue, self.get_transition_id(transitions, "Back to In Development"),
195 assignee={'name': proj_lead})
196 self.client.add_comment(issue, comment)
197 elif cur_state == "In Testing":
198 self.client.transition_issue(issue, self.get_transition_id(transitions, "Fail"),
199 assignee={'name': proj_lead})
200 self.client.add_comment(issue, comment)
201 elif cur_state == "Ready to Deploy":
202 self.client.transition_issue(issue, self.get_transition_id(transitions, "Back to In Testing"))
203 transitions = self.client.transitions(issue)
204 self.client.transition_issue(issue, self.get_transition_id(transitions, "Fail"),
205 assignee={'name': proj_lead})
206 self.client.add_comment(issue, comment)
207 except IndexError:
208 self.logger.error("Could not find a transition to reopen the issue '" + issue.key + "'")
209 return False
210 except JIRAError, e:
211 self.logger.error("Jira returned error when modifying issue '" + issue.key + "' because " + str(e))
212 return False
213
214 return True
215
216
218 """
219 Fetch the id for a transition's name
220
221 Args :
222 trans_dict : (dict) a dictionary of transitions fetched from Jira for a given issue
223 trans_name : (str) name of the Jira transition you would like the id for
224
225 Returns : (str) a numeric id associtated to the transition name
226 """
227
228 id_dict = [element['id'] for element in trans_dict if element['name'] == trans_name]
229 return id_dict[0]
230
231
232 - def prepare_issue_data(self, req_fields, test_id, project, summary, description, component, severity, version):
233 """
234 Constructs a properly formatted dictionary of data to supply to Jira for opening an issue. Creates a bug
235 in the specified project After construction, it will validate the dictionary by checking if all required
236 fields are filled
237
238 Args :
239 req_fields : (dict) dictionary of jira project required fields. Construct the dict with getRequiredFields
240 test_id : (str)the name of the test cases found in the test case's decoreator in python or the name in Spiratest
241 project : (str) the Jira project acronym for the project you wish to open a bug inspect
242 summary : (str) the summary of the bug you wish to open
243 description : (str) the description of the bug you wish to open
244 component : (str) the component of the bug you wish to open
245 severity : (str) the severity of the bug you wish to open
246 version : (str) the affected version of the bug you wish to open
247
248 Returns : (dict) if the dictionary properly complies to all the required fields.
249 Returns emtpy dict if it does not.
250
251 """
252 desc = '''This issue was created by YPG automated Test Case : %(test_id)s. \n \n The error is caused when
253 sending the following parameters to the API method in question : \n %(description)s''' % \
254 {'description': description, 'test_id': test_id}
255
256 req_fields['project'] = {'key': project}
257 req_fields['summary'] = summary
258 req_fields['description'] = desc
259 req_fields['issuetype'] = {'name': 'Bug'}
260 req_fields['customfield_10411'] = {'value': severity}
261 req_fields['components'] = [{'name': component}]
262 req_fields['versions'] = [{'name': version}]
263
264
265 if None in req_fields.values():
266 req_fields = {}
267
268 return req_fields
269