changeset 2390:f57a8eaec8ed

plugins import, tickets import, bugzilla import: comments handling: - comments are put in "comments" key in import data, it's a list of microblog data - in resulting item, the comments uri is put in comments_uri field - (plugin import) root_service and root_node are kept in session - (plugin ticket) reporter name is now in "reporter" key instead of "reporter_name" - (plugin bugizlla): if not reporter name is found, first part of email address is used
author Goffi <goffi@goffi.org>
date Fri, 20 Oct 2017 08:48:41 +0200
parents 5675af905725
children 07e1543d6992
files src/plugins/plugin_import.py src/plugins/plugin_tickets_import.py src/plugins/plugin_tickets_import_bugzilla.py src/plugins/plugin_xep_0277.py
diffstat 4 files changed, 50 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/src/plugins/plugin_import.py	Fri Oct 20 08:44:09 2017 +0200
+++ b/src/plugins/plugin_import.py	Fri Oct 20 08:48:41 2017 +0200
@@ -53,8 +53,10 @@
         @param import_handler(object): specialized import handler instance
             must have the following methods:
                 - importItem: import a single main item (i.e. prepare data for publishing)
-                - importSubitems: import sub items (i.e. items linked to main item, e.g. comments). Must return a dict with kwargs for recursiveImport if items are to be imported. At east "items_import_data", "service" and "node" keys must be provided.
-                    if None is returned, no subitems will be imported
+                - importSubitems: import sub items (i.e. items linked to main item, e.g. comments).
+                    Must return a dict with kwargs for recursiveImport if items are to be imported recursively.
+                    At least "items_import_data", "service" and "node" keys must be provided.
+                    if None is returned, no recursion will be done to import subitems, but import can still be done directly by the method.
                 - publishItem: actualy publish an item
                 - itemFilters: modify item according to options
         @param name(unicode): import handler name
@@ -139,6 +141,7 @@
                 else:
                     if not value:
                         del options[opt_name]
+
         try:
             importer = import_handler.importers[name]
         except KeyError:
@@ -159,7 +162,10 @@
                    }
         self.host.registerProgressCb(progress_id, partial(self.getProgress, import_handler), metadata, profile=client.profile)
         self.host.bridge.progressStarted(progress_id, metadata, client.profile)
-        session = {}  # session data, can be use by importers
+        session = {  # session data, can be used by importers
+            u'root_service': pubsub_service,
+            u'root_node': pubsub_node
+        }
         self.recursiveImport(client, import_handler, items_import_data, progress_id, session, options, None, pubsub_service, pubsub_node)
         defer.returnValue(progress_id)
 
@@ -172,6 +178,7 @@
         @param progress_id(unicode): id of progression
         @param session(dict): data for this import session
             can be used by importer so store any useful data
+            "root_service" and "root_node" are set to the main pubsub service and node of the import
         @param options(dict): import options
         @param return_data(dict): data to return on progressFinished
         @param service(jid.JID, None): PubSub service to use
--- a/src/plugins/plugin_tickets_import.py	Fri Oct 20 08:44:09 2017 +0200
+++ b/src/plugins/plugin_tickets_import.py	Fri Oct 20 08:48:41 2017 +0200
@@ -19,16 +19,18 @@
 
 from sat.core.i18n import _
 from sat.core.constants import Const as C
+from sat.core import exceptions
 from sat.core.log import getLogger
 log = getLogger(__name__)
 from twisted.internet import defer
