The Roundup mail gateway automatically transforms incoming email messages into issues to be tracked. This is A Good Thing (TM). However, as the famous Johan Cruyff said: every upside has its downside. More specifically, you'll find that some people are reply-button-challenged. They will not use reply. They will compose new emails without In-Reply-To references, without issue id in the subject. They'll happily invent new subject lines for existing issues.
As a consequence, Roundup will be unable to recognize such mail for what it is, a followup to an existing issue. It will open a new issue instead. So now you've got two issue threads in Roundup for what is a single issue in real life.
The solution: a new Merge action that takes a 'source' issue, copies all messages and files from it to a 'target' issue, then retires the 'source' issue. So you'll have everything relevant in the consolidated 'target' issue. Meanwhile, the 'source' issue is retired and will not accept any followup changes. Neat.
Credits to Marlon van den Berg for prototyping this.
Change schema.py:
Here we go. First, create a new property 'merged' on 'IssueClass'.
issue = IssueClass(db, "issue", assignedto=Link("user"), topic=Multilink("keyword"), priority=Link("priority"), status=Link("status"), merged=Link("issue"))
We'll use this 'merged' property to store a reference to the 'target' issue on retiring the 'source' issue.
Create a merge action class:
Next, we need a 'MergeAction' class to perform the heavy lifting.
In 'interfaces.py':
from roundup.cgi.exceptions import Redirect from roundup.cgi import actions class MergeAction(actions.Action): def handle(self): source_issue = self.nodeid # find out if the form has a target issue edit field if self.form.has_key('target_issue'): # yes it does. Get the value. target_issue = self.form['target_issue'].value.strip() else: # nope target_issue = None if not target_issue or target_issue == '': self.client.error_message.append('Unknown target issue') return elif target_issue == source_issue: self.client.error_message.append('Cannot merge issue %s into myself (%s)' % (target_issue, source_issue)) return # get the message lists of the two issues source_messages = self.db.issue.get(source_issue, 'messages') target_messages = self.db.issue.get(target_issue, 'messages') # merge them for msg in source_messages: target_messages.append(msg) # update the target issue message list self.db.issue.set(target_issue, messages=target_messages) # get the file lists of the two issues source_files = self.db.issue.get(source_issue, 'files') target_files = self.db.issue.get(target_issue, 'files') # merge them for file in source_files: target_files.append(file) # update the target issue file list self.db.issue.set(target_issue, files=target_files) # uncomment this if you're using timelogs # # get the timelog lists of the two issues # source_timelog = self.db.issue.get(source_issue, 'timelog') # target_timelog = self.db.issue.get(target_issue, 'timelog') # # merge them # for time in source_timelog: # target_timelog.append(time) # # update the target issue time list # self.db.issue.set(target_issue, timelog=target_timelog) # store the 'merged-into' issue id self.db.issue.set(source_issue, merged=target_issue) # retire the source issue self.db.issue.retire(source_issue) # commit all database changes self.db.commit() # confirm the merge and redirect to the target issue raise Redirect, """%sissue%s?@ok_message=Merging of issue %s into issue %s successful""" % (self.base, target_issue, source_issue, target_issue)
Put a merge form in your issue template:
Finally, you'll need a way to plug all this logic into your webinterface. In your template file $TRACKER_HOME/html/issue.item.html you'll add a new form _below_ the main editing form. Make sure you locate the correct '/form' tag.
After that tag (_outside_ the main form) add a new form::
<form method=POST tal:condition="python: context.merged == None"> <input type="hidden" name="@action" value="merge"> <table class="form"> <tr> <th>Merge into</th> <td> <tal:block tal:content="structure context/issue/menu" /> </td> <td> <input type="submit" value=" Merge "> </td> </tr> </table> </form>
You can of course replace the very boring issue selector you get by::
<tal:block metal:use-macro="templates/lib/macros/issue_menu" />
Which does suppose you have defined a custom macro in $TRACKER_HOME/html/lib.html for generating the issue list. YMMV.
This should work. Test.
To be continued...
- In the next installment of this series, we'll build upon this foundation to beef up this form so
we'll not only be able to 'merge into target issue', but also to 'supersede from older issue'
Superseding differs from merging, in that superseding handles related, but still different issues (brothers and sisters) whereas merging handles identical split personality issues (clones).
Stay tuned.
From wiki Sat Feb 3 20:42:27 +1100 2007 From: wiki Date: Sat, 03 Feb 2007 20:42:27 +1100 Subject: Make it work with roundup 1.x Message-ID: <20070203204227+1100@www.mechanicalcat.net>
With some small changes, this works with Roundup 1.x, too:
- the "issue" class is changed in schema.py rather than dbinit.py
- the "MergeAction" class is put in an existing or separate Python file in the "extensions" subdirectory
Restart the Roundup server to recognise the schema change and the new extension.
- to put the form for the merge action in the "issue.item.html" template, I did as follows:
- - added a slot to the "searchbox" div in page.html
<div id="searchbox"> <form method="GET" action="issue" metal:define-slot="search-form"> <!-- ... --> </form> <metal:slot define-slot="more-forms"/> </div>
- - filled this new slot in "issue.item.html"
<tal:if condition="context/is_edit_ok"> <form metal:fill-slot="more-forms" tal:condition="not:context/merged" class="additional" method="POST" name="mergeForm"> <input type="hidden" name="@action" value="merge"> <label for="merge_into" i18n:translate="">Merge into</label> <input type="text" id="merge_into" name="merge_into" size="5"> <span tal:replace="structure python:db.issue.classhelp('id,title', property='merge_into', inputtype='radio', form='mergeForm')" /> <input type="submit" value=" Merge " i18n:attributes="value"> </form> </tal:if>
Of course, this can be improved further; there is no GUI method so far to undo a merge. You can restore and unmerge issueXXXX using the "roundup-admin" script, using the following commands::
restore issueXXXX set issueXXXX merged= commit
The file attachments and messages will stay double-linked, but this might be ok and can be changed TTW.