Friday, September 30, 2011

Trac and Kanban - Redux

Hello there!

Since my last post before summer, the number of total visits has gone from 2000 to over 7000! One of the reasons for this was the  Trac and Kanban post, where I breifly describe how to use a Trac wiki page as a basic kanban board. (The main reason is this Stack Overflow question which asks for a bit more, such as drag-n-drop and the like.)

We'we worked with this for half a year now, and it still feels like a good thing. So good, actually, that I simply had to improve it a bit.

Trac and Kanban, v2.0

Mainly I now use the "format=table" option to get a table, which means we get color for the priority, we can show who is owning the item and what resolution it had (duplicate/invalid/worksforme/wontfix, etc) where suitable.

It looks like this (with no one working on anything at the moment, but you get the idea):

This could be even better if Trac supported sorting on several ticket fields, but that is not possible with 0.12. There is a ticket and patch available on the Trac home site, but I haven't tried it ... yet.

Code review

For the confused, note that I still use two custom fields for post-commit code review "reviewed_by" and "has_review_issues".

I will probably change the boolean review_issues to a checkbox that better handles the states and workflow for code review:

  • not_reviewed
  • reviewed_with_issues
  • review_issues_fixed
  • reviewed_and_approved

A custom TicketChangeListener could then manage the has_issues to issues_fixed transition, so that it's automatically visible that the code has changed.

Automatic subpage/dashboard lists 

The second new thing I've discovered and deployed is the TitleIndex macro, to simply list show all sub pages for a single wiki page. Since we have all the dashboards under Dashboard/ProductNameX.Y pages, it's easy to list them all using [[TitleIndex(Dashboard/ProductName,hideprefix)]] on a product's "home" wiki page.

Old/completed milestones could then be moved to DashboardOld container so that they're still there, but does not clutter the "current and upcoming milestones/sprints" list.

Limit right column to last two weeks

Adding modified=2weeksago.. to the queries for closed tickets makes the dashboard work over long time, since older (closed) tickets are not modified and thus removed from the table after some time.

This really helps with using the system in a "pure" Kanban setting, without sprints or milestones. It's also useful to keep a current status view during longer milestones with 100+ tickets.

Wiki code

The wiki page above was generated from the following code:

This is the ticket dashboard for **[milestone:"Orsync 1.0"]**
([/query?milestone=Orsync+1.0&status=new&status=reopened&status=testable&status=accepted&order=priority&col=id&col=summary&col=status&col=type&col=priority&col=milestone&col=component open tickets])
-- [wiki:Orcamp/FeatureList current feature list]

Create new
[/newticket?milestone=Orsync+1.0&component=Apps/Orsync&type=defect defect],
[/newticket?milestone=Orsync+1.0&component=Apps/Orsync&type=enhancement enhancement],
[/newticket?milestone=Orsync+1.0&component=Apps/Orsync&type=suggestion suggestion],
[/newticket?milestone=Orsync+1.0&component=Apps/Orsync&type=change change],
[/newticket?milestone=Orsync+1.0&component=Apps/Orsync&type=task task],
[/newticket?milestone=Orsync+1.0&component=Apps/Orsync&type=demo demo] or
[/newticket?milestone=Orsync+1.0&component=Apps/Orsync generic] ticket.

#!div style="float:left; margin-right:1em; width:30%"

= Open Tickets =

Total: [[TicketQuery(status=new|reopened,type!=suggestion,milestone=Orsync 1.0,format=count)]]
([[TicketQuery(status=new|reopened,type=suggestion,milestone=Orsync 1.0,format=count)]])

== New & Reopened ==

[[TicketQuery(format=table,col=summary|type,status=reopened|new,type!=suggestion,milestone=Orsync 1.0,group=priority,order=type)]]

== Suggestions ==

[[TicketQuery(group=priority,status=new|reopened,type=suggestion,milestone=Orsync 1.0)]]

#!div style="float:left; margin-right:1em; width:30%"

= Work In Progress =

[[TicketQuery(status=accepted,order=priority,group=owner,milestone=Orsync 1.0,format=count)]]
([[TicketQuery(status=accepted|testable,milestone=Orsync 1.0,format=count)]])

== Started ==

[[TicketQuery(status=accepted,group=owner,order=priority,format=table,col=summary|type|owner,milestone=Orsync 1.0)]]

== Unreviewed tickets ==

[[TicketQuery(review_issues!=1,reviewed=,status=testable,group=owner,order=priority,format=table,col=summary|type|owner,milestone=Orsync 1.0)]]

== Reviewed tickets with issues ==

[[TicketQuery(review_issues=1,status=testable,group=owner,order=priority,format=table,col=summary|type|owner,milestone=Orsync 1.0)]]