+from sat.tools.common import uri
 
 
 PLUGIN_INFO = {
     C.PI_NAME: "tickets import",
     C.PI_IMPORT_NAME: "TICKETS_IMPORT",
     C.PI_TYPE: C.PLUG_TYPE_IMPORT,
-    C.PI_DEPENDENCIES: ["IMPORT", "XEP-0060", "PUBSUB_SCHEMA"],
+    C.PI_DEPENDENCIES: ["IMPORT", "XEP-0060", "XEP-0277", "PUBSUB_SCHEMA"],
     C.PI_MAIN: "TicketsImportPlugin",
     C.PI_HANDLER: "no",
     C.PI_DESCRIPTION: _(u"""Tickets import management:
@@ -47,6 +49,7 @@
         self.host = host
         self._importers = {}
         self._p = host.plugins['XEP-0060']
+        self._m = host.plugins['XEP-0277']
         self._s = host.plugins['PUBSUB_SCHEMA']
         host.plugins['IMPORT'].initialize(self, u'tickets')
 
@@ -61,7 +64,8 @@
             'body': main description of the ticket
             'creation': date of creation
             'update': date of last update
-            'reporter_name': full name of reporter
+            'reporter': full name of reporter
+            'reporter_jid': jid of reporter
             'reporter_email': email of reporter
             'assigned_to_name': full name of person working on it
             'assigned_to_email': email of person working on it
@@ -79,13 +83,29 @@
                 - "review": ticket is fixed and waiting for review
                 - "closed": ticket is finished or invalid
             'milestone': target milestone for this ticket
+            'comments': list of microblog data (comment metadata, check [XEP_0277.send] data argument)
         """
+        if 'comments_uri' in item_import_data:
+            raise exceptions.DataError(_(u'comments_uri key will be generated and must not be used by importer'))
+        if session[u'root_node'] is None:
+            session[u'root_node'] = NS_TICKETS
         if not 'schema' in session:
-            session['schema'] = yield self._s.getSchemaForm(client, service, node or NS_TICKETS)
+            session['schema'] = yield self._s.getSchemaForm(client, service, node or session[u'root_node'])
         defer.returnValue(item_import_data)
 
+    @defer.inlineCallbacks
     def importSubItems(self, client, item_import_data, ticket_data, session, options):
-        return None
+        # TODO: force "open" permission (except if private, check below)
+        # TODO: handle "private" metadata, to have non public access for node
+        comments = ticket_data.get('comments', [])
+        service, node = self._m.getCommentsService(client), self._m.getCommentsNode(session['root_node'] + u'_' + ticket_data['id'])
+        yield self._p.createIfNewNode(client, service, node)
+        ticket_data['comments_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', path=service.full(), node=node)
+        for comment in comments:
+            if 'updated' not in comment and 'published' in comment:
+                # we don't want an automatic update date
+                comment['updated'] = comment['published']
+            yield self._m.send(client, comment, service, node)
 
     def publishItem(self, client, ticket_data, service, node, session):
         if node is None:
--- a/src/plugins/plugin_tickets_import_bugzilla.py	Fri Oct 20 08:44:09 2017 +0200
+++ b/src/plugins/plugin_tickets_import_bugzilla.py	Fri Oct 20 08:48:41 2017 +0200
@@ -26,6 +26,7 @@
 from twisted.internet import defer
 import os.path
 from lxml import etree
+from sat.tools import utils
 
 
 PLUGIN_INFO = {
@@ -71,7 +72,12 @@
             ticket['update'] = bug.findtext('delta_ts')
             ticket['title'] = bug.findtext('short_desc')
             reporter_elt = bug.find('reporter')
-            ticket['reporter_name'] = reporter_elt.get('name')
+            ticket['reporter'] = reporter_elt.get('name')
+            if ticket['reporter'] is None:
+                if '@' in reporter_elt.text:
+                    ticket['reporter'] = reporter_elt.text[:reporter_elt.text.find('@')].title()
+                else:
+                    ticket['reporter'] = u'no name'
             ticket['reporter_email'] = reporter_elt.text
             assigned_to_elt = bug.find('assigned_to')
             ticket['assigned_to_name'] = assigned_to_elt.get('name')
@@ -95,10 +101,11 @@
                     body = longdesc.findtext('thetext')
                 else:
                     who = longdesc.find('who')
-                    comment = {'from': who.text,
-                               'date': longdesc.findtext('bug_when'),
-                               'nick': who.get('name'),
-                               'body': longdesc.findtext('thetext')}
+                    comment = {'id': longdesc.findtext('commentid'),
+                               'author_email': who.text,
+                               'published': utils.date_parse(longdesc.findtext('bug_when')),
+                               'author': who.get('name', who.text),
+                               'content': longdesc.findtext('thetext')}
                     comments.append(comment)
 
             ticket['body'] = body
--- a/src/plugins/plugin_xep_0277.py	Fri Oct 20 08:44:09 2017 +0200
+++ b/src/plugins/plugin_xep_0277.py	Fri Oct 20 08:48:41 2017 +0200
@@ -662,12 +662,9 @@
         @param access: Node access model, according to xep-0060 #4.5
         @param profile_key: profile key
         """
+        # FIXME: check if this mehtod is needed, deprecate it if not
         client = self.host.getClient(profile_key)
 
-        _jid, xmlstream = self.host.getJidNStream(profile_key)
-        if not _jid:
-            log.error(_(u"Can't find profile's jid"))
-            return
         _options = {self._p.OPT_ACCESS_MODEL: access, self._p.OPT_PERSIST_ITEMS: 1, self._p.OPT_MAX_ITEMS: -1, self._p.OPT_DELIVER_PAYLOADS: 1, self._p.OPT_SEND_ITEM_SUBSCRIBE: 1}
 
         def cb(result):
@@ -683,17 +680,17 @@
             #If the node already exists, the condition is "conflict",
             #else we have an unmanaged error
             if s_error.value.condition == 'conflict':
-                #d = self.host.plugins["XEP-0060"].deleteNode(client, _jid.userhostJID(), NS_MICROBLOG)
+                #d = self.host.plugins["XEP-0060"].deleteNode(client, client.jid.userhostJID(), NS_MICROBLOG)
                 #d.addCallback(lambda x: create_node().addCallback(cb).addErrback(fatal_err))
                 change_node_options().addCallback(cb).addErrback(fatal_err)
             else:
                 fatal_err(s_error)
 
         def create_node():
-            return self._p.createNode(client, _jid.userhostJID(), NS_MICROBLOG, _options)
+            return self._p.createNode(client, client.jid.userhostJID(), NS_MICROBLOG, _options)
 
         def change_node_options():
-            return self._p.setOptions(_jid.userhostJID(), NS_MICROBLOG, _jid.userhostJID(), _options, profile_key=profile_key)
+            return self._p.setOptions(client.jid.userhostJID(), NS_MICROBLOG, client.jid.userhostJID(), _options, profile_key=profile_key)
 
         create_node().addCallback(cb).addErrback(err_cb)