Merge ~weichenwu/bugit:file-issue-to-JIRA into bugit:master
- Git
- lp:~weichenwu/bugit
- file-issue-to-JIRA
- Merge into master
Proposed by
Weichen Wu
Status: | Merged |
---|---|
Approved by: | StanleyHuang |
Approved revision: | eec56427f0b85971ada3d999ed5bdf8b9aea12fb |
Merged at revision: | c796f1d2041fac6aadf4e1ce578e73b012fd1d8e |
Proposed branch: | ~weichenwu/bugit:file-issue-to-JIRA |
Merge into: | bugit:master |
Diff against target: |
1219 lines (+618/-265) 6 files modified
README.md (+26/-4) bugit/__init__.py (+55/-45) bugit/bug_assistant.py (+143/-211) bugit/helper/jira_helper.py (+151/-0) bugit/helper/launchpad_helper.py (+234/-0) bugit/ui.py (+9/-5) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
StanleyHuang | Approve | ||
HanhsuanLee | Approve | ||
PeiYao Chang | Pending | ||
OEM Services QA | Pending | ||
Review via email: mp+461788@code.launchpad.net |
Commit message
Description of the change
Provide an option to file an issue on JIRA
Note:
By default, the issue will go to Launchpad as we still mainly use Launchpad at the moment.
To post a comment you must log in.
Revision history for this message
StanleyHuang (stanley31) wrote : | # |
Revision history for this message
StanleyHuang (stanley31) : | # |
review:
Needs Information
Revision history for this message
HanhsuanLee (hanhsuan) wrote : | # |
Please see my inline comment
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/README.md b/README.md |
2 | index d6332a2..9ceaf40 100644 |
3 | --- a/README.md |
4 | +++ b/README.md |
5 | @@ -18,7 +18,7 @@ $ snap install bugit --devmode |
6 | |
7 | |
8 | Usage |
9 | ------ |
10 | +------------ |
11 | |
12 | $ sudo bugit |
13 | |
14 | @@ -26,9 +26,24 @@ You will be presented with a text user interface (TUI) to fill the important |
15 | information (bug title, description, status, etc.). |
16 | |
17 | You can navigate between the different fields using the `up` and `down` keys |
18 | -and press `Alt+Enter` to submit the issue. It will be created on Launchpad, and |
19 | -logs will be gathered (using reporting tool `sosreport`) and attached to the |
20 | -report. |
21 | +and press `Alt+Enter` to submit the issue. All logs will be gathered |
22 | +(using reporting tool `sosreport`) and attached to the report. |
23 | + |
24 | +By default, the issue will be created on Launchpad. |
25 | +If you wish to create issue on JIRA, use: |
26 | + |
27 | +$ sudo bugit --jira |
28 | + |
29 | +Note: for issus to jira, the assignee could be entered partial unitl it has only one match |
30 | +For example, user email is weichen.wu@canonical.com |
31 | +You can enter "wei", it will output |
32 | +""" |
33 | +weichen.wu@canonical.com |
34 | +weii.wang@canonical.com |
35 | +yao.wei@canonical.com |
36 | +""" |
37 | + |
38 | +By entering "weichen", it will only match the first one hence successfully assigned |
39 | |
40 | If you want to submit your issue to the Staging instance of Launchpad, use: |
41 | |
42 | @@ -48,3 +63,10 @@ $ sudo bugit -p checkbox-ng -t "ce-qa-concern audio hdmi" |
43 | If you want to reopen an issue, use: |
44 | |
45 | $ sudo bugit -p checkbox-ng -r 2000334 |
46 | + |
47 | + |
48 | +Reference |
49 | +------------ |
50 | +1. How to access a file in /var/lib |
51 | +Refer to this topic for details |
52 | +https://forum.snapcraft.io/t/read-access-to-a-file-in-var-lib-on-the-host/11766 |
53 | \ No newline at end of file |
54 | diff --git a/bugit/__init__.py b/bugit/__init__.py |
55 | index cd4d8eb..40d77fc 100644 |
56 | --- a/bugit/__init__.py |
57 | +++ b/bugit/__init__.py |
58 | @@ -3,10 +3,11 @@ import logging |
59 | import os |
60 | import sys |
61 | |
62 | -from bugit.bug_assistant import BugAssistant, BugReport |
63 | +from bugit.bug_assistant import BugReport |
64 | from bugit.ui import ( |
65 | JobsScreen, SessionsScreen, ReportScreen, ReopenReportScreen |
66 | ) |
67 | + |
68 | import bugit.utils |
69 | import bugit.checkbox_utils |
70 | |
71 | @@ -16,7 +17,8 @@ logger = logging.getLogger(__name__) |
72 | |
73 | def main(): |
74 | if os.geteuid() != 0: |
75 | - sys.exit("This tool MUST be run as super user. Please run `sudo bugit`.") |
76 | + sys.exit("This tool MUST be run as super user. \ |
77 | + Please run `sudo bugit`.") |
78 | snap_name = os.getenv("SNAP_NAME") |
79 | |
80 | if not snap_name: |
81 | @@ -42,7 +44,9 @@ def main(): |
82 | numeric_level = getattr(logging, loglevel.upper(), None) |
83 | if not isinstance(numeric_level, int): |
84 | raise ValueError("Invalid log level: {}".format(loglevel)) |
85 | - logging.basicConfig(filename=logfile, format=logformat, level=numeric_level) |
86 | + logging.basicConfig(filename=logfile, |
87 | + format=logformat, |
88 | + level=numeric_level) |
89 | |
90 | epilog = ( |
91 | "Examples: `bugit --fwts -p somerville -t platform-tag-3` will " |
92 | @@ -55,10 +59,12 @@ def main(): |
93 | parser = argparse.ArgumentParser(epilog=epilog, argument_default="") |
94 | parser.add_argument( |
95 | "--fwts", |
96 | - help=("Automatically files an issue in " "Launchpad for the firmware team"), |
97 | + help=("Automatically files an issue in " |
98 | + "Launchpad for the firmware team"), |
99 | action="store_true", |
100 | ) |
101 | - parser.add_argument("-a", "--assignee", help="Assignee e.g. your Launchpad ID") |
102 | + parser.add_argument("-a", "--assignee", |
103 | + help="Assignee e.g. Launchpad ID or Jira account name") |
104 | parser.add_argument("-p", "--project", help="Project name") |
105 | parser.add_argument("-s", "--series", help="Series e.g. focal") |
106 | parser.add_argument( |
107 | @@ -72,6 +78,11 @@ def main(): |
108 | "-r", "--reopen", help="Bug number you want to reopen", |
109 | type=str, default="" |
110 | ) |
111 | + parser.add_argument( |
112 | + "--jira", |
113 | + help=("File bug to JIRA"), |
114 | + action="store_true", |
115 | + ) |
116 | args = parser.parse_args() |
117 | if args.fwts: |
118 | if not args.project: |
119 | @@ -84,15 +95,16 @@ def main(): |
120 | if not args.project: |
121 | sys.exit("You must specify a project name, e.g -p somerville") |
122 | |
123 | - if not args.reopen.isnumeric(): |
124 | - sys.exit("Invalid bug number format: {}".format(args.reopen)) |
125 | + # if not args.reopen.isnumeric(): |
126 | + # sys.exit("Invalid bug number format: {}".format(args.reopen)) |
127 | |
128 | start_reopen_ui( |
129 | - verify_bug_information(args.reopen, args.project), |
130 | + args.reopen, |
131 | args.project, |
132 | assignee=args.assignee, |
133 | sku=args.sku, |
134 | - cid=args.cid |
135 | + cid=args.cid, |
136 | + template="Jira" if args.jira else "Launchpad" |
137 | ) |
138 | else: |
139 | start_ui( |
140 | @@ -102,10 +114,23 @@ def main(): |
141 | tags=args.tags, |
142 | sku=args.sku, |
143 | cid=args.cid, |
144 | + template="Jira" if args.jira else "Launchpad" |
145 | ) |
146 | |
147 | |
148 | -def start_ui(assignee="", project="", series="", tags="", sku="", cid=""): |
149 | +def get_target_ba(template): |
150 | + """ Get target ba, launchpad or jira |
151 | + """ |
152 | + target_cls_name = f"{template}BugAssistant" |
153 | + mod = __import__('bugit.bug_assistant', fromlist=[target_cls_name]) |
154 | + target_cls = getattr(mod, target_cls_name) |
155 | + ba = target_cls() |
156 | + |
157 | + return ba |
158 | + |
159 | + |
160 | +def start_ui(assignee="", project="", series="", |
161 | + tags="", sku="", cid="", template=""): |
162 | tags += " {}".format(cid) |
163 | tags = " ".join(tags.split()) # Remove unwanted spaces |
164 | job_id = "" |
165 | @@ -115,7 +140,8 @@ def start_ui(assignee="", project="", series="", tags="", sku="", cid=""): |
166 | session_screen = SessionsScreen(items=checkbox_sessions_dir) |
167 | session_screen.run() |
168 | if session_screen.selected_item != "None": |
169 | - jobs = bugit.checkbox_utils.Session(path=session_screen.selected_item) |
170 | + jobs = bugit.checkbox_utils.Session( |
171 | + path=session_screen.selected_item) |
172 | jobs_screen = JobsScreen(items=jobs.get_run_jobs()) |
173 | jobs_screen.run() |
174 | if jobs_screen.selected_item != "None": |
175 | @@ -124,16 +150,18 @@ def start_ui(assignee="", project="", series="", tags="", sku="", cid=""): |
176 | |
177 | report_screen = ReportScreen( |
178 | assignee=assignee.lower(), |
179 | - project=project.lower(), |
180 | + project=project, |
181 | series=series.lower(), |
182 | tags=tags.lower(), |
183 | sku=sku.strip().upper(), |
184 | cid=cid, |
185 | job_id=job_id, |
186 | job_output=job_output, |
187 | + template=template.lower() |
188 | ) |
189 | report_screen.run() |
190 | - ba = BugAssistant() |
191 | + |
192 | + ba = get_target_ba(template) |
193 | ba.update(report_screen.report()) |
194 | while True: |
195 | try: |
196 | @@ -147,34 +175,12 @@ def start_ui(assignee="", project="", series="", tags="", sku="", cid=""): |
197 | break |
198 | |
199 | |
200 | -def verify_bug_information(bug_no, project): |
201 | - ba = BugAssistant() |
202 | - ba.launchpad_login() |
203 | - try: |
204 | - bug = ba.get_bug(bug_no) |
205 | - bug_link = ba.get_bug_attribute(bug, "self_link") |
206 | - except KeyError: |
207 | - raise ValueError("Invalid bug number: {}".format(bug_no)) |
208 | +def start_reopen_ui(bug_id, project, assignee="", sku="", cid="", template=""): |
209 | |
210 | - project_bugs = ba.get_project_bugs_by_title( |
211 | - project, ba.get_bug_attribute(bug, "title") |
212 | - ) |
213 | - |
214 | - for p_bug in project_bugs: |
215 | - if ba.get_bug_attribute(p_bug, "bug_link") == bug_link: |
216 | - ba.bug = bug |
217 | - break |
218 | - |
219 | - if ba.bug is None: |
220 | - sys.exit("Bug not exists in {} project: {}".format(project, bug_no)) |
221 | - |
222 | - return ba |
223 | - |
224 | - |
225 | -def start_reopen_ui(ba, project, assignee="", sku="", cid=""): |
226 | - # collect tags from original bug |
227 | - bug_info = ba.get_bug_infomation() |
228 | - tags = "{} {}".format(bug_info["tags"], cid).strip() |
229 | + ba = get_target_ba(template) |
230 | + bug, bug_info = ba.bug_helper.get_bug_information(bug_id) |
231 | + tags = "{} {}".format(bug_info["tags"], cid).strip() \ |
232 | + if template == "launchpad" else "" |
233 | |
234 | job_id = "" |
235 | job_output = None |
236 | @@ -183,7 +189,8 @@ def start_reopen_ui(ba, project, assignee="", sku="", cid=""): |
237 | session_screen = SessionsScreen(items=checkbox_sessions_dir) |
238 | session_screen.run() |
239 | if session_screen.selected_item != "None": |
240 | - jobs = bugit.checkbox_utils.Session(path=session_screen.selected_item) |
241 | + jobs = bugit.checkbox_utils.Session( |
242 | + path=session_screen.selected_item) |
243 | jobs_screen = JobsScreen(items=jobs.get_run_jobs()) |
244 | jobs_screen.run() |
245 | if jobs_screen.selected_item != "None": |
246 | @@ -192,16 +199,18 @@ def start_reopen_ui(ba, project, assignee="", sku="", cid=""): |
247 | |
248 | report_screen = ReopenReportScreen( |
249 | assignee=assignee.lower() or bug_info["assignee"], |
250 | - project=project.lower(), |
251 | + project=project, |
252 | sku=sku.strip().upper(), |
253 | cid=cid, |
254 | title=bug_info["title"], |
255 | tags=tags, |
256 | importance=bug_info["importance"], |
257 | job_id=job_id, |
258 | - job_output=job_output |
259 | + job_output=job_output, |
260 | + template=template.lower() |
261 | ) |
262 | report_screen.run() |
263 | + |
264 | ba.update(report_screen.report()) |
265 | while True: |
266 | try: |
267 | @@ -215,7 +224,7 @@ def start_reopen_ui(ba, project, assignee="", sku="", cid=""): |
268 | break |
269 | |
270 | |
271 | -def auto_submit_fwts_report(project, tags): |
272 | +def auto_submit_fwts_report(project, tags, template=""): |
273 | """ |
274 | Automatically file a Launchpad issue with log files needed by FWTS team |
275 | without starting the UI. |
276 | @@ -233,7 +242,8 @@ def auto_submit_fwts_report(project, tags): |
277 | importance="Critical", |
278 | options=options, |
279 | ) |
280 | - ba = BugAssistant(report) |
281 | + |
282 | + ba = get_target_ba(template) |
283 | try: |
284 | result = ba.file_bug() |
285 | except Exception as exc: |
286 | diff --git a/bugit/bug_assistant.py b/bugit/bug_assistant.py |
287 | index da778b1..4592fb0 100644 |
288 | --- a/bugit/bug_assistant.py |
289 | +++ b/bugit/bug_assistant.py |
290 | @@ -1,15 +1,16 @@ |
291 | -import getpass |
292 | import os |
293 | import re |
294 | import subprocess |
295 | import sys |
296 | import tarfile |
297 | +import io |
298 | from collections import Counter |
299 | from glob import glob |
300 | -from launchpadlib.launchpad import Launchpad |
301 | -from launchpadlib.uris import LPNET_WEB_ROOT, STAGING_WEB_ROOT, QASTAGING_WEB_ROOT |
302 | |
303 | -import bugit.checkbox_utils |
304 | +from bugit.helper.launchpad_helper import LaunchpadAssistant |
305 | +from bugit.helper.jira_helper import JiraAssistant |
306 | + |
307 | +from bugit.checkbox_utils import get_checkbox_last_session_dir |
308 | import bugit.utils |
309 | |
310 | |
311 | @@ -68,220 +69,157 @@ class BugReport: |
312 | self.options = options |
313 | |
314 | |
315 | -class BugAssistant: |
316 | - """Handle a bug report, its attachments and their transmission to Launchpad.""" |
317 | +class LaunchpadBugAssistant: |
318 | + """Handle a bug report, its attachments and their transmission |
319 | + to Launchpad. |
320 | + """ |
321 | |
322 | def __init__(self): |
323 | - #self.update(bug_report) |
324 | - # Launchpad bug, in case something is wrong after creating the bug |
325 | self.bug = None |
326 | - self.launchpad = None |
327 | + self.bug_helper = LaunchpadAssistant() |
328 | |
329 | def update(self, bug_report): |
330 | - self.lp_title = bug_report.title |
331 | - self.lp_description = bug_report.description |
332 | - self.lp_project = bug_report.project |
333 | - self.lp_series = bug_report.series |
334 | - self.lp_assignee = bug_report.assignee |
335 | - self.lp_tags = bug_report.tags |
336 | - self.lp_status = bug_report.status |
337 | - self.lp_importance = bug_report.importance |
338 | + self.title = bug_report.title |
339 | + self.description = bug_report.description |
340 | + self.project = bug_report.project |
341 | + self.series = bug_report.series |
342 | + self.assignee = bug_report.assignee |
343 | + self.tags = bug_report.tags |
344 | + self.status = bug_report.status |
345 | + self.importance = bug_report.importance |
346 | self.options = bug_report.options |
347 | |
348 | - def launchpad_login(self): |
349 | - """Log in Launchpad and create credentials file if needed.""" |
350 | - # can be 'production', 'staging' or 'qastaging' |
351 | - service_root = os.environ.get("APPORT_LAUNCHPAD_INSTANCE", "production") |
352 | - print("Using {} service root".format(service_root)) |
353 | - |
354 | - directory = os.path.expanduser("~/.launchpadlib") |
355 | - if not os.path.isdir(directory): |
356 | - os.mkdir(directory) |
357 | - |
358 | - launchpad = Launchpad.login_with( |
359 | - "bugit", |
360 | - service_root, |
361 | - credentials_file=os.path.join(directory, "bugit"), |
362 | - allow_access_levels=["WRITE_PRIVATE"], |
363 | - ) |
364 | - |
365 | - # Small trick to force access to launchpad and verify authentication |
366 | - launchpad.me |
367 | - self.launchpad = launchpad |
368 | - |
369 | - def get_bug_infomation(self): |
370 | - if self.bug is None: |
371 | - raise BugAssistantError("Bug is not defined") |
372 | - |
373 | - bug_task = self.bug.bug_tasks[0] |
374 | - bug_info = { |
375 | - "status": bug_task.status, |
376 | - "title": bug_task.title, |
377 | - "tags": " ".join(self.bug.lp_get_parameter("tags")), |
378 | - "assignee": bug_task.assignee.name if bug_task.assignee else "", |
379 | - "importance": bug_task.importance |
380 | - } |
381 | - |
382 | - return bug_info |
383 | - |
384 | def file_bug(self): |
385 | """Upload bug report and its attachments to Launchpad.""" |
386 | |
387 | - if not self.lp_title: |
388 | + if not self.title: |
389 | raise BugAssistantError("Bug title is mandatory") |
390 | - if not self.lp_description: |
391 | + if not self.description: |
392 | raise BugAssistantError("Description is mandatory") |
393 | - if not self.lp_project: |
394 | + if not self.project: |
395 | raise BugAssistantError("Project name is mandatory") |
396 | |
397 | print("Logging into Launchpad...") |
398 | - self.launchpad_login() |
399 | - launchpad = self.launchpad |
400 | + launchpad = LaunchpadAssistant() |
401 | + bug_dict = { |
402 | + 'assignee': self.assignee, |
403 | + 'project': self.project, |
404 | + 'title': self.title, |
405 | + 'description': self.description, |
406 | + 'priority': self.importance, |
407 | + 'status': self.status, |
408 | + 'tags': self.tags, |
409 | + 'series': self.series |
410 | + } |
411 | + self.bug, bug_url = launchpad.create_bug(bug_dict) |
412 | + print("Uploading bug data...") |
413 | + aa = AttachmentAssistant(self.options) |
414 | + attachments = aa.run_attachment_methods() |
415 | + launchpad.upload_attachments(attachments) |
416 | |
417 | - try: |
418 | - print("Checking project name...") |
419 | - project = launchpad.projects[self.lp_project] |
420 | - except Exception: |
421 | - error_message = "{} launchpad project not found".format(self.lp_project) |
422 | - raise BugAssistantError(error_message) |
423 | - |
424 | - series_name = self.lp_series |
425 | - series = None |
426 | - if series_name: |
427 | - print("Checking series...") |
428 | - series = project.getSeries(name=series_name) |
429 | - if series is None: |
430 | - error_message = "{} series not found".format(series_name) |
431 | - raise BugAssistantError(error_message) |
432 | - |
433 | - assignee_name = self.lp_assignee |
434 | - assignee = None |
435 | - if assignee_name: |
436 | - try: |
437 | - print("Checking assignee...") |
438 | - assignee = launchpad.people[assignee_name] |
439 | - except Exception: |
440 | - error_message = "{} launchpad user not found".format(assignee_name) |
441 | - raise BugAssistantError(error_message) |
442 | - |
443 | - if not self.bug: |
444 | - print("Creating Launchpad bug report...") |
445 | - self.bug = launchpad.bugs.createBug( |
446 | - title=self.lp_title, |
447 | - description=self.lp_description, |
448 | - tags=self.lp_tags.split(), |
449 | - target=project, |
450 | - ) |
451 | - print("Bug report #{} created.".format(self.bug.id)) |
452 | - else: |
453 | - print("Updating Launchpad bug report...") |
454 | - self.bug.title = self.lp_title |
455 | - self.bug.description = self.lp_description |
456 | - self.bug.tags = self.lp_tags.split() |
457 | - self.bug.lp_save() |
458 | - |
459 | - # Task configuration |
460 | - task = self.bug.bug_tasks[0] |
461 | - if self.bug and task.target != project: |
462 | - print("Updating project...") |
463 | - task.target = project |
464 | - task.lp_save() |
465 | - |
466 | - if series: |
467 | - print("Setting series...") |
468 | - nomination = self.bug.addNomination(target=series) |
469 | - nomination.approve() |
470 | - |
471 | - # We update bug info only for the latest created series |
472 | - task = self.bug.bug_tasks[len(self.bug.bug_tasks) - 1] |
473 | - if assignee: |
474 | - print("Setting assignee for series {}...".format(task.bug_target_name)) |
475 | - task.assignee = assignee |
476 | - print("Setting status...") |
477 | - task.status = self.lp_status |
478 | - print("Setting importance...") |
479 | - task.importance = self.lp_importance |
480 | - |
481 | - task.lp_save() |
482 | - |
483 | - service_root = os.environ.get("APPORT_LAUNCHPAD_INSTANCE", "production") |
484 | - if service_root == "qastaging": |
485 | - bug_url = QASTAGING_WEB_ROOT + "bugs/{}".format(self.bug.id) |
486 | - elif service_root == "staging": |
487 | - bug_url = STAGING_WEB_ROOT + "bugs/{}".format(self.bug.id) |
488 | - else: |
489 | - bug_url = LPNET_WEB_ROOT + "bugs/{}".format(self.bug.id) |
490 | + result = "Bug report #{} and its attachments are available at <{}>" |
491 | + return result.format(self.bug.id, bug_url) |
492 | |
493 | - print("Bug report #{} updated.".format(self.bug.id)) |
494 | + def reopen(self): |
495 | + """ Reopen a bug on launchpad |
496 | |
497 | - print("Uploading bug data...") |
498 | + Args: |
499 | + launchpad: launchpadAssistant instance |
500 | + """ |
501 | + bug_dict = { |
502 | + 'assignee': self.assignee, |
503 | + 'project': self.project, |
504 | + 'title': self.title, |
505 | + 'description': self.description, |
506 | + 'priority': self.importance, |
507 | + 'status': self.status, |
508 | + 'tags': self.tags, |
509 | + 'series': self.series |
510 | + } |
511 | + launchpad = self.bug_helper |
512 | + # add comment and update assignee/status/importance/tags |
513 | + launchpad.add_comment(bug_dict['description']) |
514 | + self.bug, bug_url = launchpad.update_bug(bug_dict) |
515 | + |
516 | + # upload attachment |
517 | aa = AttachmentAssistant(self.options) |
518 | - aa.run_attachment_methods() |
519 | - aa.upload_attachments(launchpad, self.bug.id) |
520 | + attachments = aa.run_attachment_methods() |
521 | + launchpad.upload_attachments(attachments) |
522 | |
523 | result = "Bug report #{} and its attachments are available at <{}>" |
524 | return result.format(self.bug.id, bug_url) |
525 | |
526 | - def reopen(self): |
527 | - assignee_name = self.lp_assignee |
528 | - assignee = None |
529 | - if assignee_name: |
530 | - try: |
531 | - print("Checking assignee...") |
532 | - assignee = self.launchpad.people[assignee_name] |
533 | - except Exception: |
534 | - error_message = "{} launchpad user not found".format( |
535 | - assignee_name) |
536 | - raise BugAssistantError(error_message) |
537 | - |
538 | - bug_task = self.bug.bug_tasks[0] |
539 | - self.add_comment(self.lp_description) |
540 | - if assignee: |
541 | - print("Setting assignee to {}...".format(bug_task.assignee)) |
542 | - bug_task.assignee = assignee |
543 | - print("Setting status...") |
544 | - bug_task.status = self.lp_status |
545 | - print("Setting importance...") |
546 | - bug_task.importance = self.lp_importance |
547 | - |
548 | - bug_task.lp_save() |
549 | - |
550 | - self.bug.tags = self.lp_tags.split() |
551 | - self.bug.lp_save() |
552 | - |
553 | - service_root = os.environ.get("APPORT_LAUNCHPAD_INSTANCE", "production") |
554 | - if service_root == "qastaging": |
555 | - bug_url = QASTAGING_WEB_ROOT + "bugs/{}".format(self.bug.id) |
556 | - elif service_root == "staging": |
557 | - bug_url = STAGING_WEB_ROOT + "bugs/{}".format(self.bug.id) |
558 | - else: |
559 | - bug_url = LPNET_WEB_ROOT + "bugs/{}".format(self.bug.id) |
560 | |
561 | - print("Bug report #{} updated.".format(self.bug.id)) |
562 | +class JiraBugAssistant: |
563 | + def __init__(self): |
564 | + self.bug = None |
565 | + self.bug_helper = JiraAssistant() |
566 | + |
567 | + def update(self, bug_report): |
568 | + self.title = bug_report.title |
569 | + self.description = bug_report.description |
570 | + self.project = bug_report.project |
571 | + self.assignee = bug_report.assignee |
572 | + self.tags = bug_report.tags |
573 | + self.status = bug_report.status |
574 | + self.importance = bug_report.importance |
575 | + self.options = bug_report.options |
576 | + |
577 | + def file_bug(self): |
578 | + """Upload bug report and its attachments to JIRA.""" |
579 | + |
580 | + if not self.title: |
581 | + raise BugAssistantError("Bug title is mandatory") |
582 | + if not self.description: |
583 | + raise BugAssistantError("Description is mandatory") |
584 | + if not self.project: |
585 | + raise BugAssistantError("Project name is mandatory") |
586 | |
587 | + print("Logging into Jira...") |
588 | + jira = JiraAssistant() |
589 | + bug_dict = { |
590 | + 'assignee': self.assignee, |
591 | + 'project': self.project, |
592 | + 'summary': self.title, |
593 | + 'description': self.description, |
594 | + 'priority': {'name': self.importance}, |
595 | + 'issuetype': {'name': 'Bug'} |
596 | + } |
597 | + self.bug, bug_url = jira.create_bug(bug_dict) |
598 | print("Uploading bug data...") |
599 | aa = AttachmentAssistant(self.options) |
600 | - aa.run_attachment_methods() |
601 | - aa.upload_attachments(self.launchpad, self.bug.id) |
602 | + attachments = aa.run_attachment_methods() |
603 | + jira.upload_attachments(attachments) |
604 | |
605 | result = "Bug report #{} and its attachments are available at <{}>" |
606 | - return result.format(self.bug.id, bug_url) |
607 | + return result.format(self.bug.key, bug_url) |
608 | |
609 | - def add_comment(self, data): |
610 | - self.bug.newMessage(content=data) |
611 | + def reopen(self): |
612 | + """ Reopen a bug on Jira |
613 | |
614 | - def get_bug(self, bug_id): |
615 | - return self.launchpad.bugs[bug_id] |
616 | + Args: |
617 | + jira: JiraAssistant instance |
618 | + """ |
619 | |
620 | - def get_project_bugs_by_title(self, project, title): |
621 | - lp_project = self.launchpad.projects[project] |
622 | - project_bugs = lp_project.searchTasks( |
623 | - search_text=title |
624 | - ) |
625 | - return project_bugs |
626 | + bug_dict = { |
627 | + 'assignee': self.assignee, |
628 | + 'project': self.project, |
629 | + 'summary': self.title, |
630 | + 'description': self.description, |
631 | + 'priority': {'name': self.importance} |
632 | + } |
633 | + jira = self.bug_helper |
634 | + # add comment and update assignee/importance |
635 | + jira.add_comment(bug_dict['description']) |
636 | + self.bug, bug_url = jira.update_bug(bug_dict) |
637 | + |
638 | + # upload attachment |
639 | + aa = AttachmentAssistant(self.options) |
640 | + attachments = aa.run_attachment_methods() |
641 | + jira.upload_attachments(attachments) |
642 | |
643 | - def get_bug_attribute(self, bug, attr): |
644 | - return bug.lp_get_parameter(attr) |
645 | + result = "Bug report #{} and its attachments are available at <{}>" |
646 | + return result.format(self.bug.key, bug_url) |
647 | |
648 | |
649 | class BugAssistantError(Exception): |
650 | @@ -302,15 +240,6 @@ class AttachmentAssistant: |
651 | self.attachments = {} |
652 | self.options = options |
653 | |
654 | - def upload_attachments(self, lp_instance, bug_number): |
655 | - attachments = self.attachments |
656 | - for a in attachments: |
657 | - print("Uploading attachment {}...".format(a)) |
658 | - bug = lp_instance.bugs[bug_number] |
659 | - bug.addAttachment( |
660 | - comment="Automatically attached", filename=a, data=attachments[a] |
661 | - ) |
662 | - |
663 | def run_attachment_methods(self): |
664 | """Runs the different attachment methods to add logs to the |
665 | attachments dictionary.""" |
666 | @@ -326,6 +255,8 @@ class AttachmentAssistant: |
667 | if self.options.get("collect_log"): |
668 | self.attach_log_report() |
669 | |
670 | + return self.attachments |
671 | + |
672 | def attach_log_report(self): |
673 | home_dir = os.getenv("HOME") |
674 | print("Running log report...") |
675 | @@ -337,7 +268,7 @@ class AttachmentAssistant: |
676 | with open(report_name, mode="rb") as f: |
677 | data = f.read() |
678 | self.attachments[report_name] = data |
679 | - except subprocess.CalledProcessError as e: |
680 | + except subprocess.CalledProcessError: |
681 | print("Failed to collect log by oem-getlogs, try sosreport...") |
682 | try: |
683 | command = [ |
684 | @@ -385,11 +316,13 @@ class AttachmentAssistant: |
685 | devmode, |
686 | snap.props.confinement.value_nick, |
687 | ) |
688 | - self.attachments["snap_list.log"] = out.encode() |
689 | + # Note: jiralib's add_attachment can only handle file-like object |
690 | + data = io.BytesIO(out.encode()) |
691 | + self.attachments["snap_list.log"] = data.read() |
692 | |
693 | def attach_last_checkbox_session(self): |
694 | """Archive and attach the most recent Checkbox session available.""" |
695 | - latest_session_dir = bugit.checkbox_utils.get_checkbox_last_session_dir() |
696 | + latest_session_dir = get_checkbox_last_session_dir() |
697 | if latest_session_dir: |
698 | session_tgz = os.path.expandvars("$HOME/checkbox-session.tgz") |
699 | if os.path.exists(session_tgz): |
700 | @@ -405,9 +338,10 @@ class AttachmentAssistant: |
701 | and attach them. |
702 | """ |
703 | |
704 | - latest_session_dir = bugit.checkbox_utils.get_checkbox_last_session_dir() |
705 | + latest_session_dir = get_checkbox_last_session_dir() |
706 | if latest_session_dir: |
707 | - logpath = os.path.join(latest_session_dir, "CHECKBOX_DATA", "fwts*.log") |
708 | + logpath = os.path.join( |
709 | + latest_session_dir, "CHECKBOX_DATA", "fwts*.log") |
710 | logfiles = glob(logpath) |
711 | if logfiles: |
712 | for log in logfiles: |
713 | @@ -417,7 +351,8 @@ class AttachmentAssistant: |
714 | else: |
715 | print("No FWTS logs found in Checkbox session!") |
716 | else: |
717 | - print(("Cannot find Checkbox session where FWTS logs could be " "located!")) |
718 | + print("Cannot find Checkbox session where" |
719 | + "FWTS logs could be located!") |
720 | |
721 | def attach_nvidia_bug_report(self): |
722 | |
723 | @@ -445,14 +380,10 @@ class AttachmentAssistant: |
724 | def attach_acpidump(self): |
725 | print("Running acpidump...") |
726 | command = ["acpidump"] |
727 | - process = subprocess.run( |
728 | - command, stdout=subprocess.PIPE, stderr=subprocess.PIPE |
729 | - ) |
730 | - if process.returncode == 0: |
731 | - self.attachments["acpidump.log"] = process.stdout |
732 | - else: |
733 | - print("Error while running acpidump:") |
734 | - print(process.stderr) |
735 | + acpi_output = issue_commands(command) |
736 | + if acpi_output is not None: |
737 | + data = io.BytesIO(acpi_output.encode()) |
738 | + self.attachments["acpidump.log"] = data.read() |
739 | |
740 | def attach_previous_kernel_log(self): |
741 | print("Dumping previous kernel log...") |
742 | @@ -486,8 +417,8 @@ class AttachmentAssistant: |
743 | standard_info = {} |
744 | |
745 | buildstamp_paths = [ |
746 | - # For how to access a file in /var/lib |
747 | - # See: https://forum.snapcraft.io/t/read-access-to-a-file-in-var-lib-on-the-host/11766 |
748 | + # For how to access a file in /var/lib, |
749 | + # please find reference in README |
750 | "/var/lib/snapd/hostfs/var/lib/ubuntu_dist_channel", # PC project |
751 | "/var/lib/snapd/hostfs/.disk/info", # ubuntu classic |
752 | "/run/mnt/ubuntu-seed/.disk/info", # ubuntu core |
753 | @@ -578,7 +509,8 @@ class AttachmentAssistant: |
754 | cpu_names[cpu_name] += 1 |
755 | |
756 | cpu_names_str = [ |
757 | - "{} ({}x)".format(cpu_name, count) for cpu_name, count in cpu_names.items() |
758 | + "{} ({}x)".format(cpu_name, count) |
759 | + for cpu_name, count in cpu_names.items() |
760 | ] |
761 | |
762 | return "\n".join(cpu_names_str) |
763 | diff --git a/bugit/helper/jira_helper.py b/bugit/helper/jira_helper.py |
764 | new file mode 100644 |
765 | index 0000000..1ce6045 |
766 | --- /dev/null |
767 | +++ b/bugit/helper/jira_helper.py |
768 | @@ -0,0 +1,151 @@ |
769 | +#!/usr/bin/python3 |
770 | + |
771 | +import os |
772 | +import configparser |
773 | + |
774 | +from jira import JIRA |
775 | + |
776 | +JIRA_SERVER = "https://warthogs.atlassian.net/" |
777 | +JIRA_CONFIG_DIR = "{}/.jira.conf".format(os.path.expanduser('~')) |
778 | + |
779 | + |
780 | +class JiraAssistant(): |
781 | + |
782 | + def __init__(self, server=JIRA_SERVER, config_dir=JIRA_CONFIG_DIR): |
783 | + self.server = server |
784 | + self.config_dir = config_dir |
785 | + |
786 | + config = configparser.ConfigParser() |
787 | + while not config.read(self.config_dir): |
788 | + self.create_token() |
789 | + jiracfg = config["jira"] |
790 | + self.user = jiracfg["user"] |
791 | + self.token = jiracfg["token"] |
792 | + |
793 | + self.jira = JIRA(server=self.server, |
794 | + basic_auth=(self.user, self.token)) |
795 | + self.bug = None |
796 | + |
797 | + def create_token(self): |
798 | + """ Create token file to store user's email and token |
799 | + """ |
800 | + email = input("Enter the user's email: ") |
801 | + token = input( |
802 | + "Enter the user's token (see " |
803 | + "https://id.atlassian.com/manage-profile/security/api-tokens): " |
804 | + ) |
805 | + |
806 | + config = configparser.ConfigParser() |
807 | + config.add_section("jira") |
808 | + jiracfg = config["jira"] |
809 | + jiracfg["user"] = email |
810 | + jiracfg["token"] = token |
811 | + with open(self.config_dir, 'w') as configfile: |
812 | + config.write(configfile) |
813 | + |
814 | + def get_bug_information(self, bug_id): |
815 | + """ Get bug's informations by given bug ID |
816 | + """ |
817 | + try: |
818 | + print("Checking bug...") |
819 | + self.bug = self.jira.issue(id=bug_id) |
820 | + except Exception: |
821 | + error_msg = f"{bug_id} jira bug not found" |
822 | + raise JIRAAssistantError(error_msg) |
823 | + |
824 | + bug_info = { |
825 | + "title": self.bug.raw['fields']['summary'], |
826 | + "importance": self.bug.raw['fields']['priority']['name'], |
827 | + "assignee": self.bug.raw['fields']['assignee']['displayName'] |
828 | + } |
829 | + |
830 | + return self.bug, bug_info |
831 | + |
832 | + def check_assignee(self, assignee): |
833 | + """ Check if the given assignee is exist |
834 | + """ |
835 | + users = self.jira.search_users(query=assignee) |
836 | + if len(users) > 1: |
837 | + error_msg = "There are more than 1 matched users," \ |
838 | + "please give an unique username" |
839 | + for user in users: |
840 | + error_msg += f"{user.emailAddress}\n" |
841 | + raise JIRAAssistantError(error_msg) |
842 | + |
843 | + elif len(users) == 0: |
844 | + error_msg = "There is no matched user," \ |
845 | + "please give an unique username" |
846 | + raise JIRAAssistantError(error_msg) |
847 | + |
848 | + return users[0].emailAddress |
849 | + |
850 | + def check_project(self, project_name): |
851 | + """ Check if the given project key is exist |
852 | + """ |
853 | + try: |
854 | + project = self.jira.project(id=project_name) |
855 | + except Exception: |
856 | + error_msg = f"Project {project_name} not found on JIRA" |
857 | + raise JIRAAssistantError(error_msg) |
858 | + |
859 | + return project.key |
860 | + |
861 | + def create_bug(self, bug_dict): |
862 | + """ Create bug to JIRA |
863 | + """ |
864 | + if bug_dict['assignee']: |
865 | + print("check assignee...") |
866 | + assignee_id = self.check_assignee(bug_dict['assignee']) |
867 | + |
868 | + if bug_dict['project']: |
869 | + print("check project...") |
870 | + project_key = self.check_project(bug_dict['project']) |
871 | + |
872 | + print(f"Creating issue to {project_key}...") |
873 | + self.bug = self.jira.create_issue(bug_dict) |
874 | + bug_url = f"https://warthogs.atlassian.net/browse/{self.bug.key}" |
875 | + print(f"Issue {self.bug.key} is created") |
876 | + |
877 | + # Assign the ticket to given assignee |
878 | + self.jira.assign_issue(self.bug.key, assignee_id) |
879 | + |
880 | + return self.bug, bug_url |
881 | + |
882 | + def upload_attachments(self, attachments): |
883 | + """ Upload an attachment to a ticket |
884 | + """ |
885 | + for a in attachments: |
886 | + print("Uploading attachment {}...".format(a)) |
887 | + self.jira.add_attachment(self.bug.id, |
888 | + filename=a, |
889 | + attachment=attachments[a]) |
890 | + |
891 | + def add_comment(self, comment): |
892 | + """Add comment to the bug |
893 | + """ |
894 | + print("Adding comment...") |
895 | + update_dict = {'comment': [{'add': {'body': comment}}]} |
896 | + self.bug.update(update=update_dict) |
897 | + |
898 | + def update_bug(self, bug_dict): |
899 | + """Update bug informations |
900 | + """ |
901 | + |
902 | + if bug_dict['assignee']: |
903 | + print("check assignee...") |
904 | + assignee_id = self.check_assignee(bug_dict['assignee']) |
905 | + self.jira.assign_issue(self.bug.key, assignee_id) |
906 | + |
907 | + update_dict = {"priority": bug_dict['priority']} |
908 | + self.bug.update(fields=update_dict) |
909 | + |
910 | + bug_url = f"https://warthogs.atlassian.net/browse/{self.bug.key}" |
911 | + print(f"Issue {self.bug.key} is updated") |
912 | + |
913 | + return self.bug, bug_url |
914 | + |
915 | + |
916 | +class JIRAAssistantError(Exception): |
917 | + """Raised when an error is reported during upload to JIRA.""" |
918 | + |
919 | + pass |
920 | diff --git a/bugit/helper/launchpad_helper.py b/bugit/helper/launchpad_helper.py |
921 | new file mode 100644 |
922 | index 0000000..90ca6fc |
923 | --- /dev/null |
924 | +++ b/bugit/helper/launchpad_helper.py |
925 | @@ -0,0 +1,234 @@ |
926 | +import os |
927 | +from launchpadlib.launchpad import Launchpad |
928 | +from launchpadlib.uris import LPNET_WEB_ROOT, STAGING_WEB_ROOT, \ |
929 | + QASTAGING_WEB_ROOT |
930 | + |
931 | + |
932 | +class LaunchpadAssistant(): |
933 | + def __init__(self): |
934 | + # can be 'production', 'staging' or 'qastaging' |
935 | + service_root = os.environ.get( |
936 | + "APPORT_LAUNCHPAD_INSTANCE", "production") |
937 | + print("Using {} service root".format(service_root)) |
938 | + |
939 | + directory = os.path.expanduser("~/.launchpadlib") |
940 | + if not os.path.isdir(directory): |
941 | + os.mkdir(directory) |
942 | + |
943 | + launchpad = Launchpad.login_with( |
944 | + "bugit", |
945 | + service_root, |
946 | + credentials_file=os.path.join(directory, "bugit"), |
947 | + allow_access_levels=["WRITE_PRIVATE"], |
948 | + ) |
949 | + |
950 | + # Small trick to force access to launchpad and verify authentication |
951 | + launchpad.me |
952 | + self.launchpad = launchpad |
953 | + self.bug = None |
954 | + |
955 | + def get_bug_information(self, bug_id): |
956 | + """ Get existing bug's informations from launchpad |
957 | + |
958 | + Args: |
959 | + bug_id (string): bug ID to retrive from launchpad |
960 | + """ |
961 | + |
962 | + try: |
963 | + print("Checking bug...") |
964 | + self.bug = self.launchpad.bugs[bug_id] |
965 | + except Exception: |
966 | + error_message = "{} launchpad bug not found".format(bug_id) |
967 | + raise LaunchpadAssistantError(error_message) |
968 | + |
969 | + bug_task = self.bug.bug_tasks[0] |
970 | + bug_info = { |
971 | + "status": bug_task.status, |
972 | + "title": bug_task.title, |
973 | + "tags": " ".join(self.bug.lp_get_parameter("tags")), |
974 | + "assignee": bug_task.assignee.name if bug_task.assignee else "", |
975 | + "importance": bug_task.importance |
976 | + } |
977 | + |
978 | + return self.bug, bug_info |
979 | + |
980 | + def check_project_exist(self, project_name): |
981 | + """ Check if given project name exist |
982 | + """ |
983 | + |
984 | + try: |
985 | + print("Checking project name...") |
986 | + project = self.launchpad.projects[project_name] |
987 | + except Exception: |
988 | + error_message = "{} launchpad project not found".format( |
989 | + project_name) |
990 | + raise LaunchpadAssistantError(error_message) |
991 | + |
992 | + return project |
993 | + |
994 | + def check_series_exist(self, project, series_name): |
995 | + """ Check if given series name exist in the project |
996 | + """ |
997 | + |
998 | + print("Checking series...") |
999 | + series = project.getSeries(name=series_name) |
1000 | + if series is None: |
1001 | + error_message = "{} series not found".format(series_name) |
1002 | + raise LaunchpadAssistantError(error_message) |
1003 | + |
1004 | + return series |
1005 | + |
1006 | + def check_assignee_exist(self, assignee_name): |
1007 | + """ Check if given assignee exist |
1008 | + """ |
1009 | + |
1010 | + try: |
1011 | + print("Checking assignee...") |
1012 | + assignee = self.launchpad.people[assignee_name] |
1013 | + except Exception: |
1014 | + error_message = "{} launchpad user not found".format(assignee_name) |
1015 | + raise LaunchpadAssistantError(error_message) |
1016 | + |
1017 | + return assignee |
1018 | + |
1019 | + def create_bug(self, bug_dict): |
1020 | + """Create issue to Launchpad with given informations |
1021 | + |
1022 | + Args: |
1023 | + bug_dict (dict): Contain all information needed to creating a bug |
1024 | + |
1025 | + """ |
1026 | + # checking if the project is exist |
1027 | + lp_project = None |
1028 | + lp_project = self.check_project_exist(bug_dict['project']) |
1029 | + |
1030 | + # checking if the series is exist |
1031 | + lp_series = None |
1032 | + if bug_dict['series']: |
1033 | + lp_series = self.check_series_exist(lp_project, bug_dict['series']) |
1034 | + |
1035 | + # checking if assignee is exist |
1036 | + lp_assignee = None |
1037 | + if bug_dict['assignee']: |
1038 | + lp_assignee = self.check_assignee_exist(bug_dict['assignee']) |
1039 | + |
1040 | + if not self.bug: |
1041 | + print("Creating Launchpad bug report...") |
1042 | + self.bug = self.launchpad.bugs.createBug( |
1043 | + title=bug_dict['title'], |
1044 | + description=bug_dict['description'], |
1045 | + tags=bug_dict['tags'].split(), |
1046 | + target=lp_project |
1047 | + ) |
1048 | + print("Bug report #{} created.".format(self.bug.id)) |
1049 | + else: |
1050 | + print("Updating Launchpad bug report...") |
1051 | + self.bug.title = self.lp_title |
1052 | + self.bug.description = self.lp_description |
1053 | + self.bug.tags = self.lp_tags.split() |
1054 | + self.bug.lp_save() |
1055 | + |
1056 | + # Task configuration |
1057 | + task = self.bug.bug_tasks[0] |
1058 | + if self.bug and task.target != lp_project: |
1059 | + print("Updating project...") |
1060 | + task.target = lp_project |
1061 | + task.lp_save() |
1062 | + |
1063 | + if lp_series: |
1064 | + print("Setting series...") |
1065 | + nomination = self.bug.addNomination(target=lp_series) |
1066 | + nomination.approve() |
1067 | + |
1068 | + # We update bug info only for the latest created series |
1069 | + task = self.bug.bug_tasks[len(self.bug.bug_tasks) - 1] |
1070 | + if lp_assignee: |
1071 | + print(f"Setting assignee for series {task.bug_target_name}...") |
1072 | + task.assignee = lp_assignee |
1073 | + print("Setting status...") |
1074 | + task.status = bug_dict['status'] |
1075 | + print("Setting importance...") |
1076 | + task.importance = bug_dict['priority'] |
1077 | + |
1078 | + task.lp_save() |
1079 | + |
1080 | + service_root = os.environ.get( |
1081 | + "APPORT_LAUNCHPAD_INSTANCE", "production") |
1082 | + if service_root == "qastaging": |
1083 | + bug_url = QASTAGING_WEB_ROOT + f"bugs/{self.bug.id}" |
1084 | + elif service_root == "staging": |
1085 | + bug_url = STAGING_WEB_ROOT + f"bugs/{self.bug.id}" |
1086 | + else: |
1087 | + bug_url = LPNET_WEB_ROOT + f"bugs/{self.bug.id}" |
1088 | + |
1089 | + print("Bug report #{} updated.".format(self.bug.id)) |
1090 | + print(bug_url) |
1091 | + |
1092 | + return self.bug, bug_url |
1093 | + |
1094 | + def upload_attachments(self, attachments): |
1095 | + """Upload attachments to the issue |
1096 | + |
1097 | + Args: |
1098 | + attachments (list): List of attachments |
1099 | + """ |
1100 | + |
1101 | + for a in attachments: |
1102 | + print("Uploading attachment {}...".format(a)) |
1103 | + # bug = self.launchpad.bugs[bug_id] |
1104 | + self.bug.addAttachment( |
1105 | + comment="Automatically attached", |
1106 | + filename=a, |
1107 | + data=attachments[a] |
1108 | + ) |
1109 | + |
1110 | + def add_comment(self, comment): |
1111 | + """Add comment to the bug |
1112 | + """ |
1113 | + |
1114 | + print("Adding comment...") |
1115 | + self.bug.newMessage(content=comment) |
1116 | + |
1117 | + def update_bug(self, bug_dict): |
1118 | + """Upload bug informations |
1119 | + """ |
1120 | + |
1121 | + bug_task = self.bug.bug_tasks[0] |
1122 | + |
1123 | + lp_assignee = None |
1124 | + if bug_dict['assignee']: |
1125 | + lp_assignee = self.check_assignee_exist(bug_dict['assignee']) |
1126 | + print("Setting assignee to {}...".format(bug_dict['assignee'])) |
1127 | + bug_task.assignee = lp_assignee |
1128 | + |
1129 | + if bug_dict['status']: |
1130 | + print("Setting status to {}...".format(bug_dict['status'])) |
1131 | + bug_task.status = bug_dict['status'] |
1132 | + if bug_dict['priority']: |
1133 | + print("Setting priority to {}...".format(bug_dict['priority'])) |
1134 | + bug_task.importance = bug_dict['priority'] |
1135 | + if bug_dict['tags']: |
1136 | + print("Setting tag...") |
1137 | + self.bug.tags = bug_dict['tags'].split() |
1138 | + |
1139 | + self.bug.lp_save() |
1140 | + bug_task.lp_save() |
1141 | + |
1142 | + service_root = os.environ.get( |
1143 | + "APPORT_LAUNCHPAD_INSTANCE", "production") |
1144 | + if service_root == "qastaging": |
1145 | + bug_url = QASTAGING_WEB_ROOT + f"bugs/{self.bug.id}" |
1146 | + elif service_root == "staging": |
1147 | + bug_url = STAGING_WEB_ROOT + f"bugs/{self.bug.id}" |
1148 | + else: |
1149 | + bug_url = LPNET_WEB_ROOT + f"bugs/{self.bug.id}" |
1150 | + |
1151 | + print("Bug report #{} updated.".format(self.bug.id)) |
1152 | + |
1153 | + return self.bug, bug_url |
1154 | + |
1155 | + |
1156 | +class LaunchpadAssistantError(Exception): |
1157 | + """Raised when an error is reported during upload to Launchpad.""" |
1158 | + |
1159 | + pass |
1160 | diff --git a/bugit/ui.py b/bugit/ui.py |
1161 | index cdd0268..986621d 100644 |
1162 | --- a/bugit/ui.py |
1163 | +++ b/bugit/ui.py |
1164 | @@ -127,8 +127,10 @@ class ReportScreen: |
1165 | ] |
1166 | |
1167 | statuses = ("New", "Confirmed") |
1168 | - importances = ("Undecided", "Wishlist", "Low", "Medium", "High", "Critical") |
1169 | - |
1170 | + importances = { |
1171 | + 'launchpad': ("Undecided", "Wishlist", "Low", "Medium", "High", "Critical"), |
1172 | + 'jira': ("Lowest", "Low", "Medium", "High", "Highest") |
1173 | + } |
1174 | STAGE_IMMEDIATE = "Issue reported and logs collected right after it happened" |
1175 | STAGE_FROZEN_DEVICE = ( |
1176 | "Device froze, issue reported and logs collected right after a reboot" |
1177 | @@ -162,6 +164,7 @@ class ReportScreen: |
1178 | cid="", |
1179 | job_id="", |
1180 | job_output=None, |
1181 | + template="Launchpad", |
1182 | ): |
1183 | if job_id and job_output: |
1184 | self.default_description += f"[Checkbox job `{job_id}` output]\n\n" |
1185 | @@ -202,7 +205,7 @@ class ReportScreen: |
1186 | for s in self.statuses: |
1187 | rb = urwid.RadioButton(self._status, s, "first True", None) |
1188 | self._importance = [] |
1189 | - for i in self.importances: |
1190 | + for i in self.importances[template]: |
1191 | rb = urwid.RadioButton(self._importance, i, "first True", None) |
1192 | self._stage_bug_filed = [] |
1193 | for s in self.stage_bug_filed: |
1194 | @@ -226,7 +229,7 @@ class ReportScreen: |
1195 | ) |
1196 | self._bug_vendors.append(cb) |
1197 | self._assignee = urwid.LineBox( |
1198 | - urwid.Edit(edit_text=assignee), "Assigned To (Launchpad ID)" |
1199 | + urwid.Edit(edit_text=assignee), "Assigned To" |
1200 | ) |
1201 | self._tags = urwid.LineBox(urwid.Edit(edit_text=tags), "Tags") |
1202 | status_list = urwid.LineBox(urwid.Pile(self._status), "Status") |
1203 | @@ -377,6 +380,7 @@ class ReopenReportScreen(ReportScreen): |
1204 | importance="", |
1205 | job_id="", |
1206 | job_output=None, |
1207 | + template="Launchpad" |
1208 | ): |
1209 | if job_id and job_output: |
1210 | job_pattern = ["comments", "stdout", "stderr"] |
1211 | @@ -417,7 +421,7 @@ class ReopenReportScreen(ReportScreen): |
1212 | self._status, self.statuses[1], "first True", None |
1213 | ) |
1214 | self._importance = [] |
1215 | - for i in self.importances: |
1216 | + for i in self.importances[template]: |
1217 | rb = urwid.RadioButton(self._importance, i, "first True", None) |
1218 | [rb.set_state(True) for rb in self._importance |
1219 | if rb.label == importance] |
@Weichen,
I would suggest we modify the way to initial Jira/LaunchpadA ssistant, please see my inline comment.