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 <[email protected]> * Madhumitha Viswanathan <[email protected]> * Fabio Prina <[email protected]> +* Alayn Gortazar <[email protected]> === 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
_______________________________________________ Mailing list: https://launchpad.net/~gtg Post to : [email protected] Unsubscribe : https://launchpad.net/~gtg More help : https://help.launchpad.net/ListHelp

