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
126 @return: a dictionary containing the required fields and None values for each. Returns empty dict if
127 search failed.
128 @rtype: dict
129 """
130
131 req_fields = {}
132
133 try:
134
135 meta = self.client.createmeta(projectKeys=project_key, expand='projects.issuetypes.fields')
136
137 fields = meta['projects'][0]['issuetypes'][0]['fields']
138
139 for field in fields:
140 if fields[field]['required']:
141 req_fields[field] = None
142
143 except JIRAError, e:
144 self.logger.error("Could not get required fields for Jira project " + project_key + " because " + str(e))
145 except IndexError, e:
146 self.logger.error("Could not get required fields for Jira project " + project_key + " because " + str(e))
147
148 return req_fields
149
151 """
152 Creates an issue in Jira with the dictionary of data supplied. Be sure that data contains all required
153 fields before using this.
154
155 @param data: dictionary of required fields and valid values
156 @type data: dict
157
158 @return: returns True if issues was created and False if there was a failure
159 @rtype: bool
160 """
161
162 try:
163 self.client.create_issue(fields=data)
164 success = True
165
166 except JIRAError, e:
167 success = False
168 self.logger.error("Issue was not created :" + str(e))
169
170 return success
171
173 """
174 Transitions a specified jira Bug from any resolved state back to In Review and assigns it to the
175 project lead with comments
176
177 @param issue: an issue object that came from Jira. Use searchForIssue first before reopening issues
178 @type issue: issue
179 @param proj_key: the Jira project acronym from Jira
180 @type proj_key: str
181 @param version: the build number currently under test where the bug was rediscovered
182 @type version: str
183
184 @return: returns False if we could not reopen issue and True if we could
185 @rtype: bool
186 """
187 cur_state = issue.fields.status.name
188
189 transitions = self.client.transitions(issue)
190
191 project = self.client.project(proj_key)
192 proj_lead = project.lead.name
193
194 version = version
195
196 comment = "This issue has reoccured in the latest version %s" % version
197
198 try:
199 if cur_state == "Closed":
200 self.client.transition_issue(issue, self.get_transition_id(transitions, "Re-Open"),
201 assignee={'name': proj_lead})
202 self.client.add_comment(issue, comment)
203 elif cur_state == "Ready for QA":
204 self.client.transition_issue(issue, self.get_transition_id(transitions, "Back to In Development"),
205 assignee={'name': proj_lead})
206 self.client.add_comment(issue, comment)
207 elif cur_state == "In Testing":
208 self.client.transition_issue(issue, self.get_transition_id(transitions, "Fail"),
209 assignee={'name': proj_lead})
210 self.client.add_comment(issue, comment)
211 elif cur_state == "Ready to Deploy":
212 self.client.transition_issue(issue, self.get_transition_id(transitions, "Back to In Testing"))
213 transitions = self.client.transitions(issue)
214 self.client.transition_issue(issue, self.get_transition_id(transitions, "Fail"),
215 assignee={'name': proj_lead})
216 self.client.add_comment(issue, comment)
217 except IndexError:
218 self.logger.error("Could not find a transition to reopen the issue '" + issue.key + "'")
219 return False
220 except JIRAError, e:
221 self.logger.error("Jira returned error when modifying issue '" + issue.key + "' because " + str(e))
222 return False
223
224 return True
225
226
228 """
229 Fetch the id for a transition's name
230
231 @param trans_dict: a dictionary of transitions fetched from Jira for a given issue
232 @type trans_dict: dict
233 @param trans_dict: name of the Jira transition you would like the id for
234 @type trans_name: str
235
236 @return: a numeric id associtated to the transition name
237 @rtype: str
238 """
239
240 id_dict = [element['id'] for element in trans_dict if element['name'] == trans_name]
241 return id_dict[0]
242
243
244 - def prepare_issue_data(self, req_fields, test_id, project, summary, description, component, severity, version):
245 """
246 Constructs a properly formatted dictionary of data to supply to Jira for opening an issue. Creates a bug
247 in the specified project After construction, it will validate the dictionary by checking if all required
248 fields are filled
249
250 @param req_fields: dictionary of jira project required fields. Construct the dict with getRequiredFields
251 @type req_fields: dict
252 @param test_id: the name of the test cases found in the test case's decoreator in python or the name in Spiratest
253 @type test_id: str
254 @param project: the Jira project acronym for the project you wish to open a bug inspect
255 @type project: str
256 @param summary: the summary of the bug you wish to open
257 @type summary: str
258 @param description: the description of the bug you wish to open
259 @type description: str
260 @param component: the component of the bug you wish to open
261 @type component: str
262 @param severity: the severity of the bug you wish to open
263 @type severity: str
264 @param version: the affected version of the bug you wish to open
265 @type version: str
266
267 @return: if the dictionary properly complies to all the required fields.
268 Returns emtpy dict if it does not.
269 @rtype: dict
270 """
271 desc = '''This issue was created by YPG automated Test Case : %(test_id)s. \n \n The error is caused when
272 sending the following parameters to the API method in question : \n %(description)s''' % \
273 {'description': description, 'test_id': test_id}
274
275 req_fields['project'] = {'key': project}
276 req_fields['summary'] = summary
277 req_fields['description'] = desc
278 req_fields['issuetype'] = {'name': 'Bug'}
279 req_fields['customfield_10411'] = {'value': severity}
280 req_fields['components'] = [{'name': component}]
281 req_fields['versions'] = [{'name': version}]
282
283
284 if None in req_fields.values():
285 req_fields = {}
286
287 return req_fields
288