Org-mode tricks for team management

During the last three years I have managed a team of engineers doing ink-jet printer design at HP's R&D lab in Barcelona. The excellent org-mode emacs package has helped me keep up-to-date with the work of the 12 engineers in my team, working in four different projects, without an obvious impact in my sanity.

During this time some work-flows have evolved, and I have written helper functions for org-mode to support them. This is an account of the steady state at which I have arrived. Please let me know if any of what follows could be done or written in a simpler or more idiomatic way.

After this article was published some people suggested that it might make sense to make it a contrib in org-mode, so I packaged it as the contrib/lisp/org-secretary.el file that you can find in the org-mode repository. I have re-structured this page so that it shows first how to configure and use it. I've left the code description at the end.

Todo keywords

I keep todo items for my team members and for me. I encode mine with TODO, the team's with TASK:

(setq org-todo-keywords
      '((sequence "TODO(t)" "|" "DONE(d)" "CANCELLED(c)")
        (sequence "TASK(f)" "|" "DONE(d)")
        (sequence "MAYBE(m)" "|" "CANCELLED(c)")))

Using different colors for TODO and TASK makes it easier to pick up at a glance which items are mine:

(setq org-todo-keyword-faces
      '(("TODO" . (:foreground "DarkOrange1" :weight bold))
        ("MAYBE" . (:foreground "sea green"))
        ("DONE" . (:foreground "light sea green"))
        ("CANCELLED" . (:foreground "forest green"))
        ("TASK" . (:foreground "blue"))))

Projects

Complex activities are projects, and are marked with the prj tag. They are supposed to contain todo items, and are considered stuck unless they do. The prj tag should not be inheritable, because otherwise its tasks will appear as projects:

(setq org-tags-exclude-from-inheritance '("prj")
      org-stuck-projects '("+prj/-MAYBE-DONE"
                           ("TODO" "TASK") ()))

Task and project ownership

Ownership is managed with tags, one for each team member. When somebody owns a project his or her name will tag the project definition line, together with prj, and it will be inherited by all the TASK todo items belonging to the project. When a TASK todo item does not belong to a project it has at least a person's name tag.

Agenda custom commands

With all this we are ready to define the custom commands. The cjr tag marks personal todo items in my case; change to whatever suits you.

(setq org-agenda-custom-commands
      '(("h" "Work todos" tags-todo
         "-personal-doat={.+}-dowith={.+}/!-TASK"
         ((org-agenda-todo-ignore-scheduled t)))
        ("H" "All work todos" tags-todo "-personal/!-TASK-MAYBE"
         ((org-agenda-todo-ignore-scheduled nil)))
        ("A" "Work todos with doat or dowith" tags-todo
         "-personal+doat={.+}|dowith={.+}/!-TASK"
         ((org-agenda-todo-ignore-scheduled nil)))
        ("j" "TODO dowith and TASK with"
         ((org-sec-with-view "TODO dowith")
          (org-sec-where-view "TODO doat")
          (org-sec-assigned-with-view "TASK with")
          (org-sec-stuck-with-view "STUCK with")))
        ("J" "Interactive TODO dowith and TASK with"
         ((org-sec-who-view "TODO dowith")))))

Usage

I always start a meeting telling emacs who I am with, using C-c w. If I am with more than one person I enter all the names separated by spaces. I sometimes also say where I am, using C-c W. Then I do C-c a j and I see:

  • Todo items defined with TODO (ie, mine) in which the dowith property matches any of the people with me.
  • Todo items defined with TODO in which the doat property matches my current location.
  • Todo items defined with TASK that are tagged with the name of any of the people with me (this is, assigned to them).
  • Stuck projects tagged with the name of the people with me.

As the meeting advances I will use C-c j to add meta-data with the people with me, the location and the time to some entries.

Example

In this file I have defined a TASK for John, a couple for Sue, and a TODO for me. There's also a stuck project associated to Sue (for some reason, the export does not honor the colors I defined for TODO and TASK).

* Open areas                                           :program:
*** TASK Win-win scenario for tremendous success       :john:
    :PROPERTIES:
    :on:       <2009-12-04 Fri 11:49>
    :with:     john
    :END:
    Leveraging on our existing assets.
*** Capitalize on previous challenges                  :prj:sue:
***** TASK Figure out what the previous challenges were
      :PROPERTIES:
      :on:       <2009-12-04 Fri 12:04>
      :with:     alex
      :END:
      And who was promoted.
***** TASK Look up "capitalize" in the dictionary
*** Success strategy looking forward                   :prj:sue:
    The strategy looking backwards already in place.
*** TODO And why are we doing this, again?
    :PROPERTIES:
    :dowith:   sue
    :END:
    Shouldn't we build a win-win scenario for tremendous
    success leveraging on our success strategy looking
    forward, while capitalizing on our existing assets as we
    move forward into the next quarter?

If I start a meeting with Sue I'll do C-c w sue, and then C-c a j to get the following agenda view:

"Agenda for Sue"

How it works

Context: with whom and where