== Ready for Test ==

[[TicketQuery(review_issues=1,status=testable,group=developertest,order=priority,format=table,col=summary|type,milestone=Orsync 1.0)]]

#!div style="float:left; margin-right:1em; width:30%"

= Closed Tickets =

Total: [[TicketQuery(status=closed,resolution=fixed,milestone=Orsync 1.0,format=count)]] 

([[TicketQuery(status=closed,resolution!=fixed,milestone=Orsync 1.0,format=count)]])

== Fixed tickets (last two weeks) ==

[[TicketQuery(status=closed,format=table,col=summary|type,resolution=fixed,order=priority,desc=1,milestone=Orsync 1.0,modified=2weeksago..)]]

== Denied tickets 
(last two weeks) ==

[[TicketQuery(status=closed,resolution=wontfix|worksforme,format=table,col=summary|type|resolution,order=priority,desc=1,milestone=Orsync 1.0

== Invalid/duplicate/other 
(last two weeks) ==

[[TicketQuery(status=closed,resolution!=fixed|wontfix|worksforme,format=table,col=summary|type|resolution,order=priority,desc=1,milestone=Orsync 1.0


#!div style="clear:both"

There you go. Hope it helps!

Tuesday, March 29, 2011

Process spawned successfully

Our daughter arrived on March 19th, hence the lack of updates lately.

Normal service may resume in the future, but expect shorter posts in the near time.

P.S. I'm considering applying as a GSoC mentor for Buildbot, guiding the Javascript UI project together with a few other Buildbot developers.

Saturday, February 26, 2011

Book Review - What Technology Wants by Kevin Kelly

I decided to order a few non-fiction book from Amazon a few months ago, as a change from the reality-escaping high-fantasy, grand space opera or hard sci-fi that I usually churn through. I suppose it's got something to do with growing up getting older.

Included in the package was:
  • Managing Humans by Michael Lopp (has a very neat 'geek & manager' blog at
  • Linchpin by Seth Godin (has a pretty unique 'be awesome now' blog at
  • Guns, Germs and Steel by Jared Diamond (the book won the Pulitzer Prize)
  • Crossing the Chasm: Marketing and Selling High-Tech Products to Mainstream Customer by Geoffery A. Moore (was recommended due to the other books I've ordered)
And, the reason for this post:
  • What Technology Wants by Kevin Kelly (former Wired editor, etc. Website at
Apart from the last one, I've also finished the two first and heartily recommend them.  I might review them too, but if you can't wait for that, I'm fairly certain you won't regret spending some time with them. Also, I'm currently a third of the way into Guns, Germs and Steel and it has not disappointed the slightest yet.

Anyway, withour further ado, here comes my short and humble review of:

What Technology Wants by Kevin Kelly
(This review is also posted on

What Technology Wants is a slightly puzzling title which poked my interest. Can technology actually "want" something? What does that phrase mean? Does it attempt to imply that technology has a will of its own, is it is a "force of nature", or just something inevitable built in the rules of the universe?

These questions might sound a bit like "hippy-talk" (for lack of a better term) and while reading the first chapters of the book, which try to grasp this rather evasive concept, it felt rather hard to follow out where the author tries to lead. Solid lines of reasoning do emerge eventually, so if the narrative feels a bit vague in the beginning, one should not give up. Getting the grips on the driving force behind all the technology that most of us humans has ready access to, and what this actually means, is to say the least a rather daunting task. Also, I suppose the book tries to cater for many readers, not just the tech-savvy, so it attempts to gather everyone and provide a foundation on which the ideas and theories of subsequent chapters can build on.

The amount of background research made for the book is phenomenal. He devotes a large part of the book on the Amish, being that they are a successful group that chooses to live outside the "normal" western civilization, actively choosing to abstain from much of today's technology. However, he notes, crucially in my (and his) opinion, that the Amish would not be able to function without the rest of the society, and that they continually lag about 50 years behind.

This choosing of technology is not specific to Amish though. Everyone is doing it, one way or the other. Often, we are not very consistent in our choices. I.e. we may be on the cutting edge on one part, but several generations back on another, just because we want to.

Kelly relates this to the fact that Amish seem to live a pretty happy and unstressful life, at least in comparison to many of the rest of us. They perform their honest work and labor with the tools they have, being fairly content with the situation. They choose their tools by waiting for the rest of society (and select individuals of their own) to try out technologies before choosing that which is good and not disruptive to their way of living. This of course relies on the fact to the rest of us continues to provide spare parts for old tech, as well as continuously producing new technology.

An interesting side-fact (related to the issue of spare parts above) that is stated is that, apparently, no technology ever dies. You can find somewhere to buy a piece of flint and steel, an axe, an abacus, vacuum tubes (for your "this-goes-to-eleven" guitar amp), a vinyl player, etc. It don't doubt it at all, and it does help to choose between various technologies.

The book also contains a treatise on the unabomber. Being Swedish (and rather young at the time of the event), I knew very little about him before reading this book. There are some excerpts of the unabombers manifesto included (and discussed) in the book, which make the case that technology is inevitable and people cannot escape it. From this, IIRC, the unabomber draws the conclusion that since it's forced upon people by the system, so the system (and/or civilization) such as it is must be destroyed completely for the people to be free.  Most of us agree with the first part, but our rejection of the latter conclusion probably separates civilization from apocalypse. (Also, even the unabomber tried to reject civilization and technology for several years, but could not do so completely, since he needed bullets for his rifle, rope for his traps and gasoline for the car to be able to travel to trade these things.)

Kelly proffers the same statement here, which is that technology in something inevitable, in the same sense that the universe has given us  DNA, multi-cellular organisms, mammals, humans and civilization (for better or worse). One simply cannot prevent technology from appearing, given how far everything have gone already, and from where it actually started (i.e. the primordial soup). Complexity, and the perpetual increase thereof, is inherent in the foundations of the universe. We've had natural evolution for almost four billion years, and for the last ten to twenty thousand years (give or take a few), mankind (a product of the above) has been selecting, domesticating, refining and reworking different parts of nature to its liking. Now, we're selecting technology instead, and technology is undergoing evolution under the same criteria that (probably) made us domesticate the wolf rather than the hyena. (It's more beautiful, more intelligent, more adaptable, etc etc.)

This strive towards beauty, complexity, adaptability, etc etc is going on with technology today. Personally, I see this in the world of computer components, libraries, frameworks, utilities, etc. The open-source ecosystem a good example of this evolutionary process, as  libraries come, evolve and leave. Some evolve quickly then stagnate when there is no opposition, then either gets wiped out when a new, better toolkit appear, or they attract sufficient interest (from it's users and developers) to catch up. The book's final chapters summarizes a number of criteria that are selected for in the evolutionary process, that will continue to be the driving force of change as technology evolves into more diverse, specialized, complex, interlinked, adaptable and beautiful manifestations..

Kelly, rather early, names the entire technological sphere the Technium. In the end, he concludes that what it wants is just to live and prosper, just like any other self-evolvable entity. The difference is that the Technium can evolve a thousand or a million times faster, and that it this speed is because it does not evolve by chance (i.e. mutation), but rather the fact that it is actively driven (you could say developed) towards improvement with every generation. Also, since it's so interlinked, and has perfect memory (i.e. the Internet, more or less), it will build upon itself much faster than evolution (wherein for instance the eye evolved independently eight times) and even faster than human civilization (which could not communicate ideas and inventions especially fast until we had the Internet).

I think this book is awesome in several ways. The question it attempts to both define, investigate and answer is immense. It is also a most relevant question, as I (and I suspect a few more) wonder where we are heading with all this technology, how it will shape us and what we can do, if anything, to guide it during its evolution. And since it actually manages to pull it off, I cannot by heartily recommend it to anyone that has some kind of interest in the field.

Having left me me with a sense that there is really no difference between the big bang and the forming solar systems, life and evolution, humans and civilization and finally technology (and thus the Technium, as Kelly names it), I feel that I'm standing slightly more on firmer ground, while the world around us spins ever faster.

Or maybe, I'm just getting older. ;)

Monday, January 17, 2011

Configuring PyQt across unit-tests

We had some problems with configuring the PyQt API version across unit-tests. The problems arise because several modules try to call sip.setapi, which you can only do once (even if you call it with the same args, which ought to be idempotent, you'd think.)

Anyway, what I did was to create a module called 'pyqtconfig' with the following contents:

import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
sip.setapi('QTextStream', 2)

import PyQt4.Qt
Qt = PyQt4.Qt

Then I simply do 'from pyqtconfig import Qt' in each module. Works like a charm.

(If you want to import each module separately, you need to adapt the above code somewhat. I'll leave that as an exercise for the reader. :)

Trac and Kanban

(Don't miss my update Trac and Kanban Redux)

We use a Trac wiki page as a simple kanban board. It's pretty neat, especially when put on a 42" LCD TV in the project room.

Here's how it looks in the browser:

And here's the wiki code, all built using TickeyQuery-macros, both for counting (to keep an eye on the WIP-limit) and listing the tickets themselves. It's pretty basic, but it works well  for our small team and it's easy to understand and maintain.

This is the ticket dashboard for Orcamp for [milestone:"Orcamp 1.0"] ([/query?milestone=2010+M11&order=priority&col=id&col=summary&col=status&col=type&col=priority&col=milestone&col=component tickets])  -- [wiki:Orcamp/FeatureList current feature list]

#!div style="float:left; margin-right:1em; width:30%"

= Open Tickets = 

Total: [[TicketQuery(status=new|reopened,milestone=Orcamp 1.0,format=count)]] 

[[TicketQuery(group=priority,status=new|reopened,milestone=Orcamp 1.0)]]

#!div style="float:left; margin-right:1em; width:30%" 

= Work In Progress =

Total: [[TicketQuery(status=accepted|testable,milestone=Orcamp 1.0,format=count)]] 

== Started ==

[[TicketQuery(status=accepted,order=priority,group=owner,milestone=Orcamp 1.0)]]

== Ready for Test ==

[[TicketQuery(status=testable&review_issues=0&reviewed!=,group=developertest,milestone=Orcamp 1.0)]]

== Reviewed tickets with issues ==

[[TicketQuery(review_issues=1,group=owner,status=testable|closed,resolution=fixed,milestone=Orcamp 1.0)]]

== Unreviewed tickets == 

[[TicketQuery(review_issues!=1,status=closed|testable,group=status,resolution=fixed,reviewed=,milestone=Orcamp 1.0)]]

#!div style="float:left; margin-right:1em; width:30%" 

= Closed Tickets = 

Total fixed: [[TicketQuery(status=closed,milestone=Orcamp 1.0,resolution=fixed,format=count)]] 
Other: [[TicketQuery(status=closed,milestone=Orcamp 1.0,resolution!=fixed,format=count)]] 

== Fixed tickets ==

[[TicketQuery(status=closed,group=type,resolution=fixed,milestone=Orcamp 1.0)]]

== Other resolutions ==

[[TicketQuery(status=closed,group=resolution,resolution!=fixed,milestone=Orcamp 1.0)]]


#!div style="clear:both"

Note that the above wiki code contains some references to custom fields (notably review_issues) that we use to manage code reviews on a ticket-by-ticket basis. In our small team, we've decided to allow commits of unreviewed code, but require code to be reviewed before it's cleared for testing. 

As always, you should adapt to local conditions. :)

Sunday, January 9, 2011

BuildBotIcon - Python Style (and it's got tests this time!)

If you've read earlier posts here, you might know that I'm behind BuildBotIcon, a small Java application that sits in the system tray, polling one or more BuildBot servers and reports any changes. It also plays sounds and turns lava lamps on or off.

I've decided to do a version in Python using PyQt instead. It's mostly as a fun exercise swim in the ecosystem of python (setuptools, unittest, mocking, coverage, yaml, etc) but it might come out better than the original.

PyQs nice, and the new-style signals-slots makes it a breeze to do what you want if, assuming you know your way around Qt. (Not that it's hard to learn, I just knew Qt pretty well before embarking on this project.)

Writing unittests are a breeze, and I think that it sure helps knowing that the code works, instead of knowing it only compiles. Coming from C++ this is actually pretty nice change. However, you should write with testing from the beginning. Grafting unit-testing later on is difficult and might not be a net win for all components, especially in C++ . (Mostly since you've run them for a while and the obvious (and thus easy-to-test-for) bugs have already been ironed out.)

However, the greatest fun so far has been using Mock, yet another mock object module for python that takes a nice and intuitive approach to mocking, IMHO. Instead of record/replay, or setup/run/verify, it's more like mock/run/assert. Read Mocking, Patching, Stubbing: all that Stuff (and it's link to Mocks aren't Stubs) for more information on this.

The ability of substituting any function or object in the APIs you're running makes unittesting immensely powerful and thorough. If you trust the framework (Qt in my case) it's very easy to verify that play() is called on the right QSound (without making noises) or that get() is run with the right URL to access the network.
Of course, more complex behaviours are trickier to mock properly, so it helps to refactor your app into testable parts (which you should do anyway).

Also, I've used YAML for the settings file this time (using PyYAML). All is good and well, except that it turns out that writing the settings file first and implementing the app second had probably been a better idea. TDD again.

It has all fallen out pretty nicely so far, except that, similar to the Java version, I've bungled together the backend that does networking and parsing with the UI. Since I currently have three ways to show build state (tray icon, sound and lava lamps) I should really had put a Listener interface in there. Fixing that now means refactoring the unit tests too, which is less fun. :-|

Nevertheless, all of this is experience is bound to pay back in my ThrustRacer-project (should I get it off the ground) or at work, where we're about to embark on a year of much Python coding. I don't expect either to get it right from the start, one never does. :)