~plattfot/issue.el

List issues from various issue trackers in emacs
Bump up patch: 1.1.0 -> 1.1.1
Allow sort by priority
Bump up minor: 1.0.0 -> 1.1.0

clone

read-only
https://git.sr.ht/~plattfot/issue.el
read/write
git@git.sr.ht:~plattfot/issue.el

You can also use your local clone with git send-email.

#Issue.el

List issues from various issue trackers in a tabulated buffer in Emacs and act on them.

It is based on Tabulated List mode. The scope of this is to give a quick view of issues from various issue trackers. And do some basic actions on them.

This is the core package and does not come with any issue tracker backend. See the project page for backends.

#Currently supported actions

  • Open marked issues in the browser

#Setup

In your init file you need to add the backends you want to the issue-backends variable. See the Create backend section for more details.

#Usage

In emacs run M-x list-issues and it should open a tabulated buffer, listing all the issues from the various issue tracker backends that are enabled.

#Keybindings

The basic keybindings are as follows:

key action
m Mark issue that the cursor is on.
M Mark all issues.
u Unmark issue that the cursor.
U Unmark all issues.
b Open marked issue(s) in browser
v Bring up view menu

Mark (m) or unmark (u) will move the cursor to the next line. Making it easier to mark/unmark multiple.

If no issue is marked, an action such as open in browser (b) will use the issue the cursor is on.

All keybindings can be viewed by pressing ? or h.

#Create backend {#create-backend}

This section describes the steps needed to create a backend for issue.el. The steps will use a fictious issue tracker called foo for the examples to hopefully make the steps a bit easier to understand.

#Struct

Issue.el is using generic functions to be abled to dispatch an action correctly per backend. More on this in the actions section.

To be able to do that, each backend need to define their own issue struct that inherit from the issue struct defined in issue.el.

(cl-defstruct (issue-foo (:include issue))
  "Structure containing information about an individual foo issue.")

The issue struct contains metadata about each issue (e.g. id, title and status of the issue) and it is this data that gets rendered in the tabulated list buffer. The issue struct defines all the slots that it issue.el needs for this. Extra data can be added to the child struct which can be accessed in the specialized action functions.

#Backend function

For issue.el to know about the issues from an issue tracker, the backend needs to add the issues to the tabulated-list-entries list.

For that issue.el calls the functions issue-backends to populate the list. The format of issue-backends is an alist where the key is a symbol that identifies the backend and the value is the function to call to populate tabulated-list-entries and call tabulated-list-printer when done. For now the function does not take any arguments, but in the future this will most likely change to allow for filtering of issues.

E.g for foo this will look something like:

;;;###autoload
(defun issue-foo-process-issues (&optional filter)
  "Return a list of `issue-foo`.

Where FILTER is the query to get the issues. Default is to get
all unresolved issues assigned to the current login user."
  (issue-update-tabulated-list-and-print
   (seq-map
    #'issue-foo--convert-to-issue-foo
    (issue-foo--query (or filter "magic query")))))

(defun issue-foo--convert-to-issue-foo (raw-issue)
  "Convert RAW-ISSUE to `issue-foo'.

Where RAW-ISSUE is expected to be an alist containing one
issue from the issues list in the data `issue-foo--query'
generates."
  (let ((fields (alist-get 'fields raw-issue)))
    (make-issue-foo :id (alist-get 'key raw-issue)
                    :title (alist-get 'title fields)
                    :priority (alist-get 'priority fields)
                    :status (alist-get 'status fields)
                    :type (alist-get 'issuetype fields)
                    :project (alist-get 'project fields)
                    :tools (alist-get 'tools fields)
                    :assignee (alist-get 'assignee fields))))

(defun issue-foo--query (query)
  "Call the foo issue tracker with QUERY.

Return a list containing the issues matching the query. Where
each entry is an alist with the data associated with an issue."
  ;; …
)

The format for tabulated-list-entries is a bit special as it needs to know what slot goes to what column. It is therefore recommended to use the helper function issue-update-tabulnated-list-and-print from issue.el to append the issue list to tabulated-list-entries and print.

The user is then expected to add foo to issue-backends like so:

(setf (alist-get 'foo issue-backends) #'issue-foo-process-issues)

#Actions {#actions}

Actions are generic functions that are applied to a subset of the issues listed in the tabulated list buffer. Namely the issues that are marked or if no issue is marked the issue the cursor is on.

They need to be specialized on the issue struct the backend defines for issue.el, for them to work properly for the issues the backend supports.

#issue-open-in-browser

This action should open the issue in the browser.

For foo this would be:

(cl-defmethod issue-open-in-browser ((issue issue-foo))
  "Open foo ISSUE in the browser."
  (browse-url (concat "https://foo/browse/" (issue-foo-id issue))))

#Known bugs

If your local clone of this repo is named issue.el, running guix build -f guix.scm will not work. Workaround is to rename it to e.g. issue-el. Reason for it is that when the directory is named issue.el, guix will only copy the file issue.el when building and testing the repo. Which causes ert-runner to error out as the test directory does not exist.