gtg team mailing list archive
-
gtg team
-
Mailing list archive
-
Message #03395
[Merge] lp:~arraintxo/gtg/mantis-backend into lp:gtg
Alayn Gortazar has proposed merging lp:~arraintxo/gtg/mantis-backend into lp:gtg.
Requested reviews:
Gtg developers (gtg)
For more details, see:
https://code.launchpad.net/~arraintxo/gtg/mantis-backend/+merge/71788
Added a readonly backend for Mantis Bug Tracker, based on the previously existing Launchpad's backend
--
https://code.launchpad.net/~arraintxo/gtg/mantis-backend/+merge/71788
Your team Gtg developers is requested to review the proposed merge of lp:~arraintxo/gtg/mantis-backend into lp:gtg.
=== modified file 'AUTHORS'
--- AUTHORS 2011-08-12 14:11:35 +0000
+++ AUTHORS 2011-08-16 23:06:29 +0000
@@ -76,3 +76,4 @@
* Ivan Evtukhovich <evtuhovich@xxxxxxxxx>
* Madhumitha Viswanathan <madhuvishy@xxxxxxxxx>
* Fabio Prina <fabio@xxxxxxxxx>
+* Alayn Gortazar <zutoin@xxxxxxxxx>
=== modified file 'CHANGELOG'
--- CHANGELOG 2011-08-12 14:11:35 +0000
+++ CHANGELOG 2011-08-16 23:06:29 +0000
@@ -1,4 +1,5 @@
????-??-?? Getting Things GNOME! ?.?.?
+ * Added Mantis Bug Tracker backend, by Alayn Gortazar
* Fixed crash traceback when pressing 'delete' key, by Jeff Oliver
* Added link to web documentation in Help menu, by Ronan Jouchet
* Fixed bug with data consistency #579189, by Marko Kevac
=== added file 'GTG/backends/backend_mantis.py'
--- GTG/backends/backend_mantis.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/backend_mantis.py 2011-08-16 23:06:29 +0000
@@ -0,0 +1,246 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import os
+import uuid
+
+from GTG import _
+from GTG.backends.genericbackend import GenericBackend
+from GTG.backends.backendsignals import BackendSignals
+from GTG.backends.periodicimportbackend import PeriodicImportBackend
+from GTG.backends.syncengine import SyncEngine, SyncMeme
+from GTG.tools.logger import Log
+
+from GTG.core.task import Task
+from suds.client import Client
+
+'''
+Backend for importing mantis issues in GTG
+'''
+class Backend(PeriodicImportBackend):
+ _general_description = { \
+ GenericBackend.BACKEND_NAME: "backend_mantis", \
+ GenericBackend.BACKEND_HUMAN_NAME: _("MantisBT"), \
+ GenericBackend.BACKEND_AUTHORS: ["Luca Invernizzi", "Alayn Gortazar"], \
+ GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READONLY, \
+ GenericBackend.BACKEND_DESCRIPTION: \
+ _("This backend lets you import the issues found on mantis"
+ " using a prestablished filter called 'gtg'."
+ " As the issue state changes in Mantis, the GTG task is "
+ " updated.\n"
+ "Please note that this is a read only backend, which "
+ "means that if you open one of the imported tasks and "
+ " change one of the:\n"
+ " - title\n"
+ " - description\n"
+ " - tags\n"
+ "Your changes <b>will</b> be reverted when the associated"
+ " issue is modified. Apart from those, you are free to set "
+ " any other field (start/due dates, subtasks...): your "
+ " changes will be preserved. This is useful to add "
+ " personal annotations to issue"), \
+ }
+
+
+ _static_parameters = {\
+ "period": { \
+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_INT, \
+ GenericBackend.PARAM_DEFAULT_VALUE: 5, }, \
+ "username": { \
+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_STRING, \
+ GenericBackend.PARAM_DEFAULT_VALUE: 'insert your username', }, \
+ "password": { \
+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_PASSWORD, \
+ GenericBackend.PARAM_DEFAULT_VALUE: '', }, \
+ "service-url": { \
+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_STRING, \
+ GenericBackend.PARAM_DEFAULT_VALUE: 'http://example.com/mantis', }, \
+ "tag-with-project-name": { \
+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_BOOL, \
+ GenericBackend.PARAM_DEFAULT_VALUE: True}, \
+ }
+
+ def __init__(self, parameters):
+ '''
+ See GenericBackend for an explanation of this function.
+ Re-loads the saved state of the synchronization
+ '''
+ super(Backend, self).__init__(parameters)
+ #loading the saved state of the synchronization, if any
+ self.data_path = os.path.join('backends/mantis/', \
+ "sync_engine-" + self.get_id())
+ self.sync_engine = self._load_pickled_file(self.data_path, \
+ SyncEngine())
+ def save_state(self):
+ '''Saves the state of the synchronization'''
+ self._store_pickled_file(self.data_path, self.sync_engine)
+
+ def do_periodic_import(self):
+ #Establishing connection
+ try:
+ self.cancellation_point()
+ client = Client('%s/api/soap/mantisconnect.php?wsdl' % (self._parameters['service-url']))
+ except KeyError:
+ self.quit(disable = True)
+ BackendSignals().backend_failed(self.get_id(), \
+ BackendSignals.ERRNO_AUTHENTICATION)
+ return
+
+
+ projects = client.service.mc_projects_get_user_accessible(self._parameters['username'], self._parameters['password'])
+ filters = client.service.mc_filter_get(self._parameters['username'], self._parameters['password'], 0)
+
+ #Fetching the issues
+ self.cancellation_point()
+ my_issues = []
+ for filt in filters:
+ if filt['name'] == 'gtg':
+ for project in projects:
+ my_issues = client.service.mc_filter_get_issues(self._parameters['username'], self._parameters['password'], project['id'], filt['id'], 0, 100)
+ for issue in my_issues:
+ self.cancellation_point()
+ self._process_mantis_issue(issue)
+ last_issue_list = self.sync_engine.get_all_remote()
+ new_issue_list = [str(issue['id']) for issue in my_issues]
+ for issue_link in set(last_issue_list).difference(set(new_issue_list)):
+ self.cancellation_point()
+ #we make sure that the other backends are not modifying the task
+ # set
+ with self.datastore.get_backend_mutex():
+ tid = self.sync_engine.get_local_id(issue_link)
+ self.datastore.request_task_deletion(tid)
+ try:
+ self.sync_engine.break_relationship(remote_id = issue_link)
+ except KeyError:
+ pass
+ return
+
+###############################################################################
+### Process tasks #############################################################
+###############################################################################
+
+ def _process_mantis_issue(self, issue):
+ '''
+ Given a issue object, finds out if it must be synced to a GTG note and,
+ if so, it carries out the synchronization (by creating or
+ updating a GTG task, or deleting itself if the related task has
+ been deleted)
+
+ @param note: a mantis issue
+ '''
+ action, tid = self.sync_engine.analyze_remote_id(str(issue['id']), \
+ self.datastore.has_task, lambda b: True)
+ Log.debug("processing mantis (%s)" % (action))
+
+ if action == None:
+ return
+
+ issue_dic = self._prefetch_issue_data(issue)
+ #for the rest of the function, no access to issue must be made, so
+ # that the time of blocking inside the with statements is short.
+ #To be sure of that, set issue to None
+ issue = None
+
+ with self.datastore.get_backend_mutex():
+ if action == SyncEngine.ADD:
+ tid = str(uuid.uuid4())
+ task = self.datastore.task_factory(tid)
+ self._populate_task(task, issue_dic)
+ self.sync_engine.record_relationship(local_id = tid,\
+ remote_id = str(issue_dic['number']), \
+ meme = SyncMeme(\
+ task.get_modified(), \
+ issue_dic['modified'], \
+ self.get_id()))
+ self.datastore.push_task(task)
+
+ elif action == SyncEngine.UPDATE:
+ task = self.datastore.get_task(tid)
+ self._populate_task(task, issue_dic)
+ meme = self.sync_engine.get_meme_from_remote_id( \
+ issue_dic['number'])
+ meme.set_local_last_modified(task.get_modified())
+ meme.set_remote_last_modified(issue_dic['modified'])
+ self.save_state()
+
+ def _prefetch_issue_data(self, mantis_issue):
+ '''
+ We fetch all the necessary info that we need from the mantis_issue to populate a
+ task beforehand (these will be used in _populate_task).
+
+ @param mantis_issue: a mantis issue
+ @returns dict: a dictionary containing the relevant issue attributes
+ '''
+ issue_dic = {'title': mantis_issue['summary'],
+ 'text': mantis_issue['description'],
+ 'reporter': mantis_issue['reporter'].name,
+ 'modified': mantis_issue['last_updated'],
+ 'project': mantis_issue['project'].name,
+ 'status': mantis_issue['status'].name,
+ 'completed': (mantis_issue['status'].id >= 80),
+ 'number': str(mantis_issue['id'])}
+
+ try:
+ issue_dic['assigned'] = mantis_issue['handler'].name == self._parameters['username']
+ except AttributeError:
+ issue_dic['assigned'] = False
+
+ return issue_dic
+
+
+ def _populate_task(self, task, issue_dic):
+ '''
+ Fills a GTG task with the data from a mantis issue.
+
+ @param task: a Task
+ @param issue_dic: a mantis issue
+
+ '''
+ #set task status
+ if issue_dic["completed"]:
+ task.set_status(Task.STA_DONE)
+ else:
+ task.set_status(Task.STA_ACTIVE)
+ if task.get_title() != issue_dic['title']:
+ task.set_title(_("Iss.") + " %s: " % issue_dic["number"]
+ + issue_dic['title'])
+ text = self._build_issue_text(issue_dic)
+ if task.get_excerpt() != text:
+ task.set_text(text)
+
+ new_tags = set([])
+ if self._parameters["tag-with-project-name"]:
+ new_tags = set(['@' + issue_dic['project']])
+ current_tags = set(task.get_tags_name())
+ #add the new ones
+ for tag in new_tags.difference(current_tags):
+ task.add_tag(tag)
+
+ task.add_remote_id(self.get_id(), issue_dic['number'])
+
+ def _build_issue_text(self, issue_dic):
+ '''
+ Creates the text that describes a issue
+ '''
+ text = _("Reported by: ") + issue_dic["reporter"] + '\n'
+ text += _("Link to issue: " ) + \
+ self._parameters['service-url'] + '/view.php?id=%s' % \
+ (issue_dic["number"]) + '\n'
+ text += '\n' + issue_dic["text"]
+ return text
=== modified file 'GTG/gtk/backends_dialog/parameters_ui/__init__.py'
--- GTG/gtk/backends_dialog/parameters_ui/__init__.py 2011-01-03 02:57:55 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/__init__.py 2011-08-16 23:06:29 +0000
@@ -78,6 +78,10 @@
), \
("password" , self.UI_generator(PasswordUI)), \
("period" , self.UI_generator(PeriodUI)), \
+ ("service-url", self.UI_generator(TextUI, \
+ {"description": _("Service URL"), \
+ "parameter_name": "service-url"}) \
+ ),\
("import-from-replies", self.UI_generator(CheckBoxUI, \
{"text": _("Import tasks from @ replies " + \
"directed to you"), \
@@ -100,7 +104,7 @@
"targeted by the bug"), \
"parameter": "tag-with-project-name"}) \
),\
- )
+ )
def UI_generator(self, param_type, special_arguments = {}):
'''A helper function to build a widget type from a template.
It passes to the created widget generator a series of common parameters,
=== added file 'data/icons/hicolor/scalable/apps/backend_mantis.png'
Binary files data/icons/hicolor/scalable/apps/backend_mantis.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_mantis.png 2011-08-16 23:06:29 +0000 differ
Follow ups