comparison sat/plugins/plugin_tickets_import_bugzilla.py @ 2562:26edcf3a30eb

core, setup: huge cleaning: - moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention - move twisted directory to root - removed all hacks from setup.py, and added missing dependencies, it is now clean - use https URL for website in setup.py - removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed - renamed sat.sh to sat and fixed its installation - added python_requires to specify Python version needed - replaced glib2reactor which use deprecated code by gtk3reactor sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author Goffi <goffi@goffi.org>
date Mon, 02 Apr 2018 19:44:50 +0200
parents src/plugins/plugin_tickets_import_bugzilla.py@0046283a285d
children 3e4e78de9cca
comparison
equal deleted inserted replaced
2561:bd30dc3ffe5a 2562:26edcf3a30eb
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3
4 # SàT plugin for import external blogs
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from sat.core.i18n import _, D_
21 from sat.core.constants import Const as C
22 from sat.core.log import getLogger
23 log = getLogger(__name__)
24 from sat.core import exceptions
25 # from twisted.internet import threads
26 from twisted.internet import defer
27 import os.path
28 from lxml import etree
29 from sat.tools import utils
30
31
32 PLUGIN_INFO = {
33 C.PI_NAME: "Bugzilla import",
34 C.PI_IMPORT_NAME: "IMPORT_BUGZILLA",
35 C.PI_TYPE: C.PLUG_TYPE_BLOG,
36 C.PI_DEPENDENCIES: ["TICKETS_IMPORT"],
37 C.PI_MAIN: "BugzillaImport",
38 C.PI_HANDLER: "no",
39 C.PI_DESCRIPTION: _("""Tickets importer for Bugzilla""")
40 }
41
42 SHORT_DESC = D_(u"import tickets from Bugzilla xml export file")
43
44 LONG_DESC = D_(u"""This importer handle Bugzilla xml export file.
45
46 To use it, you'll need to export tickets using XML.
47 Tickets will be uploaded with the same ID as for Bugzilla, any existing ticket with this ID will be replaced.
48
49 location: you must use the absolute path to your .xml file
50 """)
51
52 STATUS_MAP = {
53 'NEW': 'queued',
54 'ASSIGNED': 'started',
55 'RESOLVED': 'review',
56 'CLOSED': 'closed',
57 'REOPENED': 'started' # we loose data here because there is no need on basic workflow to have a reopened status
58 }
59
60
61 class BugzillaParser(object):
62 # TODO: add a way to reassign values
63
64 def parse(self, file_path):
65 tickets = []
66 root = etree.parse(file_path)
67
68 for bug in root.xpath('bug'):
69 ticket = {}
70 ticket['id'] = bug.findtext('bug_id')
71 ticket['created'] = utils.date_parse(bug.findtext('creation_ts'))
72 ticket['updated'] = utils.date_parse(bug.findtext('delta_ts'))
73 ticket['title'] = bug.findtext('short_desc')
74 reporter_elt = bug.find('reporter')
75 ticket['author'] = reporter_elt.get('name')
76 if ticket['author'] is None:
77 if '@' in reporter_elt.text:
78 ticket['author'] = reporter_elt.text[:reporter_elt.text.find('@')].title()
79 else:
80 ticket['author'] = u'no name'
81 ticket['author_email'] = reporter_elt.text
82 assigned_to_elt = bug.find('assigned_to')
83 ticket['assigned_to_name'] = assigned_to_elt.get('name')
84 ticket['assigned_to_email'] = assigned_to_elt.text
85 ticket['cc_emails'] = [e.text for e in bug.findall('cc')]
86 ticket['priority'] = bug.findtext('priority').lower().strip()
87 ticket['severity'] = bug.findtext('bug_severity').lower().strip()
88 ticket['product'] = bug.findtext('product')
89 ticket['component'] = bug.findtext('component')
90 ticket['version'] = bug.findtext('version')
91 ticket['platform'] = bug.findtext('rep_platform')
92 ticket['os'] = bug.findtext('op_sys')
93 ticket['status'] = STATUS_MAP.get(bug.findtext('bug_status'), 'queued')
94 ticket['milestone'] = bug.findtext('target_milestone')
95
96
97 body = None
98 comments = []
99 for longdesc in bug.findall('long_desc'):
100 if body is None:
101 body = longdesc.findtext('thetext')
102 else:
103 who = longdesc.find('who')
104 comment = {'id': longdesc.findtext('commentid'),
105 'author_email': who.text,
106 'published': utils.date_parse(longdesc.findtext('bug_when')),
107 'author': who.get('name', who.text),
108 'content': longdesc.findtext('thetext')}
109 comments.append(comment)
110
111 ticket['body'] = body
112 ticket['comments'] = comments
113 tickets.append(ticket)
114
115 tickets.sort(key = lambda t: int(t['id']))
116 return (tickets, len(tickets))
117
118
119 class BugzillaImport(object):
120
121 def __init__(self, host):
122 log.info(_(u"Bugilla Import plugin initialization"))
123 self.host = host
124 host.plugins['TICKETS_IMPORT'].register('bugzilla', self.Import, SHORT_DESC, LONG_DESC)
125
126 def Import(self, client, location, options=None):
127 if not os.path.isabs(location):
128 raise exceptions.DataError(u"An absolute path to XML data need to be given as location")
129 bugzilla_parser = BugzillaParser()
130 # d = threads.deferToThread(bugzilla_parser.parse, location)
131 d = defer.maybeDeferred(bugzilla_parser.parse, location)
132 return d