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