# HG changeset patch # User Goffi # Date 1509119885 -7200 # Node ID 66baa687c682db8db911d65c57a370eb32ce8cf0 # Parent 713cedc9975287d164ff926a4f21e475bc2b864b plugins tickets import, jp (ticket/import): implemented mapping: - mapping is an option allowing do map imported field to a specific field in the newly created item - importers can now use complexe data types in options using JSON diff -r 713cedc99752 -r 66baa687c682 frontends/src/jp/cmd_ticket.py --- a/frontends/src/jp/cmd_ticket.py Fri Oct 27 17:54:00 2017 +0200 +++ b/frontends/src/jp/cmd_ticket.py Fri Oct 27 17:58:05 2017 +0200 @@ -24,7 +24,7 @@ __commands__ = ["Ticket"] -FIELDS_MAP = u'fields_map' +FIELDS_MAP = u'mapping' class Import(base.CommandAnswering): diff -r 713cedc99752 -r 66baa687c682 src/plugins/plugin_blog_import.py --- a/src/plugins/plugin_blog_import.py Fri Oct 27 17:54:00 2017 +0200 +++ b/src/plugins/plugin_blog_import.py Fri Oct 27 17:58:05 2017 +0200 @@ -54,6 +54,7 @@ class BlogImportPlugin(object): BOOL_OPTIONS = (OPT_UPLOAD_IMAGES, OPT_IGNORE_TLS) + JSON_OPTIONS = () OPT_DEFAULTS = {OPT_UPLOAD_IMAGES: True, OPT_IGNORE_TLS: False} diff -r 713cedc99752 -r 66baa687c682 src/plugins/plugin_import.py --- a/src/plugins/plugin_import.py Fri Oct 27 17:54:00 2017 +0200 +++ b/src/plugins/plugin_import.py Fri Oct 27 17:58:05 2017 +0200 @@ -26,6 +26,7 @@ from functools import partial import collections import uuid +import json PLUGIN_INFO = { @@ -110,6 +111,11 @@ options[option] = C.bool(options[option]) except KeyError: pass + for option in import_handler.JSON_OPTIONS: + try: + options[option] = json.loads(options[option]) + except ValueError: + raise exceptions.DataError(_(u'invalid json option: {name}').format(name=option)) return self.doImport(client, import_handler, unicode(name), unicode(location), options, pubsub_service or None, pubsub_node or None) @defer.inlineCallbacks diff -r 713cedc99752 -r 66baa687c682 src/plugins/plugin_tickets_import.py --- a/src/plugins/plugin_tickets_import.py Fri Oct 27 17:54:00 2017 +0200 +++ b/src/plugins/plugin_tickets_import.py Fri Oct 27 17:58:05 2017 +0200 @@ -24,6 +24,7 @@ log = getLogger(__name__) from twisted.internet import defer from sat.tools.common import uri +from sat.tools import utils PLUGIN_INFO = { @@ -37,11 +38,16 @@ This plugin manage the different tickets importers which can register to it, and handle generic importing tasks.""") } +OPT_MAPPING = 'mapping' +FIELDS_LIST = (u'labels', u'cc_emails') # fields which must have a list as value +FIELDS_DATE = (u'created', u'updated') + NS_TICKETS = 'org.salut-a-toi.tickets:0' class TicketsImportPlugin(object): BOOL_OPTIONS = () + JSON_OPTIONS = (OPT_MAPPING,) OPT_DEFAULTS = {} def __init__(self, host): @@ -59,19 +65,21 @@ @param item_import_data(dict): no key is mandatory, but if a key doesn't exists in dest form, it will be ignored. Following names are recommendations which should be used where suitable in importers. + except if specified in description, values are unicode 'id': unique id (must be unique in the node) of the ticket 'title': title (or short description/summary) of the ticket 'body': main description of the ticket - 'creation': date of creation - 'update': date of last update + 'created': date of creation (unix time) + 'updated': date of last update (unix time) '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 - 'cc_emails': iterable of emails subscribed to the ticket + 'cc_emails': list of emails subscribed to the ticket 'priority': priority of the ticket 'severity': severity of the ticket + 'labels': list of unicode values to use as label 'product': product concerned by this ticket 'component': part of the product concerned by this ticket 'version': version of the product/component concerned by this ticket @@ -84,9 +92,25 @@ - "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) + @param options(dict, None): Below are the generic options, + tickets importer can have specific ones. All options are serialized unicode values + generic options: + - OPT_MAPPING (json): dict of imported ticket key => exported ticket key + e.g.: if you want to map "component" to "labels", you can specify: + {'component': 'labels'} + If you specify several import ticket key to the same dest key, + the values will be joined with line feeds """ if 'comments_uri' in item_import_data: raise exceptions.DataError(_(u'comments_uri key will be generated and must not be used by importer')) + for key in FIELDS_LIST: + if not isinstance(item_import_data.get(key, []), list): + raise exceptions.DataError(_(u'{key} must be a list').format(key=key)) + for key in FIELDS_DATE: + try: + item_import_data[key] = utils.xmpp_date(item_import_data[key]) + except KeyError: + continue if session[u'root_node'] is None: session[u'root_node'] = NS_TICKETS if not 'schema' in session: @@ -123,4 +147,21 @@ return self._s.sendDataFormItem(client, service, node, ticket_data, session['schema'], id_) def itemFilters(self, client, ticket_data, session, options): - return None + mapping = options.get(OPT_MAPPING) + if mapping is not None: + if not isinstance(mapping, dict): + raise exceptions.DataError(_(u'mapping option must be a dictionary')) + + for source, dest in mapping.iteritems(): + if not isinstance(source, unicode) or not isinstance(dest, unicode): + raise exceptions.DataError(_(u'keys and values of mapping must be sources and destinations ticket fields')) + if source in ticket_data: + value = ticket_data.pop(source) + if dest in FIELDS_LIST: + values = ticket_data[dest] = ticket_data.get(dest, []) + values.append(value) + else: + if dest in ticket_data: + ticket_data[dest] = ticket_data[dest] + u'\n' + value + else: + ticket_data[dest] = value