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