Uncluttered Convenience

Icon

the simplest thing that could possibly work

Integration: Project Hamster, RTM, and Trac with Greasemonkey & Ticket2RTM

Or how to achieve tracking of almost everything with no data re-entry using Firefox, Greasemonkey, Remember The Milk (RTM henceforth), Project Hamster for general time tracking, Trac and it’s Timing and Estimation and Ticket2RTM plugins for ticket & hour tracking and RTM integration, and Git or SVN for version control…or any combination of these desired. My condolences if you’re using Windoze or IE. I don’t use either and the following assumes Firefox and something Linuxy.

Project Hamster is a wonderfully easy and robust time tracking system written in python (cross-platform) and has a nice applet for the gnome toolbar among other features. I already use Trac and it’s Timing and Estimation plugin to track time related to code, tickets, and changesets, but not everything I want to track can be related to such things. I use Remember The Milk for pretty much everything I need to do aside from sleeping, eating, and various other things I can’t or don’t care to have on a “to-do” list – the Trac plugin Ticket2RTM ensures tickets are also in RTM, RTM becoming the base center of operations for my life.

Hamster, while I used it religiously for a time some years ago, fell by the wayside as just another task after the initial “wow!” realizations on how I spent my time. Recently I needed Hamster to step in and fill the gaps…but only the gaps. Re-entry of data is a very big no-no in my world, so I set about brainstorming a way to get data from Remember The Milk into Hamster.  After all, if I already wrote down what I need to do in RTM or Trac, no sense rewriting it for Hamster when I’m doing it.

Enter Greasemonkey, a Firefox plugin that lets you add and alter javascript on any webpage. Using a Greasemonkey script I added a “copy” link to tasks in RTM. The original idea of just clicking a link and having it go directly to Hamster failed due to Dbus issues with scripts, but I find I prefer the copy method as it lets me edit tasks before I enter them into Hamster (usually by prepending -10 because I’ve been working on something different for the last ten minutes and not noticed).

On to the Greasemonkey code!
// ==UserScript==
// @name           Copy Links for Hamster Tracking
// @namespace      http://jaciss.wordpress.com
// @description    Adds a copy button next to each task for starting Project Hamster tracking via a local python script
// @include        https://www.rememberthemilk.com/home/*
// @require        http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js
// ==/UserScript==
/*
jQuery inclusion code courtesy of: http://joanpiedra.com/jquery/greasemonkey/
jQuery is extreme overkill here, but it's (obviously) a quick hack at this stage.
!NOTE: The below are just examples to show what can be done!  I use Trac (ticket/bug tracking) and the timingandestimation (track hours by commit messages, now generated by these scripts) and ticket2rtm plug-ins for my work - ignore,  remove, or edit to suit your system(s) of choice where necessary.
*/
var $;

// Add jQuery
(function() {
if (typeof unsafeWindow.jQuery == 'undefined') {
var GM_Head = document.getElementsByTagName('head')[0] || document.documentElement,
GM_JQ = document.createElement('script');
GM_JQ.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js';
GM_JQ.type = 'text/javascript';
GM_JQ.async = true;
GM_Head.insertBefore(GM_JQ, GM_Head.firstChild);
}
GM_wait();
})();

// Check if jQuery's loaded
function GM_wait() {
if (typeof unsafeWindow.jQuery == 'undefined') {
window.setTimeout(GM_wait, 100);
} else {
$ = unsafeWindow.jQuery.noConflict(true);
letsJQuery();
}
}

// All your GM code must be inside this function
function letsJQuery() {
//alert($); // check if the dollar (jquery) function works
//alert($().jquery); // check jQuery version
var lists = new Array();

function compileLists() {
$.each(unsafeWindow.listList.list.entries,
function() {
lists[parseInt(this[0])] = this[1];
return true;
});
}

function htmlEntities(str) {
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '/quote');
}

