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.
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"))))
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") ()))
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.
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")))))
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:
dowith property matches any of the people with me.
doat property matches my current location.
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.
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:
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:
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.
There are three kinds of meta-data I want to be able to store quickly:
dowith property that marks a TODO entry as intended to do when meeting with somebody.
doat property that marks a TODO entry as intended to do when at a given place.
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)
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)))
blog comments powered by Disqus