jira-api/jira-api.el
2016-04-04 14:15:47 -04:00

199 lines
7 KiB
EmacsLisp

;;; JIRA-API -- JIRA REST API
;; Copyright (c) 2015 Correl Roush
;; Author: Correl Roush <correl@gmail.com>
;; Version: 0.1
;; Created: 2015-06-18
;; This file is NOT part of GNU Emacs.
;;; License:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;; Code:
(require 'dash)
(require 'url)
(require 'json)
(defcustom jira-api-host "jira.company.com"
"JIRA Hostname"
:group 'jira-api
:type 'string)
(defcustom jira-api-user ""
"JIRA username"
:group 'jira-api
:type 'string)
(defcustom jira-api-use-ssl t
"Use HTTPS"
:group 'jira-api
:type 'boolean)
(defcustom jira-api-org-story-points-field "StoryPoints"
"Org story points property"
:group 'jira-api
:type 'string)
(defcustom jira-api-org-time-estimate-field "Effort"
"Org time estimate field"
:group 'jira-api
:type 'string)
(defvar jira-api-agile-sprint-field 'customfield_10300
"JIRA Agile sprint field")
(defvar jira-api-agile-story-points-field 'customfield_10003
"JIRA Agile story points field")
(defconst jira-api-timetracking-units
'(("m" . 60) ; 60 minutes in an hour
("h" . 8) ; 8 hours in a day
("d" . 5) ; 5 days in a week
("w" . nil)))
(defun jira-api-seconds-to-duration (seconds)
(let ((minutes (ceiling seconds 60)))
(mapconcat (lambda (pair) (format "%d%s" (car pair) (cdr pair)))
(--filter (not (zerop (car it)))
(-zip
(mapcar #'cdr (--reduce-from
(let ((remainder (or (caar acc) minutes)))
(cons (cons (floor remainder it)
(if it
(% remainder it)
remainder))
acc))
(list)
(mapcar #'cdr jira-api-timetracking-units)))
(reverse (mapcar #'car jira-api-timetracking-units))))
" ")))
(defun jira-api--get-credentials ()
(let ((info (nth 0 (auth-source-search :host jira-api-host
:port (if jira-api-use-ssl 443 80)
:require '(:user :secret)
:create t))))
(if info
(let ((user (plist-get info :user))
(secret (plist-get info :secret)))
(cons user
(if (functionp secret)
(funcall secret)
secret))))))
(defun jira-api-post (endpoint &optional data)
(let ((url-request-method "POST"))
(jira-api-get endpoint data)))
(defun jira-api-get (endpoint &optional data)
(let* ((url-request-method (or url-request-method "GET"))
(url-request-data
(json-encode data))
(credentials (jira-api--get-credentials))
(jira-api-user (car credentials))
(jira-api-password (cdr credentials))
(url-request-extra-headers
`(("Authorization" . ,(concat "Basic "
(base64-encode-string (concat jira-api-user ":" jira-api-password))))
("Content-Type" . "application/json")))
(protocol (if jira-api-use-ssl "https" "http"))
(result-buffer (url-retrieve-synchronously (concat protocol "://" jira-api-host endpoint))))
(when result-buffer
(with-current-buffer result-buffer
(goto-char (point-min))
(while (not (looking-at "^$"))
(forward-line))
(let ((json-object-type 'alist)
(json-array-type 'list)
(json-key-type 'symbol))
(json-read))))))
(defun jira-api-get-issue (issue-id)
(jira-api-get (concat "/rest/api/latest/issue/"
issue-id
"?expand=names")))
(defun jira-api-get-attribute (issue &rest names)
(-reduce-from (lambda (acc value)
(cdr (assoc value acc)))
issue
names))
(defun jira-api-parse-sprint-attribute (attribute-array)
(let* ((attribute-string (elt attribute-array 0))
(sprint-matches (if (s-starts-with? "[" attribute-string)
(cdr (s-match-strings-all "\\[\\(.*?\\)\\]" attribute-string))
(s-match-strings-all "\\[\\(.*?\\)\\]" attribute-string)))
(sprint-strings (mapcar #'cadr sprint-matches))
(sprint-attributes (-tree-map (lambda (s) (let* ((pair (split-string s "=")))
(cons (intern (car pair)) (cadr pair))))
(-map (lambda (s) (split-string s ",")) sprint-strings))))
sprint-attributes))
(defun jira-api-get-issue-sprints (issue)
(jira-api-parse-sprint-attribute
(jira-api-get-attribute issue
'fields jira-api-agile-sprint-field)))
(defun jira-api-agile-get-sprints (board-id)
(jira-api-get (format "/rest/greenhopper/experimental-api/latest/board/%d/sprint"
board-id)))
(defun jira-api-get-issue-story-points (issue)
(round
(jira-api-get-attribute issue
'fields jira-api-agile-story-points-field)))
(defun jira-api-get-issue-original-estimate (issue)
(let ((seconds (jira-api-get-attribute issue
'fields 'timetracking 'originalEstimateSeconds)))
(ceiling (/ seconds 60))))
(defun jira-api-get-worklog (issue-id)
(jira-api-get (concat "/rest/api/latest/issue/"
issue-id
"/worklog")))
(defun jira-api-org-update-entry ()
(let* ((jira-id (org-entry-get (point) "JIRA_ID"))
(jira-issue (jira-api-get-issue jira-id)))
(org-set-property
"StoryPoints"
(number-to-string (jira-api-get-issue-story-points jira-issue)))
(org-set-property
"Effort"
(let ((jira-estimate
(jira-api-get-issue-original-estimate jira-issue)))
(format "%d:%02d"
(floor (/ jira-estimate 60))
(% jira-estimate 60))))))
(defun jira-api-log-work (issue-id seconds)
(jira-api-post
(concat "/rest/api/latest/issue/"
issue-id
"/worklog")
`((timeSpentSeconds . ,seconds))))
(provide 'org-jira-rest)
;;; jira-api.el ends here