Merge lp:~krytarik/ubuntu-bots/bugtracker-output into lp:ubuntu-bots

Proposed by Krytarik Raido
Status: Needs review
Proposed branch: lp:~krytarik/ubuntu-bots/bugtracker-output
Merge into: lp:ubuntu-bots
Diff against target: 662 lines (+169/-171)
1 file modified
Bugtracker/plugin.py (+169/-171)
To merge this branch: bzr merge lp:~krytarik/ubuntu-bots/bugtracker-output
Reviewer Review Type Date Requested Status
Ubuntu IRC Bots Pending
Benjamin Rubin Pending
Review via email: mp+311899@code.launchpad.net

Description of the change

Bugtracker: Make sure output fits maximum length of IRC messages, improve its formatting.

To post a comment you must log in.
321. By Krytarik Raido

Bugtracker: Strip any leading or trailing spaces off bug report title.

322. By Krytarik Raido

Bugtracker: Ensure consistent casing of bug severity and status.

323. By Krytarik Raido

Bugtracker: Improve formatting of duplicate bug reports.

324. By Krytarik Raido

Bugtracker: Improve handling of bug assignee and extended info.

325. By Krytarik Raido

Bugtracker: Backport from Git.
<https://git.launchpad.net/~krytarik/ubuntu-bots/+git/ubuntu-bots>

Unmerged revisions

325. By Krytarik Raido

Bugtracker: Backport from Git.
<https://git.launchpad.net/~krytarik/ubuntu-bots/+git/ubuntu-bots>

324. By Krytarik Raido

Bugtracker: Improve handling of bug assignee and extended info.

323. By Krytarik Raido

Bugtracker: Improve formatting of duplicate bug reports.

322. By Krytarik Raido

Bugtracker: Ensure consistent casing of bug severity and status.

321. By Krytarik Raido

Bugtracker: Strip any leading or trailing spaces off bug report title.

320. By Krytarik Raido

