comparison libervia/backend/plugins/plugin_tickets_import.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.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 ticketss
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 _
21 from libervia.backend.core.constants import Const as C
22 from libervia.backend.core import exceptions
23 from libervia.backend.core.log import getLogger
24
25 log = getLogger(__name__)
26 from twisted.internet import defer
27 from libervia.backend.tools.common import uri
28 from libervia.backend.tools import utils
29
30
31 PLUGIN_INFO = {
32 C.PI_NAME: "tickets import",
33 C.PI_IMPORT_NAME: "TICKETS_IMPORT",
34 C.PI_TYPE: C.PLUG_TYPE_IMPORT,
35 C.PI_DEPENDENCIES: ["IMPORT", "XEP-0060", "XEP-0277", "XEP-0346"],
36 C.PI_MAIN: "TicketsImportPlugin",
37 C.PI_HANDLER: "no",
38 C.PI_DESCRIPTION: _(
39 """Tickets import management:
40 This plugin manage the different tickets importers which can register to it, and handle generic importing tasks."""
41 ),
42 }
43
44 OPT_MAPPING = "mapping"
45 FIELDS_LIST = ("labels", "cc_emails") # fields which must have a list as value
46 FIELDS_DATE = ("created", "updated")
47
48 NS_TICKETS = "fdp/submitted/org.salut-a-toi.tickets:0"
49
50
51 class TicketsImportPlugin(object):
52 BOOL_OPTIONS = ()
53 JSON_OPTIONS = (OPT_MAPPING,)
54 OPT_DEFAULTS = {}
55
56 def __init__(self, host):
57 log.info(_("plugin Tickets import initialization"))
58 self.host = host
59 self._importers = {}
60 self._p = host.plugins["XEP-0060"]
61 self._m = host.plugins["XEP-0277"]
62 self._s = host.plugins["XEP-0346"]
63 host.plugins["IMPORT"].initialize(self, "tickets")
64
65 @defer.inlineCallbacks
66 def import_item(
67 self, client, item_import_data, session, options, return_data, service, node
68 ):
69 """
70
71 @param item_import_data(dict): no key is mandatory, but if a key doesn't exists in dest form, it will be ignored.
72 Following names are recommendations which should be used where suitable in importers.
73 except if specified in description, values are unicode
74 'id': unique id (must be unique in the node) of the ticket
75 'title': title (or short description/summary) of the ticket
76 'body': main description of the ticket
77 'created': date of creation (unix time)
78 'updated': date of last update (unix time)
79 'author': full name of reporter
80 'author_jid': jid of reporter
81 'author_email': email of reporter
82 'assigned_to_name': full name of person working on it
83 'assigned_to_email': email of person working on it
84 'cc_emails': list of emails subscribed to the ticket
85 'priority': priority of the ticket
86 'severity': severity of the ticket
87 'labels': list of unicode values to use as label
88 'product': product concerned by this ticket
89 'component': part of the product concerned by this ticket
90 'version': version of the product/component concerned by this ticket
91 'platform': platform converned by this ticket
92 'os': operating system concerned by this ticket
93 'status': current status of the ticket, values:
94 - "queued": ticket is waiting
95 - "started": progress is ongoing
96 - "review": ticket is fixed and waiting for review
97 - "closed": ticket is finished or invalid
98 'milestone': target milestone for this ticket
99 'comments': list of microblog data (comment metadata, check [XEP_0277.send] data argument)
100 @param options(dict, None): Below are the generic options,
101 tickets importer can have specific ones. All options are serialized unicode values
102 generic options:
103 - OPT_MAPPING (json): dict of imported ticket key => exported ticket key
104 e.g.: if you want to map "component" to "labels", you can specify:
105 {'component': 'labels'}
106 If you specify several import ticket key to the same dest key,
107 the values will be joined with line feeds
108 """
109 if "comments_uri" in item_import_data:
110 raise exceptions.DataError(
111 _("comments_uri key will be generated and must not be used by importer")
112 )
113 for key in FIELDS_LIST:
114 if not isinstance(item_import_data.get(key, []), list):
115 raise exceptions.DataError(_("{key} must be a list").format(key=key))
116 for key in FIELDS_DATE:
117 try:
118 item_import_data[key] = utils.xmpp_date(item_import_data[key])
119 except KeyError:
120 continue
121 if session["root_node"] is None:
122 session["root_node"] = NS_TICKETS
123 if not "schema" in session:
124 session["schema"] = yield self._s.get_schema_form(
125 client, service, node or session["root_node"]
126 )
127 defer.returnValue(item_import_data)
128
129 @defer.inlineCallbacks
130 def import_sub_items(self, client, item_import_data, ticket_data, session, options):
131 # TODO: force "open" permission (except if private, check below)
132 # TODO: handle "private" metadata, to have non public access for node
133 # TODO: node access/publish model should be customisable
134 comments = ticket_data.get("comments", [])
135 service = yield self._m.get_comments_service(client)
136 node = self._m.get_comments_node(session["root_node"] + "_" + ticket_data["id"])
137 node_options = {
138 self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN,
139 self._p.OPT_PERSIST_ITEMS: 1,
140 self._p.OPT_DELIVER_PAYLOADS: 1,
141 self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
142 self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
143 }
144 yield self._p.create_if_new_node(client, service, node, options=node_options)
145 ticket_data["comments_uri"] = uri.build_xmpp_uri(
146 "pubsub", subtype="microblog", path=service.full(), node=node
147 )
148 for comment in comments:
149 if "updated" not in comment and "published" in comment:
150 # we don't want an automatic update date
151 comment["updated"] = comment["published"]
152 yield self._m.send(client, comment, service, node)
153
154 def publish_item(self, client, ticket_data, service, node, session):
155 if node is None:
156 node = NS_TICKETS
157 id_ = ticket_data.pop("id", None)
158 log.debug(
159 "uploading item [{id}]: {title}".format(
160 id=id_, title=ticket_data.get("title", "")
161 )
162 )
163 return defer.ensureDeferred(
164 self._s.send_data_form_item(
165 client, service, node, ticket_data, session["schema"], id_
166 )
167 )
168
169 def item_filters(self, client, ticket_data, session, options):
170 mapping = options.get(OPT_MAPPING)
171 if mapping is not None:
172 if not isinstance(mapping, dict):
173 raise exceptions.DataError(_("mapping option must be a dictionary"))
174
175 for source, dest in mapping.items():
176 if not isinstance(source, str) or not isinstance(dest, str):
177 raise exceptions.DataError(
178 _(
179 "keys and values of mapping must be sources and destinations ticket fields"
180 )
181 )
182 if source in ticket_data:
183 value = ticket_data.pop(source)
184 if dest in FIELDS_LIST:
185 values = ticket_data[dest] = ticket_data.get(dest, [])
186 values.append(value)
187 else:
188 if dest in ticket_data:
189 ticket_data[dest] = ticket_data[dest] + "\n" + value
190 else:
191 ticket_data[dest] = value