comparison libervia/backend/plugins/plugin_tickets_import_bugzilla.py @ 4071:4b842c1fb686

refactoring: renamed `sat` package to `libervia.backend`
author Goffi <goffi@goffi.org>
date Fri, 02 Jun 2023 11:49:51 +0200
parents sat/plugins/plugin_tickets_import_bugzilla.py@524856bd7b19
children
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3
4 # SàT plugin for import external blogs
5 # Copyright (C) 2009-2021 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 libervia.backend.core.i18n import _, D_
21 from libervia.backend.core.constants import Const as C
22 from libervia.backend.core.log import getLogger
23
24 log = getLogger(__name__)
25 from libervia.backend.core import exceptions
26
27 # from twisted.internet import threads
28 from twisted.internet import defer
29 import os.path
30 from lxml import etree
31 from libervia.backend.tools.common import date_utils
32
33
34 PLUGIN_INFO = {
35 C.PI_NAME: "Bugzilla import",
36 C.PI_IMPORT_NAME: "IMPORT_BUGZILLA",
37 C.PI_TYPE: C.PLUG_TYPE_BLOG,
38 C.PI_DEPENDENCIES: ["TICKETS_IMPORT"],
39 C.PI_MAIN: "BugzillaImport",
40 C.PI_HANDLER: "no",
41 C.PI_DESCRIPTION: _("""Tickets importer for Bugzilla"""),
42 }
43
44 SHORT_DESC = D_("import tickets from Bugzilla xml export file")
45
46 LONG_DESC = D_(
47 """This importer handle Bugzilla xml export file.
48
49 To use it, you'll need to export tickets using XML.
50 Tickets will be uploaded with the same ID as for Bugzilla, any existing ticket with this ID will be replaced.
51
52 location: you must use the absolute path to your .xml file
53 """
54 )
55
56 STATUS_MAP = {
57 "NEW": "queued",
58 "ASSIGNED": "started",
59 "RESOLVED": "review",
60 "CLOSED": "closed",
61 "REOPENED": "started", # we loose data here because there is no need on basic workflow to have a reopened status
62 }
63
64
65 class BugzillaParser(object):
66 # TODO: add a way to reassign values
67
68 def parse(self, file_path):
69 tickets = []
70 root = etree.parse(file_path)
71
72 for bug in root.xpath("bug"):
73 ticket = {}
74 ticket["id"] = bug.findtext("bug_id")
75 ticket["created"] = date_utils.date_parse(bug.findtext("creation_ts"))
76 ticket["updated"] = date_utils.date_parse(bug.findtext("delta_ts"))
77 ticket["title"] = bug.findtext("short_desc")
78 reporter_elt = bug.find("reporter")
79 ticket["author"] = reporter_elt.get("name")
80 if ticket["author"] is None:
81 if "@" in reporter_elt.text:
82 ticket["author"] = reporter_elt.text[
83 : reporter_elt.text.find("@")
84 ].title()
85 else:
86 ticket["author"] = "no name"
87 ticket["author_email"] = reporter_elt.text
88 assigned_to_elt = bug.find("assigned_to")
89 ticket["assigned_to_name"] = assigned_to_elt.get("name")
90 ticket["assigned_to_email"] = assigned_to_elt.text
91 ticket["cc_emails"] = [e.text for e in bug.findall("cc")]
92 ticket["priority"] = bug.findtext("priority").lower().strip()
93 ticket["severity"] = bug.findtext("bug_severity").lower().strip()
94 ticket["product"] = bug.findtext("product")
95 ticket["component"] = bug.findtext("component")
96 ticket["version"] = bug.findtext("version")
97 ticket["platform"] = bug.findtext("rep_platform")
98 ticket["os"] = bug.findtext("op_sys")
99 ticket["status"] = STATUS_MAP.get(bug.findtext("bug_status"), "queued")
100 ticket["milestone"] = bug.findtext("target_milestone")
101
102 body = None
103 comments = []
104 for longdesc in bug.findall("long_desc"):
105 if body is None:
106 body = longdesc.findtext("thetext")
107 else:
108 who = longdesc.find("who")
109 comment = {
110 "id": longdesc.findtext("commentid"),
111 "author_email": who.text,
112 "published": date_utils.date_parse(longdesc.findtext("bug_when")),
113 "author": who.get("name", who.text),
114 "content": longdesc.findtext("thetext"),
115 }
116 comments.append(comment)
117
118 ticket["body"] = body
119 ticket["comments"] = comments
120 tickets.append(ticket)
121
122 tickets.sort(key=lambda t: int(t["id"]))
123 return (tickets, len(tickets))
124
125
126 class BugzillaImport(object):
127 def __init__(self, host):
128 log.info(_("Bugilla import plugin initialization"))
129 self.host = host
130 host.plugins["TICKETS_IMPORT"].register(
131 "bugzilla", self.import_, SHORT_DESC, LONG_DESC
132 )
133
134 def import_(self, client, location, options=None):
135 if not os.path.isabs(location):
136 raise exceptions.DataError(
137 "An absolute path to XML data need to be given as location"
138 )
139 bugzilla_parser = BugzillaParser()
140 # d = threads.deferToThread(bugzilla_parser.parse, location)
141 d = defer.maybeDeferred(bugzilla_parser.parse, location)
142 return d