mirror of
https://github.com/correl/jira-api.git
synced 2024-11-23 19:19:51 +00:00
200 lines
7 KiB
EmacsLisp
200 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
|