function addCopyLinksToTasks() {
var entries = unsafeWindow.taskList.list.hashMap;
var locs = unsafeWindow.locationList.list.hashMap;

$('#tasks table.xtable tr.xtr').each(function() {
var entry_id = parseInt(/_(\d+)$/.exec($(this).find('td.xtd_check form').attr('id'))[1]);
if (entry_id > 0) {
var entry = entries[entry_id];
var loc = unsafeWindow.locationMgr.getTaskLocation(entry.series_id);
var tags = unsafeWindow.tagMgr.getTagsForTaskSeries(entry.series_id);
var url = entry.url == null ? '': entry.url;
var name = entry.name;
var src = entry.location_id;
var list = lists[entry.list_id];
url = url.replace('http:\/\/', '');
#I preface projects with p- for the GTD RTM script
#project = list.replace('p-', '');
name = htmlEntities(name);
name = name.replace(/\'/g, '\\\'');
tagstr = '';
if (tags.length > 0) {
for (i = 0; i < tags.length; i++) {
tagstr += ' #' + tags[i];
}
}
if (loc == 'none') loc = 'computer';
#if it's a trac ticket
if (url.match("trac")) {
pathparts = url.split('/');
ticket = pathparts[pathparts.length - 1];
ticket = ' #working ticket ' + ticket;
} else ticket = '';
text = 'python /usr/share/pyshared/myhamster.py &quot;' + project + '@' + loc + ', ' + name + tagstr + ticket + "&quot;";
if ($(this).html().match('copier')) {
$('copier_' + entry_id).replaceWith('<td id="copier_' + entry_id + '"><a onclick="netscape.security.PrivilegeManager.enablePrivilege(\'UniversalXPConnect\');Components.classes[\'@mozilla.org/widget/clipboardhelper;1\'].getService(Components.interfaces.nsIClipboardHelper).copyString(\'' + text + '\')">copy </a></td>');
} else {
$(this).append('<td id="copier_' + entry_id + '"><a onclick="netscape.security.PrivilegeManager.enablePrivilege(\'UniversalXPConnect\');Components.classes[\'@mozilla.org/widget/clipboardhelper;1\'].getService(Components.interfaces.nsIClipboardHelper).copyString(\'' + text + '\')">copy </a></td>');
}
}
});
};

compileLists();
setInterval(addCopyLinksToTasks, 3000);
}

So this adds a copy link that will alert you to the danger of allowing “outside” sources access to your clipboard. When clicked, it copies something like

python /usr/share/pyshared/myhamster.py "webpresence@computer, write blog about greasemonkey key to tying hamster & rtm together #focus #na"

Where, for me, webpresence is both a list in RTM and a project/activity in Hamster (having a positive web presence is a goal of mine). Now we paste this text onto a command line and it calls a script (myhamster.py) which looks something like this:
import sys
import re
import hamster.client
from decimal import Decimal

#where to add commit log entries
commit_file = '/path/to/commit.txt'
storage = hamster.client.Storage()
facts = storage.get_todays_facts()
last_fact = facts[len(facts)-1]

#is there an ongoing task?
if last_fact['end_time'] == None:
try:
#is it a trac ticket?
for tag in last_fact['tags']:
if "#" in tag:
hours = last_fact['delta'].seconds
hours = Decimal(hours/60)
hours = Decimal(hours/60)
commit_str = "%s closes %s (%f), " % (last_fact['description'],tag,hours)
#print "Ticket was being worked on, adding line to commit.txt"
print commit_str
cf = open(commit_file,'a')
cf.write(commit_str)
cf.close()
except KeyError:
print "no tags"
else:
print "No active activity!"

new_fact=' '.join(sys.argv[1:99])
new_fact = new_fact.replace('/quote','"')
if "#" in new_fact:
reg = re.compile('ticket\s\d+')
tractag = reg.findall(new_fact);
if len(tractag)>0:
print "Trac tag initiated."
tractag = tractag[0]
tractag = tractag.replace('ticket ','#')
else: tractag=''
reg2 = re.compile('#[a-zA-Z0-9]+')
tags = reg2.findall(new_fact)
i=0
for tag in tags:
tags[i]=tags[i].replace('#','')
i=i+1
if(len(tractag)>0): tags.append(tractag)
else:
tags=''

print new_fact
#print tags

print storage.add_fact(new_fact,tags)

Note that the above script writes Timing and Estimation formatted lines (for tracking hours/ticket in Trac) to a commit.txt file if it detects a # in the tag. I left this in as an example of what can be done with this sort of system. I usually have to edit the commit file before use, but I rarely forget to add things anymore, and certainly don’t have to re-enter the data, which was the point of this exercise.

Filed under: how-to, , , , , , , , ,

3 Responses

  1. Teo Romera says:

    Hello! Thanks for your post. I have been using RTM for years now and I started a new job from home recently. I plan to use RTM for my job tasks also, and I am trying Hamster for time management / reporting. So this post looks really useful to me.

    I used to code a long time ago, so I can try to understand and try to modify your script to my needs. But I am not familiar with js. Is this GM script supposed to work as it is with RTM? Can a variable be called $ in js? Also, the lines starting with # are supposed to be comments, right?

    I am only interested in copying current RTM tasks to Hamster… so I can track time I spend on them.

    Thanks for your help!

    • jaciss says:

      My apologies for not seeing your comment earlier – for some reason WordPress didn’t automatically approve it. To briefly answer your question, the first script goes into a Firefox plugin called Greasemonkey. It should automatically add “copy” links to each task when you visit rememberthemilk.com. The copied text is meant to be pasted onto a command line – it calls a python script(the other script in the post) which adds the task to Project Hamster.

      The dollarsign function has become quite popular in javascript libraries. It’s usually shorthand for document.getElementById, and yes, # prefaces comments in the python script.

      If you’re still interested in this system I can add the Greasemonkey script to their online library for easy installation and include the python in the script’s comments…in a more user-friendly form, of course.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Recently Bookmarked

Follow

Get every new post delivered to your Inbox.