2022-07-11 A poor man's Emacs - Jira integration

I use Jira at work. (I know, I know. Actually it’s not that bad – at least it’s much better than Asana.) Of course, I also copy all my tasks to Emacs to clock them using Org-mode. A few days ago a teammate, seeing my Org file with all the Jira tasks, asked me how I pull them from Jira to Org. A bit embarassed, I told him that I’ve been just creating a new headline, typing the task id (like LT-1337) and copying the task title from Jira. This is not as bad as it sounds since it happen very rarely – say, once or twice per day, so automating this doesn’t really pay off. Still, it would be nice.

So, I set out to do exactly that. There are a few Emacs-Jira integrations in existence: jira.el (which is rather simplistic – not that it’s a bad thing! – but not Org-aware), org-jira and Ejira (both of these are pretty big and opinionated). Since I do not want to change how I use Emacs (and Jira) with my tasks, org-jira and Ejira are probably not useful to me, and since I want something small and quick to put together, I decided not to use jira.el. What I do use is jira-terminal, which is a very simple Jira CLI client. It only allows to perform a few simple things, but it’s perfectly enough for my needs.

For example, this simple shell script will fetch the task summary and output the id, summary and a link, given its number.

#!/bin/bash
PREFIX=LT
NUMBER=$1
KEY=$PREFIX-$NUMBER
NAMESPACE=leet.atlassian.net
SUMMARY=$(jira-terminal detail -f summary "$KEY")
LINK="https://$NAMESPACE/browse/$KEY"
echo -e "$KEY $SUMMARY\n$LINK"

Now let’s make a similar thing available from Elisp.

(defun trim-trailing-whitespace (string)
  "Trim trailing whitespace from STRING."
  (when (string-match "[\n\\s-]*$" string)
      (replace-match "" nil nil string)))

(defvar jira-prefix "LT"
  "Jira task prefix for `jira-number-to-summary'.")

(defvar jira-domain "https://leet.atlassian.net"
  "Jira domain for `jira-number-to-summary'.")

(defun jira-number-to-summary (number &optional interactive)
  "Given Jira task NUMBER, insert its id, summary and link.
When called from Lisp, return the task summary."
  (interactive
   (list
    (if current-prefix-arg (prefix-numeric-value current-prefix-arg)
      (read-number "Task number: "))
    (prefix-numeric-value current-prefix-arg)))
  (let* ((key (format "%s-%s" jira-prefix number))
	 (summary
	  (trim-trailing-whitespace
	   (with-temp-buffer
	     (unless
		 (zerop
		  (call-process "jira-terminal" nil t nil
				"detail" "-f" "summary" key))
	       (error "Could not retrieve task data from Jira"))
	     (buffer-substring-no-properties (point-min) (point-max)))))
	 (link (format "%s/browse/%s" jira-domain key)))
    (if interactive
	(insert (format "%s %s\n%s\n" key summary link))
      summary)))

The code is pretty self-explanatory. If the jira-number-to-summary is called interactively, it will ask for a number (or use the prefix argument if provided) and insert the task id, task summary and the link (in the next line). The idea is that you can call this command on an empty Org headline. If called from Elisp (without the interactive argument), it will just return the summary.

One drawback is that it treats prefix arguments like C-u, C-u C-u etc. as numbers equal to powers of four, which is probably not optimal. If I wanted to prevent that (and use these prefixes to modify the operation of the command in some other way), I could employ the trick goto-line uses (look up the goto-line-read-args function in Emacs sources to see how its done – it’s pretty complex, but basically it only uses (prefix-numeric-value current-prefix-arg) as the numeric argument if (consp current-prefix-arg) is false).

That’s it for today. Happy coding!

CategoryEnglish, CategoryBlog, CategoryEmacs