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