Bugtracker: Make sure output fits maximum length
of IRC messages, improve its formatting.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Bugtracker/plugin.py'
--- Bugtracker/plugin.py 2014-08-12 16:47:38 +0000
+++ Bugtracker/plugin.py 2018-05-13 02:56:20 +0000
@@ -2,6 +2,7 @@
2###2###
3# Copyright (c) 2005-2007 Dennis Kaarsemaker3# Copyright (c) 2005-2007 Dennis Kaarsemaker
4# Copyright (c) 2008-2010 Terence Simpson4# Copyright (c) 2008-2010 Terence Simpson
5# Copyright (c) 2017- Krytarik Raido
5#6#
6# This program is free software; you can redistribute it and/or modify7# This program is free software; you can redistribute it and/or modify
7# it under the terms of version 2 of the GNU General Public License as8# it under the terms of version 2 of the GNU General Public License as
@@ -25,11 +26,11 @@
25import supybot.log as supylog26import supybot.log as supylog
2627
27#import imaplib28#import imaplib
28import re, os, time, commands29import re, os, time, subprocess
29import xml.dom.minidom as minidom30import xml.dom.minidom as minidom
30from htmlentitydefs import entitydefs as entities31from htmlentitydefs import entitydefs as entities
31import email.FeedParser32from email.parser import FeedParser
32import SOAPpy33from SOAPpy.Client import SOAPProxy
3334
34# All the words below will be censored when reporting bug information35# All the words below will be censored when reporting bug information
35bad_words = set(["fuck","fuk","fucking","fuking","fukin","fuckin","fucked","fuked","fucker","shit","cunt","bastard","nazi","nigger","nigga","cock","bitches","bitch"])36bad_words = set(["fuck","fuk","fucking","fuking","fukin","fuckin","fucked","fuked","fucker","shit","cunt","bastard","nazi","nigger","nigga","cock","bitches","bitch"])
@@ -52,7 +53,7 @@
52 if description:53 if description:
53 DESC.setValue(description)54 DESC.setValue(description)
54 if trackertype:55 if trackertype:
55 if defined_bugtrackers.has_key(trackertype.lower()):56 if trackertype.lower() in defined_bugtrackers:
56 TRACKERTYPE.setValue(trackertype.lower())57 TRACKERTYPE.setValue(trackertype.lower())
57 else:58 else:
58 raise BugtrackerError("Unknown trackertype: %s" % trackertype)59 raise BugtrackerError("Unknown trackertype: %s" % trackertype)
@@ -81,6 +82,19 @@
81 val = entre.sub('?', val)82 val = entre.sub('?', val)
82 return val83 return val
8384
85def _getnodeattr(node, attr):
86 if node.hasAttribute(attr):
87 val = node.getAttribute(attr)
88 else:
89 raise ValueError, "No such attribute"
90 while entre.search(val):
91 entity = entre.search(val).group(1)
92 if entity in entities:
93 val = entre.sub(entities[entity], val)
94 else:
95 val = entre.sub('?', val)
96 return val
97
84class BugtrackerError(Exception):98class BugtrackerError(Exception):
85 """A bugtracker error"""99 """A bugtracker error"""
86 pass100 pass
@@ -103,7 +117,7 @@
103 for name in self.registryValue('bugtrackers'):117 for name in self.registryValue('bugtrackers'):
104 registerBugtracker(name)118 registerBugtracker(name)
105 group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False)119 group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False)
106 if group.trackertype() in defined_bugtrackers.keys():120 if group.trackertype() in defined_bugtrackers:
107 self.db[name] = defined_bugtrackers[group.trackertype()](name, group.url(), group.description())121 self.db[name] = defined_bugtrackers[group.trackertype()](name, group.url(), group.description())
108 else:122 else:
109 self.log.warning("Bugtracker: Unknown trackertype: %s (%s)" % (group.trackertype(), name))123 self.log.warning("Bugtracker: Unknown trackertype: %s (%s)" % (group.trackertype(), name))
@@ -171,7 +185,7 @@
171# # Read all new mail185# # Read all new mail
172# for m in new_mail:186# for m in new_mail:
173# msg = sc.fetch(m, 'RFC822')[1][0][1]187# msg = sc.fetch(m, 'RFC822')[1][0][1]
174# fp = email.FeedParser.FeedParser()188# fp = FeedParser()
175# sc.store(m, '+FLAGS', "(\Deleted)") # Mark message deleted so we don't have to process it again189# sc.store(m, '+FLAGS', "(\Deleted)") # Mark message deleted so we don't have to process it again
176# fp.feed(msg)190# fp.feed(msg)
177# bug = fp.close()191# bug = fp.close()
@@ -275,7 +289,7 @@
275 self.shorthand = utils.abbrev(self.db.keys())289 self.shorthand = utils.abbrev(self.db.keys())
276 irc.replySuccess()290 irc.replySuccess()
277 except KeyError:291 except KeyError:
278 s = self.registryValue('replyNoBugtracker', ircutils.isChannel(msg.args[0]) and msg.args[0] or None)292 s = self.registryValue('replyNoBugtracker', msg.args[0] if ircutils.isChannel(msg.args[0]) else None)
279 irc.error(s % name)293 irc.error(s % name)
280 remove = wrap(remove, [('checkCapability', 'admin'), 'text'])294 remove = wrap(remove, [('checkCapability', 'admin'), 'text'])
281295
@@ -297,7 +311,7 @@
297 self.shorthand = utils.abbrev(self.db.keys())311 self.shorthand = utils.abbrev(self.db.keys())
298 irc.replySuccess()312 irc.replySuccess()
299 except KeyError:313 except KeyError:
300 s = self.registryValue('replyNoBugtracker', ircutils.isChannel(msg.args[0]) and msg.args[0] or None)314 s = self.registryValue('replyNoBugtracker', msg.args[0] if ircutils.isChannel(msg.args[0]) else None)
301 irc.error(s % name)315 irc.error(s % name)
302 rename = wrap(rename, [('checkCapability', 'admin'), 'something','something', additional('text')])316 rename = wrap(rename, [('checkCapability', 'admin'), 'something','something', additional('text')])
303317
@@ -315,7 +329,7 @@
315 self.db[name].__class__.__name__)329 self.db[name].__class__.__name__)
316 irc.reply('%s: %s, %s [%s]' % (name, description, url, type))330 irc.reply('%s: %s, %s [%s]' % (name, description, url, type))
317 except KeyError:331 except KeyError:
318 s = self.registryValue('replyNoBugtracker', ircutils.isChannel(msg.args[0]) and msg.args[0] or None)332 s = self.registryValue('replyNoBugtracker', msg.args[0] if ircutils.isChannel(msg.args[0]) else None)
319 irc.error(s % name)333 irc.error(s % name)
320 else:334 else:
321 if self.db:335 if self.db:
@@ -328,7 +342,7 @@
328342
329 def bugSnarfer(self, irc, msg, match):343 def bugSnarfer(self, irc, msg, match):
330 r"""\b(?P<bt>(([a-z0-9]+)?\s+bugs?|[a-z0-9]+)):?\s+#?(?P<bug>\d+(?!\d*[\-\.]\d+)((,|\s*(and|en|et|und|ir))\s*#?\d+(?!\d*[\-\.]\d+))*)"""344 r"""\b(?P<bt>(([a-z0-9]+)?\s+bugs?|[a-z0-9]+)):?\s+#?(?P<bug>\d+(?!\d*[\-\.]\d+)((,|\s*(and|en|et|und|ir))\s*#?\d+(?!\d*[\-\.]\d+))*)"""
331 channel = ircutils.isChannel(msg.args[0]) and msg.args[0] or None345 channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None
332 if not self.registryValue('bugSnarfer', channel):346 if not self.registryValue('bugSnarfer', channel):
333 return347 return
334 nbugs = msg.tagged('nbugs')348 nbugs = msg.tagged('nbugs')
@@ -403,13 +417,14 @@
403 for bugid in bugids:417 for bugid in bugids:
404 bugid = int(bugid)418 bugid = int(bugid)
405 try:419 try:
406 report = self.get_bug(channel,tracker,bugid,self.registryValue('showassignee', channel), show_tracker=showTracker)420 report = self.get_bug(channel or msg.nick, tracker, bugid, self.registryValue('showassignee', channel),
421 self.registryValue('extended', channel), do_tracker=showTracker)
407 except BugNotFoundError:422 except BugNotFoundError:
408 if self.registryValue('replyWhenNotFound'):423 if self.registryValue('replyWhenNotFound'):
409 irc.error("%s bug %d could not be found" % (tracker.description, bugid))424 irc.error("%s bug %d could not be found" % (tracker.description, bugid))
410 except BugtrackerError, e:425 except BugtrackerError, e:
411# if 'private' in str(e):426# if 'private' in str(e):
412# irc.reply("Bug %d on http://launchpad.net/bugs/%d is private" % (bugid, bugid))427# irc.reply("Bug %d on https://launchpad.net/bugs/%d is private" % (bugid, bugid))
413# return428# return
414 if not sure_bug and bugid < 30:429 if not sure_bug and bugid < 30:
415 return430 return
@@ -420,7 +435,7 @@
420435
421 def turlSnarfer(self, irc, msg, match):436 def turlSnarfer(self, irc, msg, match):
422 r"(?P<tracker>https?://\S*?)/(?:Bugs/0*|str.php\?L|show_bug.cgi\?id=|bugreport.cgi\?bug=|(?:bugs|\+bug)/|ticket/|tracker/|\S*aid=|bug=)?(?P<bug>\d+)(?P<sfurl>&group_id=\d+&at_id=\d+)?"437 r"(?P<tracker>https?://\S*?)/(?:Bugs/0*|str.php\?L|show_bug.cgi\?id=|bugreport.cgi\?bug=|(?:bugs|\+bug)/|ticket/|tracker/|\S*aid=|bug=)?(?P<bug>\d+)(?P<sfurl>&group_id=\d+&at_id=\d+)?"
423 channel = ircutils.isChannel(msg.args[0]) and msg.args[0] or None438 channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None
424 if not self.registryValue('bugSnarfer', channel):439 if not self.registryValue('bugSnarfer', channel):
425 return440 return
426 nbugs = msg.tagged('nbugs')441 nbugs = msg.tagged('nbugs')
@@ -432,7 +447,8 @@
432 tracker = self.get_tracker(match.group(0),match.group('sfurl'))447 tracker = self.get_tracker(match.group(0),match.group('sfurl'))
433 if not tracker:448 if not tracker:
434 return449 return
435 report = self.get_bug(channel, tracker, int(match.group('bug')), self.registryValue('showassignee', channel), do_url = False)450 report = self.get_bug(channel or msg.nick, tracker, int(match.group('bug')), self.registryValue('showassignee', channel),
451 self.registryValue('extended', channel), do_url=False)
436 except BugtrackerError, e:452 except BugtrackerError, e:
437 irc.error(str(e))453 irc.error(str(e))
438 except BugNotFoundError, e:454 except BugNotFoundError, e:
@@ -444,32 +460,32 @@
444 # Only useful for launchpad developers460 # Only useful for launchpad developers
445 def oopsSnarfer(self, irc, msg, match):461 def oopsSnarfer(self, irc, msg, match):
446 r"(?:https?://pad.lv/){0}?OOPS-(?P<oopsid>\d*[\dA-Z]{3,})"462 r"(?:https?://pad.lv/){0}?OOPS-(?P<oopsid>\d*[\dA-Z]{3,})"
447 channel = ircutils.isChannel(msg.args[0]) and msg.args[0] or None463 channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None
448 if not self.registryValue('bugSnarfer', channel) or not self.registryValue('oopsSnarfer', channel):464 if not self.registryValue('bugSnarfer', channel) or not self.registryValue('oopsSnarfer', channel):
449 return465 return
450 oopsid = match.group(1)466 oopsid = match.group(1)
451 if oopsid.lower() == "tools":467 if oopsid.lower() == "tools":
452 return468 return
453 if not self.is_ok(channel, 'lpoops', oopsid):469 if not self.is_ok(channel or msg.nick, 'lpoops', oopsid):
454 return470 return
455 irc.reply('https://oops.canonical.com/?oopsid=OOPS-' + oopsid, prefixNick=False)471 irc.reply('https://oops.canonical.com/?oopsid=OOPS-%s' % oopsid, prefixNick=False)
456472
457 def cveSnarfer(self, irc, msg, match):473 def cveSnarfer(self, irc, msg, match):
458 r"(cve[- ]\d{4}[- ]\d{4,})"474 r"(cve[- ]\d{4}[- ]\d{4,})"
459 channel = ircutils.isChannel(msg.args[0]) and msg.args[0] or None475 channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None
460 if not self.registryValue('bugSnarfer', channel) or not self.registryValue('cveSnarfer', channel):476 if not self.registryValue('bugSnarfer', channel) or not self.registryValue('cveSnarfer', channel):
461 return477 return
462 cve = match.group(1).replace(' ','-').upper()478 cve = match.group(1).replace(' ','-').upper()
463 if not self.is_ok(channel, 'cve', cve):479 if not self.is_ok(channel or msg.nick, 'cve', cve):
464 return480 return
465 url = 'http://cve.mitre.org/cgi-bin/cvename.cgi?name=%s' % cve481 url = 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s' % cve
466 cvedata = utils.web.getUrl(url)482 cvedata = utils.web.getUrl(url)
467 m = cvere.search(cvedata)483 m = cvere.search(cvedata)
468 if m:484 if m:
469 cve = m.group(1).replace('\n', ' ')485 cve = m.group(1).replace('\n', ' ')
470 if len(cve) > 380:486 if len(cve) > 380:
471 cve = cve[:380] + '...'487 cve = cve[:380] + '...'
472 irc.reply("%s (%s)" % (cve,url), prefixNick=False)488 irc.reply("%s <%s>" % (cve, url), prefixNick=False)
473489
474#TODO: as we will depend on launchpadlib, we should consider using lazr.uri.URI to do URL parsing490#TODO: as we will depend on launchpadlib, we should consider using lazr.uri.URI to do URL parsing
475 def get_tracker(self, snarfurl, sfdata):491 def get_tracker(self, snarfurl, sfdata):
@@ -529,39 +545,57 @@
529 return tracker545 return tracker
530 return None546 return None
531547
532 def get_bug(self, channel, tracker, id, do_assignee, do_url = True, show_tracker = True):548 def get_bug(self, channel, tracker, id, do_assignee, do_extinfo, do_url=True, do_tracker=True):
533 reports = []549 reports = []
550 message_max = 450 - len(channel)
551
534 if not self.is_ok(channel, tracker, id):552 if not self.is_ok(channel, tracker, id):
535 return []553 return []
554
536 for r in tracker.get_bug(id):555 for r in tracker.get_bug(id):
537 showext = self.registryValue('extended', channel)556 (bid, product, title, severity, status, assignee, url, extinfo, duplicate) = r
538 extinfo = ''557
539 if len(r) == 8:558 if duplicate and not self.is_ok(channel, tracker, bid):
540 (bid, product, title, severity, status, assignee, url, extinfo) = r559 continue
560
561 if do_tracker:
562 report = '%s bug %s' % (tracker.description, bid)
541 else:563 else:
542 (bid, product, title, severity, status, assignee, url) = r564 report = 'Bug %s' % bid
543565
544 severity = severity[0].upper() + severity[1:].lower()
545 status = status[0].upper() + status[1:].lower()
546 tracker_name = tracker.description + ' '
547 if not do_url:
548 url = ''
549 if not show_tracker:
550 tracker_name = ''
551 if product:566 if product:
552 if showext:567 report += ' in %s' % product
553 reports.append("%sbug %s in %s \"%s\" %s [%s,%s] %s" % (tracker_name, bid, product, 568
554 title, extinfo, severity, status, url))569 report += ' "%s"' % title.replace('"', "'").strip()
555 else:570
556 reports.append("%sbug %s in %s \"%s\" [%s,%s] %s" % (tracker_name, bid, product, 571 if do_extinfo and extinfo:
557 title, severity, status, url))572 report += ' (%s)' % ', '.join(extinfo)
558 else:573
559 if showext:
560 reports.append("%sbug %s \"%s\" %s [%s,%s] %s" % (tracker_name, bid, title, extinfo, severity, status, url))
561 else:
562 reports.append("%sbug %s \"%s\" [%s,%s] %s" % (tracker_name, bid, title, severity, status, url))
563 if do_assignee and assignee:574 if do_assignee and assignee:
564 reports[-1] = reports[-1] + (" - Assigned to %s" % assignee)575 report += ' (assigned: %s)' % assignee
576
577 severity_status = []
578 if severity:
579 severity_status.append(' '.join(word[0].upper() + word[1:].lower() for word in severity.split()))
580 severity_status.append(' '.join(word[0].upper() + word[1:].lower() for word in status.split()))
581 report += ' [%s]' % ', '.join(severity_status)
582
583 if duplicate:
584 report += ' [duplicate: %s]' % duplicate[0]
585
586 if do_url:
587 report += ' %s' % url
588
589 if len(report) > message_max:
590 report_parts = report.split('"')
591 report_start = report_parts[0]
592 report_end = report_parts[-1]
593 report_title = '"'.join(report_parts[1:-1])
594 title_max = message_max - len(report_start) - len(report_end) - 5
595 report_title_cut = report_title[:title_max].rsplit(None, 1)[0] + '...'
596 report = '%s"%s"%s' % (report_start, report_title_cut, report_end)
597
598 reports.append(report)
565 return reports599 return reports
566600
567# Define all bugtrackers601# Define all bugtrackers
@@ -608,6 +642,7 @@
608 return tracker642 return tracker
609 except:643 except:
610 return None644 return None
645
611 def get_bug(self, id):646 def get_bug(self, id):
612 url = "%s/show_bug.cgi?id=%d&ctype=xml" % (self.url,id)647 url = "%s/show_bug.cgi?id=%d&ctype=xml" % (self.url,id)
613 try:648 try:
@@ -630,49 +665,22 @@
630 status = "%s: %s" % (status, _getnodetxt(bug_n.getElementsByTagName('resolution')[0]))665 status = "%s: %s" % (status, _getnodetxt(bug_n.getElementsByTagName('resolution')[0]))
631 except:666 except:
632 pass667 pass
633 component = _getnodetxt(bug_n.getElementsByTagName('component')[0])668 product = _getnodetxt(bug_n.getElementsByTagName('product')[0])
634 severity = _getnodetxt(bug_n.getElementsByTagName('bug_severity')[0])669 severity = _getnodetxt(bug_n.getElementsByTagName('bug_severity')[0])
635 assignee = '(unavailable)'
636 try:670 try:
637 assignee = _getnodetxt(bug_n.getElementsByTagName('assigned_to')[0])671 assignee = _getnodeattr(bug_n.getElementsByTagName('assigned_to')[0], 'name')
638 except:672 except:
639 pass673 try:
674 assignee = _getnodetxt(bug_n.getElementsByTagName('assigned_to')[0])
675 except:
676 assignee = ''
640 except Exception, e:677 except Exception, e:
641 s = 'Could not parse XML returned by %s bugzilla: %s (%s)' % (self.description, e, url)678 s = 'Could not parse XML returned by %s bugzilla: %s (%s)' % (self.description, e, url)
642 raise BugtrackerError, s679 raise BugtrackerError, s
643 return [(id, component, title, severity, status, assignee, "%s/show_bug.cgi?id=%d" % (self.url, id))]680 return [(id, product, title, severity, status, assignee, "%s/show_bug.cgi?id=%d" % (self.url, id), [], [])]
644681
645class Issuezilla(Bugzilla):682class Issuezilla(Bugzilla):
646 pass683 pass
647#class Issuezilla(IBugtracker):
648# def get_bug(self, id):
649# url = "%s/show_bug.cgi?id=%d&ctype=xml" % (self.url,id)
650# try:
651# bugxml = utils.web.getUrl(url)
652# zilladom = minidom.parseString(bugxml)
653# except Exception, e:
654# s = 'Could not parse XML returned by %s: %s (%s)' % (self.description, e, url)
655# raise BugtrackerError, s
656# bug_n = zilladom.getElementsByTagName('issue')[0]
657# if not (bug_n.getAttribute('status_code') == '200'):
658# if bug_n.getAttribute('status_message') == 'NotFound':
659# raise BugNotFoundError
660# s = 'Error getting %s bug #%s: %s' % (self.description, id, bug_n.getAttribute('status_message'))
661# raise BugtrackerError, s
662# try:
663# title = _getnodetxt(bug_n.getElementsByTagName('short_desc')[0])
664# status = _getnodetxt(bug_n.getElementsByTagName('issue_status')[0])
665# try:
666# status = "%s: %s" % (status, _getnodetxt(bug_n.getElementsByTagName('resolution')[0]))
667# except:
668# pass
669# component = _getnodetxt(bug_n.getElementsByTagName('component')[0])
670# severity = _getnodetxt(bug_n.getElementsByTagName('issue_type')[0])
671# assignee = _getnodetxt(bug_n.getElementsByTagName('assigned_to')[0])
672# except Exception, e:
673# s = 'Could not parse XML returned by %s bugzilla: %s (%s)' % (self.description, e, url)
674# raise BugtrackerError, s
675# return [(id, component, title, severity, status, assignee, "%s/show_bug.cgi?id=%d" % (self.url, id))]
676684
677class Launchpad(IBugtracker):685class Launchpad(IBugtracker):
678 statuses = ["Unknown", "Invalid", "Opinion", "Won't Fix", "Fix Released", "Fix Committed", "New", "Incomplete", "Confirmed", "Triaged", "In Progress"]686 statuses = ["Unknown", "Invalid", "Opinion", "Won't Fix", "Fix Released", "Fix Committed", "New", "Incomplete", "Confirmed", "Triaged", "In Progress"]
@@ -705,7 +713,7 @@
705 supylog.exception("Unknown exception while accessing the Launchpad API")713 supylog.exception("Unknown exception while accessing the Launchpad API")
706714
707 def _parse(self, task): #Depricated715 def _parse(self, task): #Depricated
708 parser = email.FeedParser.FeedParser()716 parser = FeedParser()
709 parser.feed(task)717 parser.feed(task)
710 return parser.close()718 return parser.close()
711719
@@ -770,15 +778,15 @@
770 bugdata = self.lp.bugs[id]778 bugdata = self.lp.bugs[id]
771 if bugdata.private:779 if bugdata.private:
772 raise BugtrackerError, "This bug is private"780 raise BugtrackerError, "This bug is private"
781 duplicate = []
773 dup = bugdata.duplicate_of782 dup = bugdata.duplicate_of
774 summary_prefix = '' # Used to made dups easier
775 while dup:783 while dup:
776 summary_prefix = 'duplicate for #%d ' % id784 duplicate.append(str(bugdata.id))
777 bugdata = dup785 bugdata = dup
778 dup = bugdata.duplicate_of786 dup = bugdata.duplicate_of
779787
780 affected = bugdata.users_affected_count_with_dupes788 extinfo = ['affected: %d' % bugdata.users_affected_count_with_dupes]
781 heat = bugdata.heat789 extinfo.append('heat: %d' % bugdata.heat)
782 tasks = bugdata.bug_tasks790 tasks = bugdata.bug_tasks
783791
784 if tasks.total_size != 1:792 if tasks.total_size != 1:
@@ -787,7 +795,7 @@
787 tasks.sort(self._sort)795 tasks.sort(self._sort)
788 taskdata = tasks[-1]796 taskdata = tasks[-1]
789 except ValueError:797 except ValueError:
790 tasks = [_ for _ in tasks if _.bug_target_name.endswith(u'(Ubuntu)')]798 tasks = [_ for _ in tasks if _.bug_target_name.endswith('(Ubuntu)')]
791 if tasks:799 if tasks:
792 if len(tasks) != 1:800 if len(tasks) != 1:
793 try:801 try:
@@ -802,18 +810,15 @@
802 else:810 else:
803 taskdata = tasks[0]811 taskdata = tasks[0]
804812
805 assignee = taskdata.assignee813 if taskdata.assignee:
806 t = taskdata.bug_target_display_name #task name814 assignee = taskdata.assignee.display_name
807
808 if assignee: # "Diaplay Name (Launchpad ID)"
809 assignee = u"%s (%s)" % (assignee.display_name, assignee.name)
810 else:815 else:
811 assignee = ''816 assignee = ''
812817
813 except Exception, e:818 except Exception, e:
814 if type(e).__name__ == 'HTTPError': # messy, but saves trying to import lazr.restfulclient.errors.HTPError819 if type(e).__name__ == 'HTTPError': # messy, but saves trying to import lazr.restfulclient.errors.HTPError
815 if e.response.status == 404:820 if e.response.status == 404:
816 bugNo = e.content.split(None)[-1][2:-1] # extract the real bug number821 bugNo = e.content.split()[-1][2:-1] # extract the real bug number
817 if bugNo != str(id): # A duplicate of a private bug, at least we know it exists822 if bugNo != str(id): # A duplicate of a private bug, at least we know it exists
818 raise BugtrackerError, 'Bug #%s is a duplicate of bug #%s, but it is private (%s/bugs/%s)' % (id, bugNo, self.url, bugNo)823 raise BugtrackerError, 'Bug #%s is a duplicate of bug #%s, but it is private (%s/bugs/%s)' % (id, bugNo, self.url, bugNo)
819 raise BugtrackerError, "Bug #%s (%s/bugs/%d) is private or doesn't exist" % (id, self.url, id) # Could be private, could just not exist824 raise BugtrackerError, "Bug #%s (%s/bugs/%d) is private or doesn't exist" % (id, self.url, id) # Could be private, could just not exist
@@ -825,58 +830,52 @@
825 supylog.exception("Error gathering bug data for %s bug %d" % (self.description, id))830 supylog.exception("Error gathering bug data for %s bug %d" % (self.description, id))
826 raise BugtrackerError, "Could not gather data from %s for bug #%s (%s/bugs/%s). The error has been logged" % (self.description, id, self.url, id)831 raise BugtrackerError, "Could not gather data from %s for bug #%s (%s/bugs/%s). The error has been logged" % (self.description, id, self.url, id)
827832
828 extinfo = "(affected: %d, heat: %d)" % (affected, heat)833 return [(bugdata.id, taskdata.bug_target_display_name, bugdata.title, taskdata.importance, taskdata.status,
829834 assignee, "%s/bugs/%s" % (self.url, bugdata.id), extinfo, duplicate)]
830 return [(bugdata.id, t, summary_prefix + bugdata.title, taskdata.importance, taskdata.status,835
831 assignee, "%s/bugs/%s" % (self.url, bugdata.id), extinfo)]836 def get_bug_old(self, id, duplicate=None): #Depricated
832
833 def get_bug_old(self, id): #Depricated
834 if id == 1:837 if id == 1:
835 raise BugtrackerError, "https://bugs.launchpad.net/ubuntu/+bug/1 (Not reporting large bug)"838 raise BugtrackerError, "https://bugs.launchpad.net/ubuntu/+bug/1 (Not reporting large bug)"
836839
837 try:840 try:
838 bugdata = utils.web.getUrl("%s/bugs/%d/+text" % (self.url,id))841 bugdata = utils.web.getUrl("%s/bugs/%d/+text" % (self.url, id))
839 except Exception, e:842 except Exception, e:
840 if '404' in str(e):843 if '404' in str(e):
841 raise BugNotFoundError844 if duplicate:
845 raise BugtrackerError, 'Bug #%s is a duplicate of bug #%s, but it is private (%s/bugs/%s)' % (duplicate, id, self.url, id)
846 else:
847 raise BugNotFoundError
842 s = 'Could not parse data returned by %s: %s (%s/bugs/%d)' % (self.description, e, self.url, id)848 s = 'Could not parse data returned by %s: %s (%s/bugs/%d)' % (self.description, e, self.url, id)
843 raise BugtrackerError, s849 raise BugtrackerError, s
844 summary = {}850
845 # Trap private bugs851 # Trap private bugs
846 if "<!-- 4a. didn't try to log in last time: -->" in bugdata:852 if "<!-- 4a. didn't try to log in last time: -->" in bugdata:
847 raise BugtrackerError, "This bug is private"853 raise BugtrackerError, "This bug is private"
848 try:854 try:
849 # Split bug data into separate pieces (bug data, task data)855 # Split bug data into separate pieces (bug data, task data)
850 data = bugdata.split('\n\n')856 data = bugdata.split('\n\nContent-Type:', 1)[0].split('\n\n')
851 bugdata = data[0]857 bugdata = self._parse(data[0])
852 taskdata = data[1:]858 if not bugdata['duplicate-of']:
853 parser = email.FeedParser.FeedParser()859 taskdata = map(self._parse, data[1:])
854 parser.feed(bugdata)860 taskdata.sort(self._old_sort)
855 bugdata = parser.close()861 taskdata = taskdata[-1]
856 taskdata = map(self._parse, taskdata)862 if taskdata['assignee']:
857 taskdata.sort(self._old_sort)863 assignee = re.sub(r' \([^)]*\)$', '', taskdata['assignee'])
858 taskdata = taskdata[-1]864 else:
859 865 assignee = ''
860 except Exception, e:866 except Exception, e:
861 s = 'Could not parse data returned by %s: %s (%s/bugs/%d)' % (self.description, e, self.url, id)867 s = 'Could not parse data returned by %s: %s (%s/bugs/%d)' % (self.description, e, self.url, id)
862 raise BugtrackerError, s868 raise BugtrackerError, s
869
863 # Try and find duplicates870 # Try and find duplicates
864 t = taskdata['task']871 if bugdata['duplicate-of']:
865 if '(' in t:872 data = self.get_bug_old(int(bugdata['duplicate-of']), duplicate or id)[0]
866 t = t[:t.rfind('(') -1]873 data[8].append(bugdata['bug'])
867 if bugdata['duplicate-of']: # This will suck if for dup of dups..., but +text is pure suck anyway874 return [data]
868 bugNo = bugdata['duplicate-of']875
869 try:876 return [(id, taskdata['task'], bugdata['title'], taskdata['importance'], taskdata['status'],
870 data = self.get_bug(int(bugdata['duplicate-of']))877 assignee, "%s/bugs/%s" % (self.url, id), [], [])]
871 except Exception, e:878
872 if '404' in str(e):
873 raise BugtrackerError, 'Bug #%s is a duplicate of Bug #%s, but it is private. (%s/bugs/%s)' % (id, bugNo, self.url, bugNo)
874 data = list(data[0])
875 data[2] = ('duplicate for #%d ' % id) + data[2]
876 return [tuple(data)]
877 return [(id, t, bugdata['title'], taskdata['importance'],
878 taskdata['status'], taskdata['assignee'], "%s/bugs/%s" % (self.url, id))]
879
880# <rant>879# <rant>
881# Debbugs sucks donkeyballs880# Debbugs sucks donkeyballs
882# * HTML pages are inconsistent881# * HTML pages are inconsistent
@@ -890,11 +889,10 @@
890class Debbugs(IBugtracker):889class Debbugs(IBugtracker):
891 def __init__(self, *args, **kwargs):890 def __init__(self, *args, **kwargs):
892 IBugtracker.__init__(self, *args, **kwargs)891 IBugtracker.__init__(self, *args, **kwargs)
893 self.soap_proxy = SOAPpy.SOAPProxy("bugs.debian.org/cgi-bin/soap.cgi", "Debbugs/SOAP/Status")892 self.soap_proxy = SOAPProxy("%s/cgi-bin/soap.cgi" % self.url, namespace="Debbugs/SOAP")
894 self.soap_proxy.soapaction = "Debbugs/SOAP/Status#get_status"
895893
896 def get_bug(self, id):894 def get_bug(self, id):
897 bug_url = "http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%d" % id895 bug_url = "%s/cgi-bin/bugreport.cgi?bug=%d" % (self.url, id)
898 try:896 try:
899 raw = self.soap_proxy.get_status(id)897 raw = self.soap_proxy.get_status(id)
900 except Exception, e:898 except Exception, e:
@@ -902,13 +900,13 @@
902 raise BugtrackerError, s900 raise BugtrackerError, s
903 if not raw:901 if not raw:
904 raise BugNotFoundError902 raise BugNotFoundError
905 raw = raw['item']['value']
906 try:903 try:
907 if len(raw['fixed_versions']):904 raw = raw['item']['value']
905 if raw['fixed_versions']:
908 status = 'Fixed'906 status = 'Fixed'
909 else:907 else:
910 status = 'Open'908 status = 'Open'
911 return [(id, raw['package'], raw['subject'], raw['severity'], status, '', "%s/%s" % (self.url, id))]909 return [(id, raw['package'], raw['subject'], raw['severity'], status, '', "%s/%s" % (self.url, id), [], [])]
912 except Exception, e:910 except Exception, e:
913 s = 'Could not parse data returned by %s bugtracker: %s (%s)' % (self.description, e, bug_url)911 s = 'Could not parse data returned by %s bugtracker: %s (%s)' % (self.description, e, bug_url)
914 raise BugtrackerError, s912 raise BugtrackerError, s
@@ -916,11 +914,10 @@
916class Mantis(IBugtracker):914class Mantis(IBugtracker):
917 def __init__(self, *args, **kwargs):915 def __init__(self, *args, **kwargs):
918 IBugtracker.__init__(self, *args, **kwargs)916 IBugtracker.__init__(self, *args, **kwargs)
919 self.soap_proxy = SOAPpy.SOAPProxy(self.url + "/api/soap/mantisconnect.php", "http://futureware.biz/mantisconnect")917 self.soap_proxy = SOAPProxy("%s/api/soap/mantisconnect.php" % self.url, namespace="http://futureware.biz/mantisconnect")
920 self.soap_proxy.soapaction = "http://futureware.biz/mantisconnect#mc_issue_get"
921918
922 def get_bug(self, id):919 def get_bug(self, id):
923 url = self.url + "/view.php?id=%i" % id920 url = "%s/view.php?id=%d" % (self.url, id)
924 try:921 try:
925 raw = self.soap_proxy.mc_issue_get('', "", id)922 raw = self.soap_proxy.mc_issue_get('', "", id)
926 except Exception, e:923 except Exception, e:
@@ -929,7 +926,7 @@
929 if not raw:926 if not raw:
930 raise BugNotFoundError927 raise BugNotFoundError
931 try:928 try:
932 return [(id, raw['project']['name'], raw['summary'], raw['priority']['name'], raw['resolution']['name'], '', url)]929 return [(id, raw['project']['name'], raw['summary'], raw['severity']['name'], raw['resolution']['name'], '', url, [], [])]
933 except Exception, e:930 except Exception, e:
934 s = 'Could not parse data returned by %s bugtracker: %s (%s)' % (self.description, e, url)931 s = 'Could not parse data returned by %s bugtracker: %s (%s)' % (self.description, e, url)
935 raise BugtrackerError, s932 raise BugtrackerError, s
@@ -952,7 +949,8 @@
952 (headers, rest) = raw.split('\n', 1)949 (headers, rest) = raw.split('\n', 1)
953 headers = headers.strip().split('\t')950 headers = headers.strip().split('\t')
954 rest = rest.strip().split('\t')951 rest = rest.strip().split('\t')
955 title = status = package = severity = assignee = "Unknown"952
953 title = status = package = severity = assignee = ""
956 if "summary" in headers:954 if "summary" in headers:
957 title = rest[headers.index("summary")]955 title = rest[headers.index("summary")]
958 if "status" in headers:956 if "status" in headers:
@@ -961,13 +959,12 @@
961 package = rest[headers.index("component")]959 package = rest[headers.index("component")]
962 if "severity" in headers:960 if "severity" in headers:
963 severity = rest[headers.index("severity")]961 severity = rest[headers.index("severity")]
962 elif "priority" in headers:
963 severity = rest[headers.index("priority")]
964 if "owner" in headers:964 if "owner" in headers:
965 assingee = rest[headers.index("owner")]965 assignee = rest[headers.index("owner")]
966 if severity == "Unknown" and "priority" in headers:966 return [(id, package, title, severity, status, assignee, bug_url, [], [])]
967 severity = rest[headers.index("priority")]
968967
969 return [(id, package, title, severity, status, assignee, bug_url)]
970
971class WikiForms(IBugtracker):968class WikiForms(IBugtracker):
972 def get_bug(self, id):969 def get_bug(self, id):
973 def strip_tags(s):970 def strip_tags(s):
@@ -986,14 +983,14 @@
986 for l in bugdata.split("\n"):983 for l in bugdata.split("\n"):
987 l2 = l.lower()984 l2 = l.lower()
988 if '<dt>importance</dt>' in l2:985 if '<dt>importance</dt>' in l2:
989 severity = 'Importance ' + strip_tags(l[l.find('<dd>')+4:])986 severity = strip_tags(l[l.find('<dd>')+4:])
990 if '<dt>summary</dt>' in l2:987 if '<dt>summary</dt>' in l2:
991 title = strip_tags(l[l.find('<dd>')+4:])988 title = strip_tags(l[l.find('<dd>')+4:])
992 if '<dt>status</dt>' in l2:989 if '<dt>status</dt>' in l2:
993 status = strip_tags(l[l.find('<dd>')+4:])990 status = strip_tags(l[l.find('<dd>')+4:])
994 if '<dt>category</dt>' in l2:991 if '<dt>category</dt>' in l2:
995 package = strip_tags(l[l.find('<dd>')+4:])992 package = strip_tags(l[l.find('<dd>')+4:])
996 return [(id, package, title, severity, status, '', "%s/%05d" % (self.url, id))]993 return [(id, package, title, severity, status, '', "%s/%05d" % (self.url, id), [], [])]
997994
998class Str(IBugtracker):995class Str(IBugtracker):
999 def get_bug(self, id):996 def get_bug(self, id):
@@ -1010,7 +1007,7 @@
1010 for l in bugdata.split("\n"):1007 for l in bugdata.split("\n"):
1011 l2 = l.lower()1008 l2 = l.lower()
1012 if 'nowrap>priority:</th>' in l2:1009 if 'nowrap>priority:</th>' in l2:
1013 severity = 'Priority ' + l[l.find(' - ')+3:min(l.find(','),l.find('</td>'))]1010 severity = l[l.find(' - ')+3:min(l.find(','),l.find('</td>'))]
1014 if '>application:</th>' in l2:1011 if '>application:</th>' in l2:
1015 package = l[l.find('<td>')+4:l.find('</td>')]1012 package = l[l.find('<td>')+4:l.find('</td>')]
1016 if 'nowrap>status:</th>' in l2:1013 if 'nowrap>status:</th>' in l2:
@@ -1020,9 +1017,8 @@
1020 if 'nowrap>assigned to:</th>' in l2:1017 if 'nowrap>assigned to:</th>' in l2:
1021 assignee = strip_tags(l[l.find('<td>')+4:l.find('</td>')])1018 assignee = strip_tags(l[l.find('<td>')+4:l.find('</td>')])
1022 if assignee == 'Unassigned':1019 if assignee == 'Unassigned':
1023 assignee = 'nobody'1020 assignee = ''
1024 return [(id, package, title, severity, status, assignee, "%s?L%d" % (self.url, id))]1021 return [(id, package, title, severity, status, assignee, "%s?L%d" % (self.url, id), [], [])]
1025
10261022
1027sfre = re.compile(r"""1023sfre = re.compile(r"""
1028 .*?1024 .*?
@@ -1051,9 +1047,12 @@
1051 reo = sfre.search(bugdata)1047 reo = sfre.search(bugdata)
1052 status = reo.group('status')1048 status = reo.group('status')
1053 resolution = reo.group('resolution')1049 resolution = reo.group('resolution')
1054 if not (resolution.lower() == 'none'):1050 if resolution.lower() != 'none':
1055 status += ' ' + resolution1051 status += ': %s' % resolution
1056 return [(id, None, reo.group('title'), "Pri: %s" % reo.group('priority'), status, reo.group('assignee'),self._sf_url % id)]1052 assignee = reo.group('assignee')
1053 if assignee.lower() == 'nobody':
1054 assignee = ''
1055 return [(id, '', reo.group('title'), "Pri: %s" % reo.group('priority'), status, assignee, self._sf_url % id, [], [])]
1057 except:1056 except:
1058 raise BugNotFoundError1057 raise BugNotFoundError
10591058
@@ -1064,26 +1063,25 @@
1064 if type(v[k]) == type(IBugtracker) and issubclass(v[k], IBugtracker) and not (v[k] == IBugtracker):1063 if type(v[k]) == type(IBugtracker) and issubclass(v[k], IBugtracker) and not (v[k] == IBugtracker):
1065 defined_bugtrackers[k.lower()] = v[k]1064 defined_bugtrackers[k.lower()] = v[k]
10661065
1067registerBugtracker('mozilla', 'http://bugzilla.mozilla.org', 'Mozilla', 'bugzilla')1066registerBugtracker('mozilla', 'https://bugzilla.mozilla.org', 'Mozilla', 'bugzilla')
1067registerBugtracker('gnome', 'https://bugzilla.gnome.org', 'Gnome', 'bugzilla')
1068registerBugtracker('gnome2', 'https://bugs.gnome.org', 'Gnome', 'bugzilla')
1069registerBugtracker('kde', 'https://bugs.kde.org', 'KDE', 'bugzilla')
1070registerBugtracker('xfce', 'https://bugzilla.xfce.org', 'Xfce', 'bugzilla')
1071registerBugtracker('freedesktop', 'https://bugzilla.freedesktop.org', 'Freedesktop', 'bugzilla')
1072registerBugtracker('freedesktop2', 'https://bugs.freedesktop.org', 'Freedesktop', 'bugzilla')
1073registerBugtracker('openoffice', 'https://bz.apache.org/ooo', 'OpenOffice', 'bugzilla')
1068registerBugtracker('ubuntu', 'https://launchpad.net', 'Ubuntu', 'launchpad')1074registerBugtracker('ubuntu', 'https://launchpad.net', 'Ubuntu', 'launchpad')
1069registerBugtracker('gnome', 'http://bugzilla.gnome.org', 'Gnome', 'bugzilla')1075registerBugtracker('ubottu', 'https://launchpad.net', 'Ubottu', 'launchpad')
1070registerBugtracker('gnome2', 'http://bugs.gnome.org', 'Gnome', 'bugzilla')
1071registerBugtracker('kde', 'http://bugs.kde.org', 'KDE', 'bugzilla')
1072registerBugtracker('ximian', 'http://bugzilla.ximian.com', 'Ximian', 'bugzilla')
1073registerBugtracker('freedesktop', 'http://bugzilla.freedesktop.org', 'Freedesktop', 'bugzilla')
1074registerBugtracker('freedesktop2', 'http://bugs.freedesktop.org', 'Freedesktop', 'bugzilla')
1075registerBugtracker('openoffice', 'http://openoffice.org/issues', 'OpenOffice.org', 'issuezilla')
1076registerBugtracker('launchpad', 'https://launchpad.net', 'Launchpad', 'launchpad')1076registerBugtracker('launchpad', 'https://launchpad.net', 'Launchpad', 'launchpad')
1077registerBugtracker('lp', 'https://launchpad.net', 'Launchpad', 'launchpad')1077registerBugtracker('lp', 'https://launchpad.net', 'Launchpad', 'launchpad')
1078registerBugtracker('malone', 'https://launchpad.net', 'Launchpad', 'launchpad')1078registerBugtracker('debian', 'https://bugs.debian.org', 'Debian', 'debbugs')
1079registerBugtracker('debian', 'http://bugs.debian.org', 'Debian', 'debbugs')1079registerBugtracker('mantis', 'https://www.mantisbt.org/bugs', 'Mantis', 'mantis')
1080registerBugtracker('trac', 'http://trac.edgewall.org/ticket', 'Trac', 'trac')1080registerBugtracker('trac', 'https://trac.edgewall.org/ticket', 'Trac', 'trac')
1081registerBugtracker('django', 'http://code.djangoproject.com/ticket', 'Django', 'trac')1081registerBugtracker('django', 'https://code.djangoproject.com/ticket', 'Django', 'trac')
1082registerBugtracker('cups', 'http://www.cups.org/str.php', 'CUPS', 'str')1082# Outdated
1083registerBugtracker('gnewsense', 'http://bugs.gnewsense.org/Bugs', 'gNewSense', 'wikiforms')1083#registerBugtracker('cups', 'http://www.cups.org/str.php', 'CUPS', 'str')
1084registerBugtracker('supybot', 'http://sourceforge.net/tracker/?group_id=58965&atid=489447', 'Supybot', 'sourceforge')1084#registerBugtracker('gnewsense', 'http://bugs.gnewsense.org/Bugs', 'gNewSense', 'wikiforms')
1085registerBugtracker('mantis', "http://www.mantisbt.org/bugs", "Mantis", 'mantis')1085#registerBugtracker('sourceforge', 'http://sourceforge.net/tracker/', 'Sourceforge', 'sourceforge')
1086registerBugtracker('ubottu', 'https://launchpad.net', 'Ubottu', 'launchpad')1086#registerBugtracker('supybot', 'http://sourceforge.net/tracker/?group_id=58965&atid=489447', 'Supybot', 'sourceforge')
1087# Don't delete this one
1088registerBugtracker('sourceforge', 'http://sourceforge.net/tracker/', 'Sourceforge', 'sourceforge')
1089Class = Bugtracker1087Class = Bugtracker

Subscribers

People subscribed via source and target branches