Most of the management activity happens at meetings with team members. There are two main variables that define the context: who is with me, and where I am when we are attending a regular meeting (like the section's staff). I want to be able to:

  • Quickly add meta-data to entries storing the context, including with whom, where and the time.
  • Easily retrieve todo items and stuck projects associated to the people I am meeting with.
  • Easily retrieve todo items that I want to do when meeting with the people currently with me.

I define two variables and their histories:

(defvar org-sec-with "nobody"
  "Value of the :with: property when doing an
   org-sec-tag-entry. Change it with org-sec-set-with,
   set to C-c w")

(defvar org-sec-where ""
  "Value of the :at: property when doing an
   org-sec-tag-entry. Change it with org-sec-set-with,
   set to C-c W")

(defvar org-sec-with-history '()
  "History list of :with: properties")

(defvar org-sec-where-history '()
  "History list of :where: properties")

with the functions to set them up and keyboard shortcuts:

(defun org-sec-set-with ()
  "Changes the value of the org-sec-with variable for use
   in the next call of org-sec-tag-entry."
  (interactive)
  (setq org-sec-with (read-string "With: " nil
                                  'org-sec-with-history "")))
(global-set-key "\C-cw" 'org-sec-set-with)

(defun org-sec-set-where ()
  "Changes the value of the org-sec-where variable for use
   in the next call of org-sec-tag-entry."
  (interactive)
  (setq org-sec-where
        (read-string "Where: " nil
                     'org-sec-where-history "")))
(global-set-key "\C-cW" 'org-sec-set-where)

These make it easy to let emacs know who is with me and where I am, which we shall later use to tag entries with the current context and to retrieve agenda information based on it.

Tagging entries

There are three kinds of meta-data I want to be able to store quickly:

  1. A dowith property that marks a TODO entry as intended to do when meeting with somebody.
  2. A doat property that marks a TODO entry as intended to do when at a given place.
  3. And the on, at and with properties that store current context information in any kind of entry.
(defun org-sec-set-dowith ()
  "Sets the value of the dowith property."
  (interactive)
  (let ((do-with
         (read-string "Do with: "
                      nil 'org-sec-dowith-history "")))
    (unless (string= do-with "")
      (org-entry-put nil "dowith" do-with))))
(global-set-key "\C-cd" 'org-sec-set-dowith)

(defun org-sec-set-doat ()
  "Sets the value of the doat property."
  (interactive)
  (let ((do-at (read-string "Do at: "
                            nil 'org-sec-doat-history "")))
    (unless (string= do-at "")
      (org-entry-put nil "doat" do-at))))
(global-set-key "\C-cD" 'org-sec-set-doat)

(defun org-sec-tag-entry ()
  "Adds a :with: property with the value of org-sec-with if
   defined, an :at: property with the value of org-sec-where
   if defined, and an :on: property with the current time."
  (interactive)
  (save-excursion
    (org-entry-put nil "on" (format-time-string
                             (org-time-stamp-format 'long)
                             (current-time)))
    (unless (string= org-sec-where "")
      (org-entry-put nil "at" org-sec-where))
    (unless (string= org-sec-with "nobody")
      (org-entry-put nil "with" org-sec-with))))
(global-set-key "\C-cj" 'org-sec-tag-entry)

Agenda views

The next step is to define agenda functions that make use of the stored state and the taggings. First we need a helper function (there has to be an easier way to do this):

(defun join (lst sep &optional pre post)
  (mapconcat (function (lambda (x)
                         (concat pre x post)))
             lst sep))

And then we define several views for use with the agenda:

(defun org-sec-with-view (par &optional who)
  "Select tasks marked as dowith=who, where who
   defaults to the value of org-sec-with."
  (org-tags-view '(4) (join (split-string (if who
                                              who
                                            org-sec-with))
                            "|" "dowith=\"" "\"")))

(defun org-sec-where-view (par)
  "Select tasks marked as doat=org-sec-where."
  (org-tags-view '(4) (concat "doat={" org-sec-where "}")))

(defun org-sec-assigned-with-view (par &optional who)
  "Select tasks assigned to who, by default org-sec-with."
  (org-tags-view '(4)
                 (concat (join (split-string (if who
                                                 who
                                               org-sec-with))
                               "|")
                         "/TASK")))

(defun org-sec-stuck-with-view (par &optional who)
  "Select stuck projects assigned to who, by default
   org-sec-with."
  (let ((org-stuck-projects
         `(,(concat "+prj+"
                    (join (split-string (if who
                                            who
                                          org-sec-with)) "|")
                    "/-MAYBE-DONE")
           ("TODO" "TASK") ())))
    (org-agenda-list-stuck-projects)))

It is also convenient to be able to build an agenda view for somebody that is not currently in the org-sec-with state variable,

(defun org-sec-who-view (par)
  "Builds agenda for a given user.  Queried. "
  (let ((who (read-string "Build todo for user/tag: "
                          "" "" "")))
    (org-sec-with-view "TODO dowith" who)
    (org-sec-assigned-with-view "TASK with" who)
    (org-sec-stuck-with-view "STUCK with" who)))
Juan Reyero Barcelona, 2009-11-01
 

blog comments powered by Disqus