comparison src/plugins/plugin_tickets_import.py @ 2396:66baa687c682

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
author Goffi <goffi@goffi.org>
date Fri, 27 Oct 2017 17:58:05 +0200
parents 8ed4ac10cb5e
children 8b37a62336c3
comparison
equal deleted inserted replaced
2395:713cedc99752 2396:66baa687c682
22 from sat.core import exceptions 22 from sat.core import exceptions
23 from sat.core.log import getLogger 23 from sat.core.log import getLogger
24 log = getLogger(__name__) 24 log = getLogger(__name__)
25 from twisted.internet import defer 25 from twisted.internet import defer
26 from sat.tools.common import uri 26 from sat.tools.common import uri
27 from sat.tools import utils
27 28
28 29
29 PLUGIN_INFO = { 30 PLUGIN_INFO = {
30 C.PI_NAME: "tickets import", 31 C.PI_NAME: "tickets import",
31 C.PI_IMPORT_NAME: "TICKETS_IMPORT", 32 C.PI_IMPORT_NAME: "TICKETS_IMPORT",
35 C.PI_HANDLER: "no", 36 C.PI_HANDLER: "no",
36 C.PI_DESCRIPTION: _(u"""Tickets import management: 37 C.PI_DESCRIPTION: _(u"""Tickets import management:
37 This plugin manage the different tickets importers which can register to it, and handle generic importing tasks.""") 38 This plugin manage the different tickets importers which can register to it, and handle generic importing tasks.""")
38 } 39 }
39 40
41 OPT_MAPPING = 'mapping'
42 FIELDS_LIST = (u'labels', u'cc_emails') # fields which must have a list as value
43 FIELDS_DATE = (u'created', u'updated')
44
40 NS_TICKETS = 'org.salut-a-toi.tickets:0' 45 NS_TICKETS = 'org.salut-a-toi.tickets:0'
41 46
42 47
43 class TicketsImportPlugin(object): 48 class TicketsImportPlugin(object):
44 BOOL_OPTIONS = () 49 BOOL_OPTIONS = ()
50 JSON_OPTIONS = (OPT_MAPPING,)
45 OPT_DEFAULTS = {} 51 OPT_DEFAULTS = {}
46 52
47 def __init__(self, host): 53 def __init__(self, host):
48 log.info(_("plugin Tickets Import initialization")) 54 log.info(_("plugin Tickets Import initialization"))
49 self.host = host 55 self.host = host
57 def importItem(self, client, item_import_data, session, options, return_data, service, node): 63 def importItem(self, client, item_import_data, session, options, return_data, service, node):
58 """ 64 """
59 65
60 @param item_import_data(dict): no key is mandatory, but if a key doesn't exists in dest form, it will be ignored. 66 @param item_import_data(dict): no key is mandatory, but if a key doesn't exists in dest form, it will be ignored.
61 Following names are recommendations which should be used where suitable in importers. 67 Following names are recommendations which should be used where suitable in importers.
68 except if specified in description, values are unicode
62 'id': unique id (must be unique in the node) of the ticket 69 'id': unique id (must be unique in the node) of the ticket
63 'title': title (or short description/summary) of the ticket 70 'title': title (or short description/summary) of the ticket
64 'body': main description of the ticket 71 'body': main description of the ticket
65 'creation': date of creation 72 'created': date of creation (unix time)
66 'update': date of last update 73 'updated': date of last update (unix time)
67 'reporter': full name of reporter 74 'reporter': full name of reporter
68 'reporter_jid': jid of reporter 75 'reporter_jid': jid of reporter
69 'reporter_email': email of reporter 76 'reporter_email': email of reporter
70 'assigned_to_name': full name of person working on it 77 'assigned_to_name': full name of person working on it
71 'assigned_to_email': email of person working on it 78 'assigned_to_email': email of person working on it
72 'cc_emails': iterable of emails subscribed to the ticket 79 'cc_emails': list of emails subscribed to the ticket
73 'priority': priority of the ticket 80 'priority': priority of the ticket
74 'severity': severity of the ticket 81 'severity': severity of the ticket
82 'labels': list of unicode values to use as label
75 'product': product concerned by this ticket 83 'product': product concerned by this ticket
76 'component': part of the product concerned by this ticket 84 'component': part of the product concerned by this ticket
77 'version': version of the product/component concerned by this ticket 85 'version': version of the product/component concerned by this ticket
78 'platform': platform converned by this ticket 86 'platform': platform converned by this ticket
79 'os': operating system concerned by this ticket 87 'os': operating system concerned by this ticket
82 - "started": progress is ongoing 90 - "started": progress is ongoing
83 - "review": ticket is fixed and waiting for review 91 - "review": ticket is fixed and waiting for review
84 - "closed": ticket is finished or invalid 92 - "closed": ticket is finished or invalid
85 'milestone': target milestone for this ticket 93 'milestone': target milestone for this ticket
86 'comments': list of microblog data (comment metadata, check [XEP_0277.send] data argument) 94 'comments': list of microblog data (comment metadata, check [XEP_0277.send] data argument)
95 @param options(dict, None): Below are the generic options,
96 tickets importer can have specific ones. All options are serialized unicode values
97 generic options:
98 - OPT_MAPPING (json): dict of imported ticket key => exported ticket key
99 e.g.: if you want to map "component" to "labels", you can specify:
100 {'component': 'labels'}
101 If you specify several import ticket key to the same dest key,
102 the values will be joined with line feeds
87 """ 103 """
88 if 'comments_uri' in item_import_data: 104 if 'comments_uri' in item_import_data:
89 raise exceptions.DataError(_(u'comments_uri key will be generated and must not be used by importer')) 105 raise exceptions.DataError(_(u'comments_uri key will be generated and must not be used by importer'))
106 for key in FIELDS_LIST:
107 if not isinstance(item_import_data.get(key, []), list):
108 raise exceptions.DataError(_(u'{key} must be a list').format(key=key))
109 for key in FIELDS_DATE:
110 try:
111 item_import_data[key] = utils.xmpp_date(item_import_data[key])
112 except KeyError:
113 continue
90 if session[u'root_node'] is None: 114 if session[u'root_node'] is None:
91 session[u'root_node'] = NS_TICKETS 115 session[u'root_node'] = NS_TICKETS
92 if not 'schema' in session: 116 if not 'schema' in session:
93 session['schema'] = yield self._s.getSchemaForm(client, service, node or session[u'root_node']) 117 session['schema'] = yield self._s.getSchemaForm(client, service, node or session[u'root_node'])
94 defer.returnValue(item_import_data) 118 defer.returnValue(item_import_data)
121 id_ = ticket_data.pop('id', None) 145 id_ = ticket_data.pop('id', None)
122 log.debug(u"uploading item [{id}]: {title}".format(id=id_, title=ticket_data.get('title',''))) 146 log.debug(u"uploading item [{id}]: {title}".format(id=id_, title=ticket_data.get('title','')))
123 return self._s.sendDataFormItem(client, service, node, ticket_data, session['schema'], id_) 147 return self._s.sendDataFormItem(client, service, node, ticket_data, session['schema'], id_)
124 148
125 def itemFilters(self, client, ticket_data, session, options): 149 def itemFilters(self, client, ticket_data, session, options):
126 return None 150 mapping = options.get(OPT_MAPPING)
151 if mapping is not None:
152 if not isinstance(mapping, dict):
153 raise exceptions.DataError(_(u'mapping option must be a dictionary'))
154
155 for source, dest in mapping.iteritems():
156 if not isinstance(source, unicode) or not isinstance(dest, unicode):
157 raise exceptions.DataError(_(u'keys and values of mapping must be sources and destinations ticket fields'))
158 if source in ticket_data:
159 value = ticket_data.pop(source)
160 if dest in FIELDS_LIST:
161 values = ticket_data[dest] = ticket_data.get(dest, [])
162 values.append(value)
163 else:
164 if dest in ticket_data:
165 ticket_data[dest] = ticket_data[dest] + u'\n' + value
166 else:
167 ticket_data[dest] = value