mirror of
https://github.com/correl/jira-api.git
synced 2024-11-23 11:09:54 +00:00
Initial commit
This commit is contained in:
commit
3353a7c082
2 changed files with 209 additions and 0 deletions
10
Cask
Normal file
10
Cask
Normal file
|
@ -0,0 +1,10 @@
|
|||
;; -*- mode: emacs-lisp -*-
|
||||
(source melpa)
|
||||
|
||||
(package "jira-api" "0.0.1" "JIRA REST API")
|
||||
|
||||
(depends-on "dash")
|
||||
|
||||
(development
|
||||
(depends-on "f")
|
||||
(depends-on "ert-runner"))
|
199
jira-api.el
Normal file
199
jira-api.el
Normal file
|
@ -0,0 +1,199 @@
|
|||
;;; 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
|
Loading…
Reference in a new issue