]> git.bundesbrandschatzamt.de Git - emacsconfig.git/commitdiff
updae org-jira
authorAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Sun, 22 Dec 2024 12:27:50 +0000 (13:27 +0100)
committerAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Sun, 22 Dec 2024 12:53:59 +0000 (13:53 +0100)
Signed-off-by: Andreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
elpa/org-jira-20230915.57/jiralib.el [deleted file]
elpa/org-jira-20230915.57/org-jira-autoloads.el [deleted file]
elpa/org-jira-20230915.57/org-jira-pkg.el [deleted file]
elpa/org-jira-20230915.57/org-jira-sdk.el [deleted file]
elpa/org-jira-20230915.57/org-jira.el [deleted file]
elpa/org-jira-20240928.2340/jiralib.el [new file with mode: 0644]
elpa/org-jira-20240928.2340/org-jira-autoloads.el [new file with mode: 0644]
elpa/org-jira-20240928.2340/org-jira-pkg.el [new file with mode: 0644]
elpa/org-jira-20240928.2340/org-jira-sdk.el [new file with mode: 0644]
elpa/org-jira-20240928.2340/org-jira.el [new file with mode: 0644]

diff --git a/elpa/org-jira-20230915.57/jiralib.el b/elpa/org-jira-20230915.57/jiralib.el
deleted file mode 100644 (file)
index 4c1907b..0000000
+++ /dev/null
@@ -1,1332 +0,0 @@
-;;; jiralib.el -- Provide connectivity to JIRA SOAP/REST services.
-
-;; Copyright (C) 2016-2022 Matthew Carter <mNOSPAMahungry.com>
-;; Copyright (C) 2011 Bao Haojun
-;; original Copyright (C) 2009 Alex Harsanyi
-
-;; Also, used some code from jira.el, which use xml-rpc instead of soap.
-;; Thus Copyright (C) for jira.el related code:
-;; Brian Zwahr <echosaNOSPAMgmail.com>
-;; Dave Benjamin <daveNOSPAMramenlabs.com>
-
-;; Authors:
-;; Matthew Carter <mNOSPAMahungry.com>
-;; Bao Haojun <baohaojunNOSPAMgmail.com>
-;; Alex Harsanyi <AlexHarsanyiNOSPAMgmail.com>
-
-;; Maintainer: Matthew Carter <mNOSPAMahungry.com>
-;; Version: 3.0.0
-;; Homepage: https://github.com/ahungry/org-jira
-
-;; This file is not part of GNU Emacs.
-
-;; 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 of the License, 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 this program.  If not, see
-;; <http://www.gnu.org/licenses/> or write to the Free Software
-;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-;; 02110-1301, USA.
-
-;; Author: Alexandru Harsanyi (AlexHarsanyiNOSPAMgmail.com)
-;; Created: December, 2009
-;; Keywords: soap, web-services, jira
-;; Homepage: http://code.google.com/p/emacs-soap-client
-
-;;; Commentary:
-
-;; This file provides a programmatic interface to JIRA.  It provides access to
-;; JIRA from other programs, but no user level functionality.
-
-;; Jira References:
-
-;; Primary reference (on current Jira, only REST is supported):
-;; https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis
-
-;; Full API list reference:
-;; https://docs.atlassian.com/jira/REST/cloud/
-
-;; Legacy reference (unsupported and deprecated/unavailable):
-;; http://confluence.atlassian.com/display/JIRA/Creating+a+SOAP+Client
-
-;; JavaDoc for the Jira SOAP service
-;; http://docs.atlassian.com/software/jira/docs/api/rpc-jira-plugin/latest/com/atlassian/jira/rpc/soap/JiraSoapService.html
-
-;;; News:
-
-;;;; Changes since 2.6.3:
-;; - Add worklog import filter and control variable for external worklogs.
-;; - Add the worklog related endpoint/calls.
-
-;;;; Changes since 2.1.0:
-;; - Remove os_username / os_password manual http request as part of sign in process
-;;     This produces sysadmin level warnings on Jira when these are used under the latest Jira.
-;; - Remove unused function jiralib-link-issue
-;; - Bring version up to match org-jira version so they can share tag
-
-;;;; Changes since 2.0.0:
-;; - Allow issue type query by project
-
-;;;; Changes since 0.0.0:
-;; - Converted many calls to async
-;; - Converted many calls to make use of caching
-
-;;; Code:
-
-(eval-when-compile (require 'cl))
-(require 'cl-seq)
-(require 'soap-client)
-(require 'request)
-(require 'json)
-(require 'url-parse)
-(require 'url-util)
-
-(defconst jiralib-version "3.0.0"
-  "Current version of jiralib.el.")
-
-(defgroup jiralib nil
-  "Jiralib customization group."
-  :group 'applications)
-
-(defgroup jiralib-faces nil
-  "Faces for displaying Jiralib information."
-  :group 'jiralib)
-
-(defcustom jiralib-use-restapi t
-  "Use restapi instead of soap."
-  :group 'jiralib
-  :type 'boolean
-  :initialize 'custom-initialize-set)
-
-(defcustom jiralib-coding-system 'utf-8
-  "Use custom coding system for Jiralib."
-  :group 'jiralib)
-
-(defcustom jiralib-host ""
-  "User customizable host name of the Jiralib server.
-
-This will be used with USERNAME to compute password from
-.authinfo file.  Will be calculated from jiralib-url if not set."
-  :group 'jiralib
-  :type 'string
-  :initialize 'custom-initialize-set)
-
-(defcustom jiralib-user ""
-  "User customizable user name of the Jiralib server.
-
-This will be used with USERNAME to compute password from
-.authinfo file.  Will be calculated from jiralib-url if not set."
-  :group 'jiralib
-  :type 'string
-  :initialize 'custom-initialize-set)
-
-(defface jiralib-issue-info-face
-  '((t (:foreground "black" :background "yellow4")))
-  "Base face for issue information."
-  :group 'jiralib-faces)
-
-(defface jiralib-issue-info-header-face
-  '((t (:bold t :inherit 'jiralib-issue-info-face)))
-  "Base face for issue headers."
-  :group 'jiralib-faces)
-
-(defface jiralib-issue-summary-face
-  '((t (:bold t)))
-  "Base face for issue summary."
-  :group 'jiralib-faces)
-
-(defface jiralib-comment-face
-  '((t (:background "gray23")))
-  "Base face for comments."
-  :group 'jiralib-faces)
-
-(defface jiralib-comment-header-face
-  '((t (:bold t)))
-  "Base face for comment headers."
-  :group 'jiralib-faces)
-
-(defface jiralib-link-issue-face
-  '((t (:underline t)))
-  "Face for linked issues."
-  :group 'jiralib-faces)
-
-(defface jiralib-link-project-face
-  '((t (:underline t)))
-  "Face for linked projects"
-  :group 'jiralib-faces)
-
-(defface jiralib-link-filter-face
-  '((t (:underline t)))
-  "Face for linked filters"
-  :group 'jiralib-faces)
-
-(defvar jiralib-mode-hook nil)
-(defvar jiralib-mode-map nil)
-(defvar jiralib-issue-regexp "\\<\\(?:[A-Za-z0-9]+\\)-[0-9]+\\>")
-
-(defcustom jiralib-wsdl-descriptor-url
-  ""
-  "The location for the WSDL descriptor for the JIRA service.
-This is specific to your local JIRA installation.  The URL is
-typically:
-
-  http://YOUR_INSTALLATION/rpc/soap/jirasoapservice-v2?wsdl
-
-The default value works if JIRA is located at a hostname named
-'jira'."
-  :type 'string
-  :group 'jiralib)
-
-(defcustom jiralib-url
-  "http://localhost:8081/"
-  "The address of the jira host."
-  :type 'string
-  :group 'jiralib)
-
-(defcustom jiralib-agile-page-size
-  50
-  "Page size for agile API retrieve. Limited by server property jira.search.views.default.max"
-  :type 'integer
-  :group 'jiralib)
-
-(defvar jiralib-token nil
-  "JIRA token used for authentication.")
-
-(defvar jiralib-user-login-name nil
-  "The name of the user logged into JIRA.
-This is maintained by `jiralib-login'.")
-
-(defvar jiralib-wsdl nil)
-
-(defcustom jiralib-worklog-import--filters-alist
-  (list
-   '(nil "WorklogUpdatedByCurrentUser"
-         (lambda (wl)
-           (let-alist wl
-             (when
-                 (and wl
-                      (string-equal
-                       (downcase
-                        (or jiralib-user-login-name user-login-name ""))
-                       (downcase (or .updateAuthor.name
-                                     (car (split-string (or .updateAuthor.emailAddress "") "@"))
-                                     ""))))
-               wl))))
-   '(nil "WorklogAuthoredByCurrentUser"
-         (lambda (wl)
-           (let-alist wl
-             (when
-                 (and wl
-                      (string-equal
-                       (downcase
-                        (or jiralib-user-login-name user-login-name))
-                       (downcase (or .author.name
-                                     (car (split-string (or .author.emailAddress "") "@"))))))
-               wl)))))
-  "A list of triplets: ('Global-Enable 'Descriptive-Label 'Function-Definition)
-that apply worklog predicate filters during import.
-
-Example: (list '('t \"descriptive-predicate-label\" (lambda (x) x)))"
-  :type '(repeat (list boolean string function))
-  :group 'org-jira)
-
-
-(defcustom jiralib-update-issue-fields-exclude-list nil
-  "A list of symbols to check for exclusion on updates based on matching key.
-Key names should be one of components, description, assignee, reporter, summary, issuetype."
-  :type '(set (const :tag "Exclude components" components)
-              (const :tag "Exclude description" description)
-              (const :tag "Exclude assignee" assignee)
-              (const :tag "Exclude reporter" reporter)
-              (const :tag "Exclude summary" summary)
-              (const :tag "Exclude priority" priority)
-              (const :tag "Exclude issue type" issuetype))
-  :group 'org-jira)
-
-(defun jiralib-load-wsdl ()
-  "Load the JIRA WSDL descriptor."
-  (setq jiralib-wsdl (soap-load-wsdl-from-url (if (string-equal jiralib-wsdl-descriptor-url "")
-                                                  (concat jiralib-url "/rpc/soap/jirasoapservice-v2?wsdl")
-                                                jiralib-wsdl-descriptor-url))))
-
-(defun jiralib-login (username password)
-  "Login into JIRA as user USERNAME with PASSWORD.
-
-After a successful login, store the authentication token in
-`jiralib-token'."
-  ;; NOTE that we cannot rely on `jiralib-call' because `jiralib-call' relies on
-  ;; us ;-)
-  (interactive
-   (if (> 24 emacs-major-version)
-       (let ((user (read-string "Username for Jira server login? "))
-             (password (read-passwd "Password for Jira server login? ")))
-         (list user password))
-     (let ((found (nth 0 (auth-source-search :max 1
-                                             :host (if (string= jiralib-host "")
-                                                       (url-host (url-generic-parse-url jiralib-url))
-                                                     jiralib-host)
-                                             :user (if (string= jiralib-user "")
-                                                       (url-user (url-generic-parse-url jiralib-url))
-                                                     jiralib-user)
-                                             ;; secrets.el wouldn’t accept a number.
-                                             :port (list (number-to-string (url-port (url-generic-parse-url jiralib-url)))
-                                                         (url-type (url-generic-parse-url jiralib-url)))
-                                             :require '(:user :secret)
-                                             :create t)))
-           user secret)
-       (when found
-         (setq user (plist-get found :user)
-               secret
-               (let ((sec (plist-get found :secret)))
-                 (if (functionp sec)
-                     (funcall sec)
-                   sec)))
-         (list user secret)))))
-  (if jiralib-use-restapi
-      (setq jiralib-token `("Authorization" . , (format "Basic %s" (base64-encode-string (concat username ":" password) t))))
-    (unless jiralib-wsdl
-      (jiralib-load-wsdl))
-    (setq jiralib-token
-          (car (soap-invoke jiralib-wsdl "jirasoapservice-v2" "login" username password))))
-    (setq jiralib-user-login-name username))
-
-(defvar jiralib-complete-callback nil)
-
-(defun jiralib-call (method callback &rest params)
-  "Invoke the Jira METHOD, then CALLBACK with supplied PARAMS.
-
-This function should be used for all JIRA interface calls, as the
-method ensures the user is logged in and invokes `soap-invoke'
-with the correct service name and authentication token.
-
-All JIRA interface methods take an authentication token as the
-first argument.  The authentication token is supplied by this
-function, so PARAMS should omit this parameter.  For example, the
-\"getIssue\" method takes two parameters: auth and key, however,
-when invoking it through `jiralib-call', the call should be:
-
-  (jiralib-call \"getIssue\" KEY)
-
-CALLBACK should be the post processing function to run with the
-completed data from the request result, which can be accessed with:
-
-  (cl-getf data :data)
-
-as such, the CALLBACK should follow this type of form:
-
-  (cl-function
-    (lambda (&rest data &allow-other-keys)
-      (print (cl-getf data :data))))
-
-If CALLBACK is set to nil then the request will occur with sync.
-This produces a noticeable slowdown and is not recommended by
-request.el, so if at all possible, it should be avoided."
-  ;; @TODO :auth: Probably pass this all the way down, but I think
-  ;; it may be OK at the moment to just set the variable each time.
-  
-  (setq jiralib-complete-callback
-        ;; Don't run with async if we don't have a login token yet.
-        (if jiralib-token callback nil))
-
-  ;; If we don't have a regex set, ensure it is set BEFORE any async
-  ;; calls are processing, or we're going to have a bad time.
-  ;; This should only end up running once per session.
-  (unless jiralib-issue-regexp
-    (let ((projects (mapcar (lambda (e) (downcase (cdr (assoc 'key e))))
-                            (append (jiralib--rest-call-it
-                                     "/rest/api/2/project"
-                                     :params '((expand . "description,lead,url,projectKeys")))
-                                    nil)
-                            )))
-      (when projects
-        (setq jiralib-issue-regexp
-              (concat "\\<" (regexp-opt projects) "-[0-9]+\\>")))))
-
-  (if (not jiralib-use-restapi)
-      (car (apply 'jiralib--call-it method params))
-    (unless jiralib-token
-      (call-interactively 'jiralib-login))
-    (cl-case (intern method)
-      ('getStatuses (jiralib--rest-call-it "/rest/api/2/status"))
-      ('getIssueTypes (jiralib--rest-call-it "/rest/api/2/issuetype"))
-      ('getSubTaskIssueTypes (jiralib--rest-call-it "/rest/api/2/issuetype"))
-      ('getIssueTypesByProject
-       (let ((response (jiralib--rest-call-it (format "/rest/api/2/project/%s" (first params)))))
-         (cl-coerce (cdr (assoc 'issueTypes response)) 'list)))
-      ('getUser (jiralib--rest-call-it "/rest/api/2/user" :params `((accountId . ,(first params)))))
-      ('getVersions (jiralib--rest-call-it (format "/rest/api/2/project/%s/versions" (first params))))
-
-      ;; Worklog calls
-      ('getWorklogs
-       (jiralib--rest-call-it (format "/rest/api/2/issue/%s/worklog" (first params))))
-
-      ('addWorklog
-       (jiralib--rest-call-it (format "/rest/api/2/issue/%s/worklog" (first params))
-                              :type "POST"
-                              :data (json-encode (second params))))
-
-      ('updateWorklog
-       (jiralib--rest-call-it (format "/rest/api/2/issue/%s/worklog/%s" (first params) (second params))
-                              :type "PUT"
-                              :data (json-encode (third params))))
-
-      ('addWorklogAndAutoAdjustRemainingEstimate
-       (jiralib--rest-call-it (format "/rest/api/2/issue/%s/worklog" (first params))
-                              :type "POST"
-                              :data (json-encode (second params))))
-
-      ('addComment (jiralib--rest-call-it
-                    (format "/rest/api/2/issue/%s/comment" (first params))
-                    :type "POST"
-                    :data (json-encode (second params))))
-      ('createIssue
-       ;; Creating the issue doesn't return it, a second call must be
-       ;; made to pull it in by using the self key in response.
-       (let ((response (jiralib--rest-call-it
-                        "/rest/api/2/issue"
-                        :type "POST"
-                        :data (json-encode (first params)))))
-         (jiralib--rest-call-it (cdr (assoc 'self response)) :type "GET")
-         ))
-      ('createIssueWithParent
-       (let ((response (jiralib--rest-call-it
-                        "/rest/api/2/issue"
-                        :type "POST"
-                        :data (json-encode (first params)))))
-         (jiralib--rest-call-it (cdr (assoc 'self response)) :type "GET")
-         ))
-      ('editComment (jiralib--rest-call-it
-                     (format "/rest/api/2/issue/%s/comment/%s" (first params) (second params))
-                     :data (json-encode `((body . ,(third params))))
-                     :type "PUT"))
-      ('getBoard  (jiralib--rest-call-it (format "/rest/agile/1.0/board/%s"  (first params))))
-      ('getBoards (apply 'jiralib--agile-call-it "/rest/agile/1.0/board" 'values params))
-      ('getComment (org-jira-find-value
-                     (jiralib--rest-call-it
-                      (format "/rest/api/2/issue/%s/comment/%s" (first params) (second params)))
-                     'comments))
-      ('getComments (org-jira-find-value
-                     (jiralib--rest-call-it
-                      (format "/rest/api/2/issue/%s/comment" (first params)))
-                     'comments))
-      ('getAttachmentsFromIssue (org-jira-find-value
-                                 (jiralib--rest-call-it
-                                  (format "/rest/api/2/issue/%s?fields=attachment" (first params)))
-                                 'comments))
-      ('getComponents (jiralib--rest-call-it
-                       (format "/rest/api/2/project/%s/components" (first params))))
-      ('getIssue (jiralib--rest-call-it
-                  (format "/rest/api/2/issue/%s" (first params))))
-      ('getIssuesFromBoard  (apply 'jiralib--agile-call-it
-                                  (format "rest/agile/1.0/board/%d/issue" (first params))
-                                  'issues
-                                  (cdr params)))
-      ('getSprintsFromBoard  (jiralib--rest-call-it (format "/rest/agile/1.0/board/%s/sprint"  (first params))))
-      ('getIssuesFromSprint  (apply 'jiralib--agile-call-it
-                                  (format "rest/agile/1.0/sprint/%d/issue" (first params))
-                                  'issues
-                                  (cdr params)))
-      ('getIssuesFromJqlSearch  (append (cdr ( assoc 'issues (jiralib--rest-call-it
-                                                              "/rest/api/2/search"
-                                                              :type "POST"
-                                                              :data (json-encode `((jql . ,(first params))
-                                                                                   (maxResults . ,(second params)))))))
-                                        nil))
-      ('getPriorities (jiralib--rest-call-it
-                       "/rest/api/2/priority"))
-      ('getProjects (jiralib--rest-call-it "rest/api/2/project"))
-      ('getProjectsNoSchemes (append (jiralib--rest-call-it
-                                      "/rest/api/2/project"
-                                      :params '((expand . "description,lead,url,projectKeys"))) nil))
-      ('getResolutions (append (jiralib--rest-call-it
-                                "/rest/api/2/resolution") nil))
-      ('getAvailableActions
-       (mapcar
-        (lambda (trans)
-          `(,(assoc 'name trans) ,(assoc 'id trans)))
-        (cdadr (jiralib--rest-call-it (format "/rest/api/2/issue/%s/transitions" (first params))))))
-      ('getFieldsForAction (org-jira-find-value (car (let ((issue (first params))
-                                                           (action (second params)))
-                                                       (seq-filter (lambda (trans)
-                                                                     (or (string-equal action (org-jira-find-value trans 'id))
-                                                                         (string-equal action (org-jira-find-value trans 'name))))
-                                                                   (cdadr (jiralib--rest-call-it
-                                                                           (format "/rest/api/2/issue/%s/transitions" (first params))
-                                                                           :params '((expand . "transitions.fields")))))))
-                                                'fields))
-      ('progressWorkflowAction (jiralib--rest-call-it
-                                (format "/rest/api/2/issue/%s/transitions" (first params))
-                                :type "POST"
-                                :data (json-encode `(,(car (second params)) ,(car (third params))))))
-      ('getUsers
-       (jiralib--rest-call-it (format "/rest/api/2/user/assignable/search?project=%s&maxResults=10000" (first params))
-                              :type "GET"))
-      ('updateIssue (jiralib--rest-call-it
-                     (format "/rest/api/2/issue/%s" (first params))
-                     :type "PUT"
-                     :data (json-encode `((fields . ,(second params)))))))))
-
-(defun jiralib--soap-call-it (&rest args)
-  "Deprecated SOAP call endpoint.  Will be removed soon.
-Pass ARGS to jiralib-call."
-  (let ((jiralib-token nil)
-        (jiralib-use-restapi nil))
-    (apply #'jiralib-call args)))
-
-(defun jiralib--json-read ()
-  "Read with json, force utf-8"
-  (decode-coding-region (point) (point-max) jiralib-coding-system)
-  (json-read))
-
-(defun jiralib--rest-call-it (api &rest args)
-  "Invoke the corresponding jira rest method API.
-Invoking COMPLETE-CALLBACK when the
-JIRALIB-COMPLETE-CALLBACK is non-nil, request finishes, and
-passing ARGS to REQUEST."
-  (unless api (error "jiralib--rest-call-it was called with a NIL api value."))
-  (setq args
-        (mapcar
-         (lambda (arg)
-           (if (stringp arg)
-               (encode-coding-string arg jiralib-coding-system)
-             arg))
-         args))
-  (append (request-response-data
-           (apply #'request (if (string-match "^http[s]*://" api) api ;; If an absolute path, use it
-                              (concat (replace-regexp-in-string "/*$" "/" jiralib-url)
-                                      (replace-regexp-in-string "^/*" "" api)))
-                  :sync (not jiralib-complete-callback)
-                  :headers `(,jiralib-token ("Content-Type" . "application/json"))
-                  :parser 'jiralib--json-read
-                  :complete jiralib-complete-callback
-                  ;; Ensure we have useful errors
-                  :error
-                  (lexical-let
-                      ((my-api api)
-                       (my-args args))
-                    (cl-function
-                       (lambda (&key data &allow-other-keys)
-                         (print "JIRA_ERROR - see your *Messages* buffer for more details.")
-                         (print "JIRA_ERROR REQUEST: ")
-                         (print my-api)
-                         (print my-args)
-                         (print "JIRA_ERROR RESPONSE: ")
-                         (print data)
-                         (error "JIRA_ERROR - see your *Messages* buffer for more details.")
-                         )))
-                  args))
-          nil))
-
-(defun jiralib--call-it (method &rest params)
-  "Invoke the JIRA METHOD with supplied PARAMS.
-
-Internal use, returns a list of responses, of which only the
-first is normally used."
-  (when (symbolp method)
-    (setq method (symbol-name method)))
-  (unless jiralib-token
-    (call-interactively 'jiralib-login))
-  (condition-case data
-      (apply 'soap-invoke jiralib-wsdl "jirasoapservice-v2"
-             method jiralib-token params)
-    (soap-error
-     ;; If we are here, we had a token, but it expired.  Re-login and try
-     ;; again.
-     (setq jiralib-token nil)
-     (call-interactively 'jiralib-login)
-     (apply 'soap-invoke jiralib-wsdl "jirasoapservice-v2"
-            method jiralib-token params))))
-
-\f
-;;;; Some utility functions
-
-(defun jiralib-make-list (data field)
-  "Map all assoc elements in DATA to the value of FIELD in that element."
-  (cl-loop for element in data
-        collect (cdr (assoc field element))))
-
-(defun jiralib-make-assoc-list (data key-field value-field)
-  "Create an association list from a SOAP structure array.
-
-DATA is a list of association lists (a SOAP array-of type)
-KEY-FIELD is the field to use as the key in the returned alist
-VALUE-FIELD is the field to use as the value in the returned alist"  
-  (cl-loop for element in data
-        collect (cons (cdr (assoc key-field element))
-                      (cdr (assoc value-field element)))))
-
-(defun jiralib-make-remote-field-values (fields)
-  "Transform the (KEY . VALUE) list FIELDS into a RemoteFieldValue structure.
-
-Each (KEY . VALUE) pair is transformed into
- ((id . KEY) (values . (VALUE)))
-
-This method exists because Several JIRA methods require a
-RemoteFieldValue list, but it is easier to work with ALISTS in
-emacs-lisp"
-  (let ((remote-field-values))
-
-    ;; we accept an ALIST of field-name field-values parameter, but we need to
-    ;; construct a structure that encodes as a RemoteFieldValue which is what
-    ;; updateIssue wants
-    (dolist (field fields)
-      (let ((name (car field))
-            (value (cdr field)))
-        (when (symbolp name)
-          (setq name (symbol-name name)))
-        ;; Value must be an "array" (for which soap-client accepts lists) even
-        ;; if it is just one value
-        (unless (vectorp value)
-          (setq value (vector value)))
-        (push `((id . ,name) (values . ,value))
-              remote-field-values)))
-
-    (apply 'vector (nreverse remote-field-values))))
-
-\f
-;;;; Wrappers around JIRA methods
-
-(defun jiralib--rest-api-for-issue-key (key)
-  "Return jira rest api for issue KEY."
-  (concat "rest/api/2/issue/" key))
-
-(defun jiralib-filter-fields-by-exclude-list (exclude-list fields)
-  (cl-remove-if
-   (lambda (el) (cl-member (car el) exclude-list)) fields))
-
-(defun jiralib-update-issue (key fields &optional callback)
-  "Update the issue with id KEY with the values in FIELDS, invoking CALLBACK."
-  (let ((filtered-fields (jiralib-filter-fields-by-exclude-list
-                          jiralib-update-issue-fields-exclude-list
-                          fields)))
-    (jiralib-call
-     "updateIssue"
-     callback
-     key (if jiralib-use-restapi
-             filtered-fields
-           (jiralib-make-remote-field-values filtered-fields)))))
-
-(defvar jiralib-status-codes-cache nil)
-
-(defun jiralib-get-statuses ()
-  "Return an assoc list mapping a status code to its name.
-NOTE: Status codes are stored as strings, not numbers.
-
-This function will only ask JIRA for the list of codes once, then
-will cache it."
-  (unless jiralib-status-codes-cache
-    (setq jiralib-status-codes-cache
-          (jiralib-make-assoc-list (jiralib-call "getStatuses" nil) 'id 'name)))
-  jiralib-status-codes-cache)
-
-(defvar jiralib-issue-types-cache nil)
-
-(defun jiralib-get-issue-types ()
-  "Return an assoc list mapping an issue type code to its name.
-NOTE: Issue type codes are stored as strings, not numbers.
-
-This function will only ask JIRA for the list of codes once, than
-will cache it.
-
-The issue types returned via getIssueTypes are all the ones
-available to the user, but not necessarily available to the given
-project.
-
-This endpoint is essentially a master reference for when issue
-types need a name lookup when given an id.
-
-For applying issue types to a given project that is being created, see
-the #'jiralib-get-issue-types-by-project call."
-  (unless jiralib-issue-types-cache
-    (setq jiralib-issue-types-cache
-          (jiralib-make-assoc-list (jiralib-call "getIssueTypes" nil) 'id 'name)))
-  jiralib-issue-types-cache)
-
-(defvar jiralib-issue-types-by-project-cache nil "An alist of available issue types.")
-
-(defun jiralib-get-issue-types-by-project (project)
-  "Return the available issue types for PROJECT.
-
-PROJECT should be the key, such as `EX' or `DEMO'."
-  (unless (assoc project jiralib-issue-types-by-project-cache)
-    (push (cons project
-                (jiralib-make-assoc-list
-                 (jiralib-call "getIssueTypesByProject" nil project)
-                 'id 'name))
-          jiralib-issue-types-by-project-cache))
-  (cdr (assoc project jiralib-issue-types-by-project-cache)))
-
-(defvar jiralib-priority-codes-cache nil)
-
-(defun jiralib-get-priorities ()
-  "Return an assoc list mapping a priority code to its name.
-NOTE: Priority codes are stored as strings, not numbers.
-
-This function will only ask JIRA for the list of codes once, than
-will cache it."
-  (unless jiralib-priority-codes-cache
-    (setq jiralib-priority-codes-cache
-          (jiralib-make-assoc-list (jiralib-call "getPriorities" nil) 'id 'name)))
-  jiralib-priority-codes-cache)
-
-(defvar jiralib-resolution-code-cache nil)
-
-(defun jiralib-get-resolutions ()
-  "Return an assoc list mapping a resolution code to its name.
-NOTE: Resolution codes are stored as strings, not numbers.
-
-This function will only ask JIRA for the list of codes once, than
-will cache it."
-  (unless jiralib-resolution-code-cache
-    (setq jiralib-resolution-code-cache
-          (jiralib-make-assoc-list (jiralib-call "getResolutions" nil) 'id 'name)))
-  jiralib-resolution-code-cache)
-
-;; NOTE: it is not such a good idea to use this, as it needs a JIRA
-;; connection to construct the regexp (the user might be prompted for a JIRA
-;; username and password).
-;;
-;; The best use of this function is to generate the regexp once-off and
-;; persist it somewhere.
-;;
-;; FIXME: Probably just deprecate/remove this, we can assert we're on
-;; an issue with a general regexp that matches the common format, vs
-;; needing to know specific user project list.
-(defun jiralib-get-issue-regexp ()
-  "Return a regexp that will match an issue id.
-
-The regexp is constructed from the project keys in the JIRA
-database.  An issue is assumed to be in the format KEY-NUMBER,
-where KEY is a project key and NUMBER is the issue number."
-  (unless jiralib-issue-regexp
-    (let ((projects (mapcar (lambda (e) (downcase (cdr (assoc 'key e))))
-                            (jiralib-call "getProjectsNoSchemes" nil))))
-      (when projects
-        (setq jiralib-issue-regexp
-              (concat "\\<" (regexp-opt projects) "-[0-9]+\\>")))))
-  jiralib-issue-regexp)
-
-(defun jiralib-do-jql-search (jql &optional limit callback)
-  "Run a JQL query and return the list of issues that matched.
-LIMIT is the maximum number of queries to return.  Note that JIRA
-has an internal limit of how many queries to return, as such, it
-might not be possible to find *ALL* the issues that match a
-query."
-  (unless (or limit (numberp limit))
-    (setq limit 100))
-  (jiralib-call "getIssuesFromJqlSearch" callback jql limit))
-
-(defcustom jiralib-available-actions-cache-p t
-  "Set to t to enable caching for jiralib-get-available-actions.
-
-If nil, will disable caching for this endpoint.
-
-Possible side-effects:
-
-  - If the server has the project workflow updated, the cache
-saved here will be incorrect.
-
-  - If the issue is not up to date with the remote, the wrong
-cache key may be queried."
-  :type 'boolean
-  :group 'jiralib)
-
-(defvar jiralib-available-actions-cache nil "An alist of available actions.")
-
-(defun jiralib-get-available-actions (issue-key &optional status)
-  "Return the available workflow actions for ISSUE-KEY.
-This uses STATUS as the cache key.
-This runs the getAvailableActions SOAP method."
-  (if (and jiralib-available-actions-cache-p status)
-      (progn
-        (unless (assoc status jiralib-available-actions-cache)
-          (push (cons status
-                      (jiralib-make-assoc-list
-                       (mapcar (lambda (x)
-                                 (let ((namestring (cdr (car x)))
-                                       (id (cdr x)))
-                                   (cons
-                                    (cons 'name (org-jira-decode namestring))
-                                    id)))
-                               (jiralib-call "getAvailableActions" nil issue-key))
-                       'id 'name))
-                jiralib-available-actions-cache))
-        (cdr (assoc status jiralib-available-actions-cache)))
-    (progn
-      (jiralib-make-assoc-list
-       (mapcar (lambda (x)
-                 (let ((namestring (cdr (car x)))
-                       (id (cdr x)))
-                   (cons
-                    (cons 'name (org-jira-decode namestring))
-                    id)))
-               (jiralib-call "getAvailableActions" nil issue-key))
-       'id 'name))))
-
-(defcustom jiralib-fields-for-action-cache-p t
-  "Set to t to enable caching for jiralib-get-fields-for-action.
-
-If nil, will disable caching for this endpoint.
-
-Possible side-effects:
-
-  - If many tasks have different workflows, you may want to disable this."
-  :type 'boolean
-  :group 'jiralib)
-
-(defvar jiralib-fields-for-action-cache nil "An alist of available fields.")
-
-(defun jiralib-get-fields-for-action-with-cache (issue-key action-id)
-  "Return the required fields to change ISSUE-KEY to ACTION-ID."
-  (if (and jiralib-fields-for-action-cache-p action-id)
-      (progn
-        (unless (assoc action-id jiralib-fields-for-action-cache)
-          (push (cons action-id
-                      (jiralib-call "getFieldsForAction" nil issue-key action-id))
-                jiralib-fields-for-action-cache))
-        (cdr (assoc action-id jiralib-fields-for-action-cache)))
-    (jiralib-call "getFieldsForAction" nil issue-key action-id)))
-
-(defun jiralib-get-fields-for-action (issue-key action-id)
-  "Return the required fields to change ISSUE-KEY to ACTION-ID."
-  (if jiralib-use-restapi
-      (let ((fields (jiralib-get-fields-for-action-with-cache issue-key action-id)))
-        (mapcar (lambda (field)
-                  (cons (symbol-name (car field))
-                        (format "%s (required: %s)"
-                                (org-jira-find-value field 'name)
-                                (if (eq (org-jira-find-value field 'required) :json-false)
-                                    "nil"
-                                  "t"))))
-                fields))
-    (jiralib-make-assoc-list
-     (jiralib-get-fields-for-action-with-cache issue-key action-id)
-     'id 'name)))
-
-(defun jiralib-progress-workflow-action (issue-key action-id params &optional callback)
-  "Progress issue with ISSUE-KEY to action ACTION-ID, and provide the needed PARAMS.
-
-When CALLBACK is present, this will run async."
-  (if jiralib-use-restapi
-      (jiralib-call "progressWorkflowAction"
-                    callback issue-key `((transition (id . ,action-id)))
-                    `((fields . ,params)))
-    (jiralib-call "progressWorkflowAction"
-                  callback issue-key action-id (jiralib-make-remote-field-values params))))
-
-
-(defun jiralib-format-datetime (&optional datetime)
-  "Convert a mixed DATETIME format into the Jira required datetime format.
-
-This will produce a datetime string such as:
-
-  2010-02-05T14:30:00.000+0000
-
-for being consumed in the Jira API.
-
-If DATETIME is not passed in, it will default to the current time."
-  (let* ((defaults (format-time-string "%Y-%m-%d %H:%M:%S" (current-time)))
-         (datetime (concat datetime (subseq defaults (length datetime))))
-         (parts (parse-time-string datetime)))
-    (format "%04d-%02d-%02dT%02d:%02d:%02d.000+0000"
-            (nth 5 parts)
-            (nth 4 parts)
-            (nth 3 parts)
-            (nth 2 parts)
-            (nth 1 parts)
-            (nth 0 parts))))
-
-(defvar jiralib-worklog-coming-soon-message
-  "WORKLOG FEATURES ARE NOT IMPLEMENTED YET, COMING SOON!")
-
-(defun jiralib-add-worklog-and-autoadjust-remaining-estimate (issue-key start-date time-spent comment)
-  "Log time spent on ISSUE-KEY to its worklog.
-The time worked begins at START-DATE and has a TIME-SPENT
-duration.  JIRA will automatically update the remaining estimate
-by subtracting TIME-SPENT from it.
-
-START-DATE should be in the format 2010-02-05T14:30:00Z
-
-TIME-SPENT can be in one of the following formats: 10m, 120m
-hours; 10h, 120h days; 10d, 120d weeks.
-
-COMMENT will be added to this worklog."
-  (let ((formatted-start-date (jiralib-format-datetime start-date)))
-    (jiralib-call "addWorklogAndAutoAdjustRemainingEstimate"
-                  nil
-                  issue-key
-                  ;; Expects data such as: '{"timeSpent":"1h", "started":"2017-02-21T00:00:00.000+0000", "comment":"woot!"}'
-                  ;; and only that format will work (no loose formatting on the started date)
-                  `((started   . ,formatted-start-date)
-                    (timeSpent . ,time-spent)
-                    (comment   . ,comment)))))
-
-\f
-;;;; Issue field accessors
-
-(defun jiralib-issue-key (issue)
-  "Return the key of ISSUE."
-  (cdr (assoc 'key issue)))
-
-(defun jiralib-issue-owner (issue)
-  "Return the owner of ISSUE."
-  (cdr (assq 'assignee issue)))
-
-(defun jiralib-issue-status (issue)
-  "Return the status of ISSUE as a status string (not as a number!)."
-  (let ((status-code (cdr (assq 'status issue))))
-    (cdr (assoc status-code (jiralib-get-statuses)))))
-
-(defun jiralib-custom-field-value (custom-field issue)
-  "Return the value of CUSTOM-FIELD for ISSUE.
-Return nil if the field is not found"
-  (catch 'found
-    (dolist (field (cdr (assq 'customFieldValues issue)))
-      (when (equal (cdr (assq 'customfieldId field)) custom-field)
-        (throw 'found (cadr (assq 'values field)))))))
-
-(defvar jiralib-current-issue nil
-  "This holds the currently selected issue.")
-
-(defvar jiralib-projects-list nil
-  "This holds a list of projects and their details.")
-
-(defvar jiralib-types nil
-  "This holds a list of issues types.")
-
-(defvar jiralib-priorities nil
-  "This holds a list of priorities.")
-
-(defvar jiralib-user-fullnames nil
-  "This holds a list of user fullnames.")
-
-(defun jiralib-get-project-name (key)
-  "Return the name of the JIRA project with id KEY."
-  (let ((projects jiralib-projects-list)
-        (name nil))
-    (dolist (project projects)
-      (if (equal (cdr (assoc 'key project)) key)
-          (setf name (cdr (assoc 'name project)))))
-    name))
-
-(defun jiralib-get-type-name (id)
-  "Return the name of the issue type with ID."
-  (let ((types jiralib-types)
-        (name nil))
-    (dolist (type types)
-      (if (equal (cdr (assoc 'id type)) id)
-          (setf name (cdr (assoc 'name type)))))
-    name))
-
-;; (defun jiralib-get-user-fullname (account-id)
-;;   "Return the full name (display name) of the user with USERNAME."
-  ;; (if (assoc account-id jiralib-user-fullnames)
-  ;;     (cdr (assoc account-id jiralib-user-fullnames))
-  ;;   (progn
-  ;;     (let ((user (jiralib-get-user account-id)))
-  ;;       (setf jiralib-user-fullnames (append jiralib-user-fullnames (list (cons account-id (cdr (assoc 'fullname user))))))
-  ;;       (cdr (assoc 'fullname user))))))
-
-(defun jiralib-get-user-fullname (account-id)
-  "Return the full name (displayName) of the user with ACCOUNT-ID."
-  (cl-loop for user in (jiralib-get-users nil)
-        when (rassoc account-id user)
-        return (cdr (assoc 'displayName user))))
-
-(defun jiralib-get-user-account-id (project full-name)
-    "Return the account-id (accountId) of the user with FULL-NAME (displayName) in PROJECT."
-  (cl-loop for user in (jiralib-get-users project)
-        when (rassoc full-name user)
-        return (cdr (assoc 'accountId user))))
-
-(defun jiralib-get-filter (filter-id)
-  "Return a filter given its FILTER-ID."
-  (cl-flet ((id-match (filter)
-                      (equal filter-id (cdr (assoc 'id filter)))))
-    (cl-find-if 'id-match (jiralib-get-saved-filters))))
-
-(defun jiralib-get-filter-alist ()
-  "Return an association list mapping filter names to IDs."
-  (mapcar (lambda (filter)
-            (cons (cdr (assoc 'name filter))
-                  (cdr (assoc 'id filter))))
-          (jiralib-get-saved-filters)))
-
-(defun jiralib-add-comment (issue-key comment &optional callback)
-  "Add to issue with ISSUE-KEY the given COMMENT, invoke CALLBACK."
-  (jiralib-call "addComment" callback issue-key `((body . ,comment))))
-
-(defun jiralib-edit-comment (issue-id comment-id comment &optional callback)
-  "Edit ISSUE-ID's comment COMMENT-ID to reflect the new COMMENT, invoke CALLBACK."
-  (if (not jiralib-use-restapi)
-      (jiralib-call "editComment" callback `((id . ,comment-id)
-                                             (body . ,comment)))
-    (jiralib-call "editComment" callback issue-id comment-id comment)))
-
-(defun jiralib-create-issue (issue)
-  "Create a new ISSUE in JIRALIB.
-
-ISSUE is a Hashtable object."
-  (jiralib-call "createIssue" nil issue))
-
-(defun jiralib-create-subtask (subtask)
-  "Create SUBTASK for issue with PARENT-ISSUE-ID.
-
-SUBTASK is a Hashtable object."
-  (jiralib-call "createIssueWithParent" nil subtask))
-
-(defvar jiralib-subtask-types-cache nil)
-
-(defun jiralib-get-subtask-types ()
-  "Return an assoc list mapping an issue type code to its name.
-NOTE: Issue type codes are stored as strings, not numbers.
-
-This function will only ask JIRA for the list of codes once, than
-will cache it."
-  (unless jiralib-subtask-types-cache
-    (setq jiralib-subtask-types-cache
-          (jiralib-make-assoc-list (jiralib-call "getSubTaskIssueTypes" nil) 'id 'name)))
-  jiralib-subtask-types-cache)
-
-(defun jiralib-get-comment (issue-key comment-id &optional callback)
-  "Return all comments associated with issue ISSUE-KEY, invoking CALLBACK."
-  (jiralib-call "getComment" callback issue-key comment-id))
-
-(defun jiralib-get-comments (issue-key &optional callback)
-  "Return all comments associated with issue ISSUE-KEY, invoking CALLBACK."
-  (jiralib-call "getComments" callback issue-key))
-
-(defun jiralib-get-attachments (issue-key &optional callback)
-  "Return all attachments associated with issue ISSUE-KEY, invoking CALLBACK."
-  (jiralib-call "getAttachmentsFromIssue" callback issue-key))
-
-(defun jiralib-get-worklogs (issue-key &optional callback)
-  "Return all worklogs associated with issue ISSUE-KEY, invoking CALLBACK."
-  (jiralib-call "getWorklogs" callback issue-key))
-
-(defun jiralib-add-worklog (issue-id started time-spent-seconds comment &optional callback)
-  "Add the worklog linked to ISSUE-ID.
-
-Requires STARTED (a jira datetime), TIME-SPENT-SECONDS (integer) and a COMMENT.
-CALLBACK will be invoked if passed in upon endpoint completion."
-  ;; Call will fail if 0 seconds are set as the time, so always do at least one min.
-  (setq time-spent-seconds (max 60 time-spent-seconds))
-  (let ((worklog `((started . ,started)
-                   ;; @TODO :worklog: timeSpentSeconds changes into incorrect values
-                   ;; in the Jira API (for instance, 89600 = 1 day, but Jira thinks 3 days...
-                   ;; We should convert to a Xd Xh Xm format from our seconds ourselves.
-                   (timeSpentSeconds . ,time-spent-seconds)
-                   (comment . ,comment))))
-    (jiralib-call "addWorklog" callback issue-id worklog)))
-
-(defun jiralib-update-worklog (issue-id worklog-id started time-spent-seconds comment &optional callback)
-  "Update the worklog linked to ISSUE-ID and WORKLOG-ID.
-
-Requires STARTED (a jira datetime), TIME-SPENT-SECONDS (integer) and a COMMENT.
-CALLBACK will be invoked if passed in upon endpoint completion."
-  ;; Call will fail if 0 seconds are set as the time, so always do at least one min.
-  (setq time-spent-seconds (max 60 time-spent-seconds))
-  (let ((worklog `((started . ,started)
-                   ;; @TODO :worklog: timeSpentSeconds changes into incorrect values
-                   ;; in the Jira API (for instance, 89600 = 1 day, but Jira thinks 3 days...
-                   ;; We should convert to a Xd Xh Xm format from our seconds ourselves.
-                   (timeSpentSeconds . ,time-spent-seconds)
-                   (comment . ,comment))))
-    (jiralib-call "updateWorklog" callback issue-id worklog-id worklog)))
-
-(defvar jiralib-components-cache nil "An alist of project components.")
-
-(defun jiralib-get-components (project-key)
-  "Return all components available in the project PROJECT-KEY."
-  (unless (assoc project-key jiralib-components-cache)
-    (push (cons project-key
-                (jiralib-make-assoc-list
-                 (jiralib-call "getComponents" nil project-key) 'id 'name))
-          jiralib-components-cache))
-  (cdr (assoc project-key jiralib-components-cache)))
-
-(defun jiralib-get-issue (issue-key &optional callback)
-  "Get the issue with key ISSUE-KEY, running CALLBACK after."
-  (jiralib-call "getIssue" callback issue-key))
-
-(defun jiralib-get-issues-from-filter (filter-id)
-  "Get the issues from applying saved filter FILTER-ID."
-  (message "jiralib-get-issues-from-filter is NOT IMPLEMENTED!!  Do not use!")
-  (jiralib-call "getIssuesFromFilter" nil filter-id))
-
-(defun jiralib-get-issues-from-text-search (search-terms)
-  "Find issues using free text search SEARCH-TERMS."
-  (jiralib-call "getIssuesFromTextSearch" nil search-terms))
-
-(defun jiralib-get-issues-from-text-search-with-project
-    (project-keys search-terms max-num-results)
-  "Find issues in projects PROJECT-KEYS, using free text search SEARCH-TERMS.
-
-Return no more than MAX-NUM-RESULTS."
-  (jiralib-call "getIssuesFromTextSearchWithProject"
-                nil
-                (apply 'vector project-keys) search-terms max-num-results))
-
-;; Modified by Brian Zwahr to use getProjectsNoSchemes instead of getProjects
-(defun jiralib-get-projects ()
-  "Return a list of projects available to the user."
-  (if jiralib-projects-list
-      jiralib-projects-list
-    (setq jiralib-projects-list
-          (if jiralib-use-restapi
-              (jiralib-call "getProjects" nil)
-            (jiralib-call "getProjectsNoSchemes" nil)))))
-
-(defun jiralib-get-saved-filters ()
-  "Get all saved filters available for the currently logged in user."
-  (jiralib-make-assoc-list (jiralib-call "getSavedFilters" nil) 'id 'name))
-
-(defun jiralib-get-server-info ()
-  "Return the Server information such as baseUrl, version, edition, buildDate, buildNumber."
-  (jiralib-call "getServerInfo" nil))
-
-(defun jiralib-get-sub-task-issue-types ()
-  "Return all visible subtask issue types in the system."
-  (jiralib-call "getSubTaskIssueTypes" nil))
-
-(defun jiralib-get-user (account-id)
-  "Return a user's information given their full name."
-  (cond ((eq 0 (length account-id)) nil) ;; Unassigned
-        (t (jiralib-call "getUser" nil account-id))))
-
-(defvar jiralib-users-cache nil "Cached list of users.")
-
-(defun jiralib-get-users (project-key)
-  "Return assignable users information given the PROJECT-KEY."
-  (unless jiralib-users-cache
-    (setq jiralib-users-cache
-          (jiralib-call "getUsers" nil project-key))
-    (cl-loop for (name . id) in org-jira-users do
-          (setf jiralib-users-cache (append (list (jiralib-get-user id)) jiralib-users-cache))))
-  jiralib-users-cache)
-
-(defun jiralib-get-versions (project-key)
-  "Return all versions available in project PROJECT-KEY."
-  (jiralib-call "getVersions" nil project-key))
-
-(defun jiralib-strip-cr (string)
-  "Remove carriage returns from STRING."
-  (when string (replace-regexp-in-string "\r" "" string)))
-
-(defun jiralib-worklog-import--filter-apply
-    (worklog-obj &optional predicate-fn-lst unwrap-worklog-records-fn rewrap-worklog-records-fn)
-  "Remove non-matching org-jira issue worklogs.
-
-Variables:
-  WORKLOG-OBJ is the passed in object
-  PREDICATE-FN-LST is the list of lambdas used as match predicates.
-  UNWRAP-WORKLOG-RECORDS-FN is the function used to produce the list of worklog records from within the worklog-obj
-  REWRAP-WORKLOG-RECORDS-FN is the function used to reshape the worklog records back into the form they were received in.
-
-Auxiliary Notes:
-  Only the WORKLOG-OBJ variable is required.
-  The value of PPREDICATE-FN-LST is filled from the jiralib-worklog-import--filters-alist variable by default.
-  If PREDICATE-FN-LST is empty the unmodified value of WORKLOG-OBJ is returned.
-  If PREDICATE-FN-LST contains multiple predicate functions, each predicate filters operates as a clause in an AND match.  In effect, a worklog must match all predicates to be returned.
-  The variable 'jiralib-user-login-name is used by many lambda filters."
-
-  (let
-      ((unwrap-worklog-records-fn)
-       (rewrap-worklog-records-fn)
-       (predicate-fn-lst)
-       (worklogs worklog-obj)
-       (predicate-fn))
-    ;; let-body
-    (progn
-      (setq unwrap-worklog-records-fn
-            (if (and
-                 (boundp 'unwrap-worklog-records-fn)
-                 (functionp unwrap-worklog-records-fn))
-                unwrap-worklog-records-fn
-              (lambda (x) (cl-coerce x 'list))))
-      (setq rewrap-worklog-records-fn
-            (if (and
-                 (boundp 'rewrap-worklog-records-fn)
-                 (functionp rewrap-worklog-records-fn))
-                rewrap-worklog-records-fn
-              (lambda (x) (remove 'nil (cl-coerce x 'vector)))))
-      (setq predicate-fn-lst
-            (if (and (boundp 'predicate-fn-lst)
-                     (not (null predicate-fn-lst))
-                     (listp predicate-fn-lst))
-                predicate-fn-lst
-              (mapcar 'caddr
-                      (remove 'nil
-                              (mapcar (lambda (x) (unless (null (car x)) x))
-                                      jiralib-worklog-import--filters-alist)))))
-      ;; final condition/sanity checks before processing
-      (cond
-       ;; pass cases, don't apply filters, return unaltered worklog-obj
-       ((or (not (boundp 'predicate-fn-lst)) (not (listp predicate-fn-lst)) (null predicate-fn-lst))
-        worklog-obj)
-       ;; default-case, apply worklog filters and return only matching worklogs
-       (t
-        (setq worklogs (funcall unwrap-worklog-records-fn worklogs))
-        (while (setq predicate-fn (pop predicate-fn-lst))
-          (setq worklogs (mapcar predicate-fn worklogs)))
-        (funcall rewrap-worklog-records-fn worklogs))))))
-
-
-(defun jiralib-get-board (id &optional callback)
-  "Return details on given board"
-  (jiralib-call "getBoard" nil id))
-
-(defun jiralib-get-boards ()
-  "Return list of jira boards"
-  (jiralib-call "getBoards" nil))
-
-(defun jiralib-get-board-sprints (id)
-  "Return list of jira sprints in the specified jira board"
-  (jiralib-call "getSprintsFromBoard" nil id))
-
-(defun jiralib-get-sprint-issues (id &rest params)
-  "Return list of issues in the specified sprint"
-  (apply 'jiralib-call "getIssuesFromSprint"
-        (cl-getf params :callback) id params))
-
-(defun jiralib-get-board-issues (board-id &rest params)
-  "Return list of jira issues in the specified jira board"
-  (apply 'jiralib-call "getIssuesFromBoard"
-        (cl-getf params :callback) board-id params))
-
-(defun jiralib--agile-not-last-entry (num-entries total start-at limit)
-  "Return true if need to retrieve next page from agile api"
-  (and (> num-entries 0)
-       (or (not limit) ; not required to be set
-          (< limit 1) ; ignore invalid limit
-          (> limit start-at))
-       (or (not total) ; not always returned
-           (> total start-at))))
-
-(defun jiralib--agile-limit-page-size (page-size start-at limit)
-  (if (and limit
-          (> (+ start-at page-size) limit))
-      (- limit  start-at)
-    page-size))
-
-
-(defun jiralib--agile-rest-call-it (api max-results start-at limit query-params)
-  (let ((callurl
-        (format "%s?%s" api
-                (url-build-query-string
-                 (append `((maxResults ,(jiralib--agile-limit-page-size max-results start-at limit))
-                           (startAt ,start-at))
-                         query-params)))))
-    (jiralib--rest-call-it callurl)))
-
-(defun jiralib--agile-call-it (api values-key &rest params)
-  "Invoke Jira agile method api and retrieve the results using
-paging.
-
-If JIRALIB-COMPLETE-CALLBACK is non-nil, then the call will be
-performed asynchronously and JIRALIB-COMPLETE-CALLBACK will be
-called when all data are retrieved.
-
-If JIRALIB-COMPLETE-CALLBACK is nil, then the call will be
-performed synchronously and this function will return the
-retrieved data.
-
-API - path to called API that must start with /rest/agile/1.0.
-
-VALUES-KEY - key of the actual reply data in the reply assoc list.
-
-PARAMS - optional additional parameters.
-:limit - limit total number of retrieved entries.
-:query-params - extra query parameters in the format of url-build-query-string.
-"
-  (if jiralib-complete-callback
-      (apply 'jiralib--agile-call-async api values-key params)
-    (apply 'jiralib--agile-call-sync api values-key params)))
-
-(defun jiralib--agile-call-sync (api values-key &rest params)
-  "Syncroniously invoke Jira agile method api retrieve all the
-results using paging and return results.
-
-VALUES-KEY - key of the actual reply data in the reply assoc list.
-
-PARAMS - extra parameters (as keyword arguments), the supported parameters are:
-
-:limit - limit total number of retrieved entries.
-:query-params - extra query parameters in the format of url-build-query-string.
-"
-  (setq jiralib-complete-callback nil)
-  (let ((not-last t)
-        (start-at 0)
-       (limit (cl-getf params :limit))
-       (query-params (cl-getf params :query-params))
-       ;; maximum page size, 50 is server side maximum
-        (max-results jiralib-agile-page-size)
-        (values ()))
-    (while not-last
-      (let* ((reply-alist
-             (jiralib--agile-rest-call-it api max-results start-at limit query-params))
-             (values-array (cdr (assoc values-key reply-alist)))
-             (num-entries (length values-array))
-             (total (cdr (assq 'total reply-alist))))
-        (setf values (append values (append values-array nil)))
-        (setf start-at (+ start-at num-entries))
-        (setf not-last (jiralib--agile-not-last-entry num-entries total start-at limit))))
-    values))
-
-(defun jiralib--agile-call-async  (api values-key &rest params)
-  "Asyncroniously invoke Jira agile method api,
-retrieve all the results using paging and call
-JIRALIB-COMPLETE_CALLBACK when all the data are retrieved.
-
-VALUES-KEY - key of the actual reply data in the reply assoc list.
-
-PARAMS - extra parameters (as keyword arguments), the supported parameters are:
-
-limit - limit total number of retrieved entries."
-  (lexical-let
-      ((start-at 0)
-       (limit (cl-getf params :limit))
-       (query-params (cl-getf params :query-params))
-       ;; maximum page size, 50 is server side maximum
-       (max-results jiralib-agile-page-size)
-       (values-list ())
-       (vk values-key)
-       (url api)
-       ;; save the call back to be called later after the last page
-       (complete-callback jiralib-complete-callback))
-    ;; setup new callback to be called after each page
-    (setf jiralib-complete-callback
-          (cl-function
-           (lambda  (&rest data &allow-other-keys)
-             (condition-case err
-                 (let* ((reply-alist (cl-getf data :data))
-                        (values-array (cdr (assoc vk reply-alist)))
-                        (num-entries (length values-array))
-                        (total (cdr (assq 'total reply-alist))))
-                   (setf values-list (append values-list (append values-array nil)))
-                   (setf start-at (+ start-at num-entries))
-                   (message "jiralib agile retrieve: got %d values%s%s"
-                            start-at
-                            (if total " of " "")
-                            (if total (int-to-string total) ""))
-                   (if (jiralib--agile-not-last-entry num-entries total start-at limit)
-                      (jiralib--agile-rest-call-it url max-results start-at limit query-params)
-                     ;; last page: call originall callback
-                     (message "jiralib agile retrieve: calling callback")
-                     (setf jiralib-complete-callback complete-callback)
-                     (funcall jiralib-complete-callback
-                              :data  (list (cons vk  values-list)))
-                     (message "jiralib agile retrieve: all done")))
-               ('error (message (format "jiralib agile retrieve: caught error: %s" err)))))))
-    (jiralib--agile-rest-call-it api max-results start-at limit query-params)))
-
-(provide 'jiralib)
-;;; jiralib.el ends here
diff --git a/elpa/org-jira-20230915.57/org-jira-autoloads.el b/elpa/org-jira-20230915.57/org-jira-autoloads.el
deleted file mode 100644 (file)
index cf50172..0000000
+++ /dev/null
@@ -1,176 +0,0 @@
-;;; org-jira-autoloads.el --- automatically extracted autoloads (do not edit)   -*- lexical-binding: t -*-
-;; Generated by the `loaddefs-generate' function.
-
-;; This file is part of GNU Emacs.
-
-;;; Code:
-
-(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
-
-
-\f
-;;; Generated autoloads from jiralib.el
-
-(register-definition-prefixes "jiralib" '("jiralib-"))
-
-\f
-;;; Generated autoloads from org-jira.el
-
-(autoload 'org-jira-mode "org-jira" "\
-Toggle org-jira mode.
-
-With no argument, the mode is toggled on/off.
-Non-nil argument turns mode on.
-Nil argument turns mode off.
-
-Commands:
-\\{org-jira-entry-mode-map}
-
-Entry to this mode calls the value of `org-jira-mode-hook'.
-
-This is a minor mode.  If called interactively, toggle the
-`Org-jira mode' mode.  If the prefix argument is positive, enable
-the mode, and if it is zero or negative, disable the mode.
-
-If called from Lisp, toggle the mode if ARG is `toggle'.  Enable
-the mode if ARG is nil, omitted, or is a positive number.
-Disable the mode if ARG is a negative number.
-
-To check whether the minor mode is enabled in the current buffer,
-evaluate `org-jira-mode'.
-
-The mode's hook is called both when the mode is enabled and when
-it is disabled.
-
-(fn &optional ARG)" t)
-(autoload 'org-jira-get-projects "org-jira" "\
-Get list of projects." t)
-(autoload 'org-jira-get-issue-list "org-jira" "\
-Get list of issues, using jql (jira query language), invoke CALLBACK after.
-
-Default is unresolved issues assigned to current login user; with
-a prefix argument you are given the chance to enter your own
-jql.
-
-(fn &optional CALLBACK)")
-(autoload 'org-jira-get-summary "org-jira" "\
-Get issue summary from point and place next to issue id from jira" t)
-(autoload 'org-jira-get-summary-url "org-jira" "\
-Get issue summary from point and place next to issue id from jira, and make issue id a link" t)
-(autoload 'org-jira-get-issues-headonly "org-jira" "\
-Get list of ISSUES, head only.
-
-The default behavior is to return issues assigned to you and unresolved.
-
-With a prefix argument, allow you to customize the jql.  See
-`org-jira-get-issue-list'.
-
-(fn ISSUES)" t)
-(autoload 'org-jira-get-issue "org-jira" "\
-Get a JIRA issue, allowing you to enter the issue-id first.
-
-(fn ID)" t)
-(autoload 'org-jira-get-issues-by-fixversion "org-jira" "\
-Get list of issues by FIXVERSION.
-
-(fn FIXVERSION)" t)
-(autoload 'org-jira-get-issue-project "org-jira" "\
-
-
-(fn ISSUE)")
-(autoload 'org-jira-get-issues "org-jira" "\
-Get list of ISSUES into an org buffer.
-
-Default is get unfinished issues assigned to you, but you can
-customize jql with a prefix argument.
-See`org-jira-get-issue-list'
-
-(fn ISSUES)" t)
-(autoload 'org-jira-get-issues-from-custom-jql "org-jira" "\
-Get JQL-LIST list of issues from a custom JQL and PROJ-KEY.
-
-The PROJ-KEY will act as the file name, while the JQL will be any
-valid JQL to populate a file to store PROJ-KEY results in.
-
-Please note that this is *not* concurrent or race condition
-proof.  If you try to run multiple calls to this function, it
-will mangle things badly, as they rely on globals DEFAULT-JQL and
-ORG-JIRA-PROJ-KEY-OVERRIDE being set before and after running.
-
-(fn &optional JQL-LIST)" t)
-(autoload 'org-jira-update-comment "org-jira" "\
-Update a comment for the current issue." t)
-(autoload 'org-jira-update-worklogs-from-org-clocks "org-jira" "\
-Update or add a worklog based on the org clocks." t)
-(autoload 'org-jira-copy-current-issue-key "org-jira" "\
-Copy the current issue's key into clipboard." t)
-(autoload 'org-jira-unassign-issue "org-jira" "\
-Update an issue to be unassigned." t)
-(autoload 'org-jira-set-issue-reporter "org-jira" "\
-Update an issue's reporter interactively." t)
-(autoload 'org-jira-assign-issue "org-jira" "\
-Update an issue with interactive re-assignment." t)
-(autoload 'org-jira-update-issue "org-jira" "\
-Update an issue." t)
-(autoload 'org-jira-todo-to-jira "org-jira" "\
-Convert an ordinary todo item to a jira ticket." t)
-(autoload 'org-jira-get-subtasks "org-jira" "\
-Get subtasks for the current issue." t)
-(autoload 'org-jira-create-issue "org-jira" "\
-Create an issue in PROJECT, of type TYPE, with given SUMMARY and DESCRIPTION.
-
-(fn PROJECT TYPE SUMMARY DESCRIPTION)" t)
-(autoload 'org-jira-create-subtask "org-jira" "\
-Create a subtask issue for PROJECT, of TYPE, with SUMMARY and DESCRIPTION.
-
-(fn PROJECT TYPE SUMMARY DESCRIPTION)" t)
-(autoload 'org-jira-refresh-issue "org-jira" "\
-Refresh current issue from jira to org." t)
-(autoload 'org-jira-progress-issue "org-jira" "\
-Progress issue workflow." t)
-(autoload 'org-jira-progress-issue-next "org-jira" "\
-Progress issue workflow." t)
-(autoload 'org-jira-browse-issue "org-jira" "\
-Open the current issue in external browser." t)
-(autoload 'org-jira-download-attachment "org-jira" "\
-Download the attachment under cursor." t)
-(autoload 'org-jira-get-issues-from-filter "org-jira" "\
-Get issues from the server-side stored filter named FILTER.
-
-Provide this command in case some users are not able to use
-client side jql (maybe because of JIRA server version?).
-
-(fn FILTER)" t)
-(autoload 'org-jira-get-issues-from-filter-headonly "org-jira" "\
-Get issues *head only* from saved filter named FILTER.
-See `org-jira-get-issues-from-filter'.
-
-(fn FILTER)" t)
-(autoload 'org-jira-get-issues-by-board "org-jira" "\
-Get list of ISSUES from agile board." t)
-(autoload 'org-jira-get-issues-by-sprint "org-jira" "\
-Get list of ISSUES from sprint." t)
-(autoload 'org-jira-get-issues-by-board-headonly "org-jira" "\
-Get list of ISSUES from agile board, head only." t)
-(autoload 'org-jira-get-boards "org-jira" "\
-Get list of boards and their properties." t)
-(register-definition-prefixes "org-jira" '("ensure-on-" "org-jira-"))
-
-\f
-;;; Generated autoloads from org-jira-sdk.el
-
-(register-definition-prefixes "org-jira-sdk" '("org-jira-sdk-"))
-\f
-;;; End of scraped data
-
-(provide 'org-jira-autoloads)
-
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; no-native-compile: t
-;; coding: utf-8-emacs-unix
-;; End:
-
-;;; org-jira-autoloads.el ends here
diff --git a/elpa/org-jira-20230915.57/org-jira-pkg.el b/elpa/org-jira-20230915.57/org-jira-pkg.el
deleted file mode 100644 (file)
index bbda076..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-(define-package "org-jira" "20230915.57" "Syncing between Jira and Org-mode."
-  '((emacs "24.5")
-    (cl-lib "0.5")
-    (request "0.2.0")
-    (dash "2.14.1"))
-  :commit "295b01ede42952c848bd8d76bc8c456a87876cbc" :maintainers
-  '(("Matthew Carter" . "mNOSPAMahungry.com"))
-  :maintainer
-  '("Matthew Carter" . "mNOSPAMahungry.com")
-  :keywords
-  '("ahungry" "jira" "org" "bug" "tracker")
-  :url "https://github.com/ahungry/org-jira")
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
diff --git a/elpa/org-jira-20230915.57/org-jira-sdk.el b/elpa/org-jira-20230915.57/org-jira-sdk.el
deleted file mode 100644 (file)
index 864de37..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-;;; org-jira-sdk.el -- SDK Layer for entities
-
-;; Copyright (C) 2018 Matthew Carter <mNOSPAMahungry.com>
-;;
-;; Authors:
-;; Matthew Carter <mNOSPAMahungry.com>
-;;
-;; Maintainer: Matthew Carter <mNOSPAMahungry.com>
-;; URL: https://github.com/ahungry/org-jira
-;; Version: 3.1.1
-;; Keywords: ahungry jira org bug tracker
-;; Package-Requires: ((emacs "24.5") (cl-lib "0.5") (request "0.2.0") (s "0.0.0"))
-
-;; This file is not part of GNU Emacs.
-
-;; 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 of the License, 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 this program.  If not, see
-;; <http://www.gnu.org/licenses/> or write to the Free Software
-;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-;; 02110-1301, USA.
-
-;;; Commentary:
-
-;; This provides an SDK wrapper for more strictly defining the entities we interact with.
-
-;;; Code:
-
-(require 'jiralib)
-(require 'cl-lib)
-(require 'eieio)
-(require 'dash)
-(require 'cl-generic)
-
-(defclass org-jira-sdk-record ()
-  ((id :initarg :id :type string :required t)
-   (data :initarg :data :documentation "The area to hold a big alist of data.")
-   (hydrate-fn :initform (lambda (id) (message "Not implemented."))))
-  "The ID of the record.")
-
-(defun org-jira-sdk-string-but-first (s) (cl-subseq s 1))
-
-(defun org-jira-sdk-to-string (s) (format "%s" s))
-
-(defun org-jira-sdk-to-prefixed-string (s) (format "org-jira-sdk-%s" s))
-
-(defun org-jira-sdk-record-type-to-symbol (record-type)
-  (-> record-type symbol-name org-jira-sdk-string-but-first org-jira-sdk-to-prefixed-string intern))
-
-(defun org-jira-sdk-create-from-id (record-type id &optional parent-id callback)
-  (let ((rec (funcall (org-jira-sdk-record-type-to-symbol record-type)
-                      :id (format "%s" id)
-                      :parent-id parent-id)))
-    (with-slots (data) rec
-      (setf data (org-jira-sdk-hydrate rec callback))
-      (org-jira-sdk-from-data rec))))
-
-(defun org-jira-sdk-create-from-data (record-type data)
-  (let ((rec (funcall (org-jira-sdk-record-type-to-symbol record-type) :data data)))
-    (org-jira-sdk-from-data rec)))
-
-(cl-defmethod org-jira-sdk-hydrate ((rec org-jira-sdk-record) &optional callback)
-  "Populate the record with data from the remote endpoint."
-  (with-slots (id hydrate-fn) rec
-    (funcall hydrate-fn id callback)))
-
-(cl-defgeneric org-jira-sdk-from-data ((rec org-jira-sdk-record)))
-
-(cl-defmethod org-jira-sdk-dump ((rec org-jira-sdk-record))
-  "A decent pretty print/object dump for working with the class items."
-  (let ((slots (mapcar (lambda (slot) (aref slot 1)) (eieio-class-slots (eieio-object-class rec)))))
-    (setq slots (cl-remove-if (lambda (s) (not (slot-boundp rec s))) slots))
-    (apply #'concat
-     (mapcar (lambda (slot)
-               (let ((slot (intern (org-jira-sdk-to-string slot))))
-                 (format "\n%+16s:   %s" slot (slot-value rec (intern (org-jira-sdk-to-string slot)))))
-               )
-             slots))))
-
-(defun org-jira-sdk-path (alist key-chain)
-  "Query a nested path in some type of ALIST by traversing down the keys of KEY-CHAIN."
-  (cl-reduce (lambda (a k) (alist-get k a)) key-chain :initial-value alist))
-
-(defclass org-jira-sdk-issue (org-jira-sdk-record)
-  ((assignee :type (or null string) :initarg :assignee)
-   (components :type string :initarg :components)
-   (labels :type string :initarg :labels)
-   (created :type string :initarg :created)
-   (description :type (or null string) :initarg :description)
-   (duedate :type (or null string) :initarg :duedate)
-   (headline :type string :initarg :headline)
-   (id :type string :initarg :id)       ; TODO: Probably remove me
-   (issue-id :type string :initarg :issue-id :documentation "The common ID/key, such as EX-1.")
-   (issue-id-int :type string :initarg :issue-id-int :documentation "The internal Jira ID, such as 12345.")
-   (filename :type (or null string) :initarg :filename :documentation "The filename to write issue to.")
-   (priority :type (or null string) :initarg :priority)
-   (proj-key :type string :initarg :proj-key)
-   (reporter :type (or null string) :initarg :reporter)
-   (resolution :type (or null string) :initarg :resolution)
-   (sprint :type (or null string) :initarg :sprint)
-   (start-date :type (or null string) :initarg :start-date)
-   (status :type string :initarg :status)
-   (summary :type string :initarg :summary)
-   (type :type string :initarg :type)
-   (type-id :type string :initarg :type-id)
-   (updated :type string :initarg :updated)
-   (data :initarg :data :documentation "The remote Jira data object (alist).")
-   (hydrate-fn :initform #'jiralib-get-issue :initarg :hydrate-fn))
-  "An issue on the end.  ID of the form EX-1, or a numeric such as 10000.")
-
-(defclass org-jira-sdk-comment (org-jira-sdk-record)
-  ((author :type string :initarg :author)
-   (body :type string :initarg :body)
-   (comment-id :type string :initarg :comment-id :documentation "The comment ID, such as 12345.")
-   (created :type string :initarg :created)
-   (headline :type string :initarg :headline)
-   (parent-id :type string :initarg :parent-id :documentation "The parent issue-id such as EX-1.")
-   (updated :type string :initarg :updated)
-   (data :initarg :data :documentation "The reomte Jira data object (alist).")
-   (hydrate-fn :initform #'jiralib-get-comment :initarg :hydrate-fn)))
-
-(defclass org-jira-sdk-board (org-jira-sdk-record)
-  ((name :type string :initarg :name :required t)
-   (url  :type string :initarg :url  :required t)
-   (board-type :type string :initarg :board-type)
-   (jql  :type string :initarg :jql)
-   (limit :type integer :initarg :limit)
-   ;; unused
-   (parent-id :type string :initarg :parent-id)
-   (hydrate-fn :initform #'jiralib-get-board :initarg :hydrate-fn)))
-
-(cl-defmethod org-jira-sdk-hydrate ((rec org-jira-sdk-comment) &optional callback)
-  "Populate the record with data from the remote endpoint."
-  (with-slots (id proj-key hydrate-fn) rec
-    (funcall hydrate-fn proj-key id callback)))
-
-(cl-defmethod org-jira-sdk-from-data ((rec org-jira-sdk-issue))
-  (cl-flet ((path (keys) (org-jira-sdk-path (oref rec data) keys)))
-    (org-jira-sdk-issue
-     :assignee (path '(fields assignee displayName))
-     :components (mapconcat (lambda (c) (org-jira-sdk-path c '(name))) (path '(fields components)) ", ")
-     :labels (mapconcat (lambda (c) (format "%s" c)) (mapcar #'identity (path '(fields labels))) ", ")
-     :created (path '(fields created))     ; confirm
-     :description (or (path '(fields description)) "")
-     :duedate (or (path '(fields sprint endDate)) (path '(fields duedate)))         ; confirm
-     :filename (path '(fields project key))
-     :headline (path '(fields summary)) ; Duplicate of summary, maybe different.
-     :id (path '(key))
-     :issue-id (path '(key))
-     :issue-id-int (path '(id))
-     :priority (path '(fields priority name))
-     :proj-key (path '(fields project key))
-     :reporter (path '(fields reporter displayName)) ; reporter could be an object of its own slot values
-     :resolution (path '(fields resolution name))  ; confirm
-     :sprint (path '(fields sprint name))
-     :start-date (path '(fields start-date))  ; confirm
-     :status (org-jira-decode (path '(fields status name)))
-     :summary (path '(fields summary))
-     :type (path '(fields issuetype name))
-     :type-id (path '(fields issuetype id))
-     :updated (path '(fields updated))  ; confirm
-     ;; TODO: Remove this
-     ;; :data (oref rec data)
-     )))
-
-(cl-defmethod org-jira-sdk-from-data ((rec org-jira-sdk-comment))
-  (cl-flet ((path (keys) (org-jira-sdk-path (oref rec data) keys)))
-    (org-jira-sdk-comment
-     :author (path '(author displayName))
-     :body (path '(body))
-     :comment-id (path '(id))
-     :created (path '(created))
-     :headline (format "Comment: %s" (path '(author displayName)))
-     :parent-id (if (slot-boundp rec 'parent-id) (oref rec parent-id) "")
-     :updated (path '(updated))
-     ;; TODO: Remove this
-     ;; :data (oref rec data)
-     )))
-
-(cl-defmethod org-jira-sdk-from-data ((rec org-jira-sdk-board))
-  (with-slots (data) rec
-    (org-jira-sdk-board
-     :id   (int-to-string (alist-get 'id data))
-     :name (alist-get 'name data)
-     :url  (alist-get 'self data)
-     ;; TODO: remove it? used by org-jira-sdk-create-from-id
-     :parent-id ""
-     :board-type (alist-get 'type data))))
-
-;; Issue
-(defun org-jira-sdk-create-issue-from-data (d) (org-jira-sdk-create-from-data :issue d))
-(defun org-jira-sdk-create-issues-from-data-list (ds) (mapcar #'org-jira-sdk-create-issue-from-data ds))
-
-;; Issue with custom filename setting
-(defun org-jira-sdk-create-issue-from-data-with-filename (filename)
-  (lambda (d)
-    (let ((Issue (org-jira-sdk-create-from-data :issue d)))
-      (setf (oref Issue filename) filename)
-      Issue)))
-
-(defun org-jira-sdk-create-issues-from-data-list-with-filename (filename ds)
-  (let ((fn (org-jira-sdk-create-issue-from-data-with-filename filename)))
-    (mapcar fn ds)))
-
-;; Comment
-(defun org-jira-sdk-create-comment-from-data (d) (org-jira-sdk-create-from-data :comment d))
-(defun org-jira-sdk-create-comments-from-data-list (ds) (mapcar #'org-jira-sdk-create-comment-from-data ds))
-
-(defun org-jira-sdk-isa-record? (i) (cl-typep i 'org-jira-sdk-record))
-(defun org-jira-sdk-isa-issue? (i) (cl-typep i 'org-jira-sdk-issue))
-(defun org-jira-sdk-isa-comment? (i) (cl-typep i 'org-jira-sdk-comment))
-
-;; Board
-(defun org-jira-sdk-create-board-from-data (d) (org-jira-sdk-create-from-data :board d))
-(defun org-jira-sdk-create-boards-from-data-list (ds) (mapcar #'org-jira-sdk-create-board-from-data ds))
-
-(provide 'org-jira-sdk)
-
-;;; org-jira-sdk.el ends here
diff --git a/elpa/org-jira-20230915.57/org-jira.el b/elpa/org-jira-20230915.57/org-jira.el
deleted file mode 100644 (file)
index 0f45c41..0000000
+++ /dev/null
@@ -1,2583 +0,0 @@
-;;; org-jira.el --- Syncing between Jira and Org-mode. -*- lexical-binding: t -*-
-
-;; Copyright (C) 2016-2022 Matthew Carter <mNOSPAMahungry.com>
-;; Copyright (C) 2011 Bao Haojun
-;;
-;; Authors:
-;; Matthew Carter <mNOSPAMahungry.com>
-;; Bao Haojun <baohaojunNOSPAMgmail.com>
-;;
-;; Maintainer: Matthew Carter <mNOSPAMahungry.com>
-;; URL: https://github.com/ahungry/org-jira
-;; Version: 4.3.3
-;; Keywords: ahungry jira org bug tracker
-;; Package-Requires: ((emacs "24.5") (cl-lib "0.5") (request "0.2.0") (dash "2.14.1"))
-
-;; This file is not part of GNU Emacs.
-
-;; 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 of the License, 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 this program.  If not, see
-;; <http://www.gnu.org/licenses/> or write to the Free Software
-;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-;; 02110-1301, USA.
-
-;;; Commentary:
-
-;; This provides an extension to org-mode for syncing issues with JIRA
-;; issue servers.
-
-;;; News:
-
-;;;; Changes in 4.4.1
-;; - Fix tag (4.3.3 was out of order - we had a 4.4.0 on repo)
-;; - Fix for some crazy scoping issue in the org-jira-get-issue-val-from-org function
-
-;;;; Changes in 4.3.3:
-;; - Address issue with assignee property being removed when Unassigned
-
-;;;; Changes in 4.3.2:
-;; - Fixes issues with org-jira-add-comment and org-jira-update-comment
-
-;;;; Changes in 4.3.1:
-;; - Fix to make custom-jql results sync worklogs properly.
-
-;;;; Changes in 4.3.0:
-;; - Allow org-jira-set-issue-reporter call to dynamically set this value.
-
-;;;; Changes in 4.1.0:
-;; - Allow custom-jql to be specified and render in special files (see: README.md).
-
-;;;; Changes in 4.0.0:
-;; - Introduce SDK type for handling records vs random alist structures.
-
-;;;; Changes since 3.1.0:
-;; - Fix how we were ruining the kill-ring with kill calls.
-
-;;;; Changes since 3.0.0:
-;; - Add new org-jira-add-comment call (C-c c c)
-
-;;;; Changes since 2.8.0:
-;; - New version 3.0.0 deprecates old filing mechanism and files
-;;   all of the changes under the top level ticket header.
-;; - If you want other top level headers in the same file, this should
-;;   work now, as long as they come after the main project one.
-
-;;;; Changes since 2.7.0:
-;; - Clean up multi-buffer handling, disable attachments call until
-;; - refresh is compatible with it.
-
-;;;; Changes since 2.6.3:
-;; - Insert worklog import filter in the existing org-jira-update-worklogs-for-current-issue function
-;; - Sync up org-clocks and worklogs!  Set org-jira-worklog-sync-p to nil to avoid.
-
-;;;; Changes since 2.6.1:
-;; - Fix bug with getting all issues when worklog is an error trigger.
-
-;;;; Changes since 2.5.4:
-;; - Added new org-jira-refresh-issues-in-buffer call and binding
-
-;;;; Changes since 2.5.3:
-;; - Re-introduce the commit that introduced a break into Emacs 25.1.1 list/array push
-;;     The commit caused updates/comment updates to fail when a blank list of components
-;;     was present - it will now handle both cases (full list, empty list).
-
-;;;; Changes since 2.5.2:
-;; - Revert a commit that introduced a break into Emacs 25.1.1 list/array push
-;;     The commit caused updates/comment updates to fail
-
-;;;; Changes since 2.5.1:
-;; - Only set duedate if a DEADLINE is present in the tags and predicate is t
-
-;;;; Changes since 2.5.0:
-;; - Allow overriding the org property names with new defcustom
-
-;;;; Changes since 2.4.0:
-;; - Fix many deprecation/warning issues
-;; - Fix error with allow-other-keys not being wrapped in cl-function
-
-;;;; Changes since 2.3.0:
-;; - Integration with org deadline and Jira due date fields
-
-;;;; Changes since 2.2.0:
-;; - Selecting issue type based on project key for creating issues
-
-;;;; Changes since 2.1.0:
-;; - Allow changing to unassigned user
-;; - Add new shortcut for quick assignment
-
-;;;; Changes since 2.0.0:
-;; - Change statusCategory to status value
-;; - Clean out some redundant code
-;; - Add ELPA tags in keywords
-
-;;;; Changes since 1.0.1:
-;; - Converted many calls to async
-;; - Removed minor annoyances (position resets etc.)
-
-;;; Code:
-
-(eval-when-compile (require 'cl))
-(require 'org)
-(require 'org-clock)
-(require 'cl-lib)
-(require 'url)
-(require 'ls-lisp)
-(require 'dash)
-(require 'jiralib)
-(require 'org-jira-sdk)
-
-(defconst org-jira-version "4.3.1"
-  "Current version of org-jira.el.")
-
-(defgroup org-jira nil
-  "Customization group for org-jira."
-  :tag "Org JIRA"
-  :group 'org)
-
-(defcustom org-jira-working-dir "~/.org-jira"
-  "Folder under which to store org-jira working files."
-  :group 'org-jira
-  :type 'directory)
-
-(defcustom org-jira-project-filename-alist nil
-  "Alist translating project keys to filenames.
-
-Each element has a structure:
-
-  (PROJECT-KEY . NEW-FILE-NAME)
-
-where both are strings.  NEW-FILE-NAME is relative to
-`org-jira-working-dir'."
-  :group 'org-jira
-  :type '(alist :key-type (string :tag "Project key")
-                :value-type (string :tag "File name for this project")))
-
-(defcustom org-jira-default-jql
-  "assignee = currentUser() and resolution = unresolved ORDER BY
-  priority DESC, created ASC"
-  "Default jql for querying your Jira tickets."
-  :group 'org-jira
-  :type 'string)
-
-(defcustom org-jira-ignore-comment-user-list
-  '("admin")
-  "Jira usernames that should have comments ignored."
-  :group 'org-jira
-  :type '(repeat (string :tag "Jira username:")))
-
-(defcustom org-jira-reverse-comment-order nil
-  "If non-nil, order comments from most recent to least recent."
-  :group 'org-jira
-  :type 'boolean)
-
-(defcustom org-jira-done-states
-  '("Closed" "Resolved" "Done")
-  "Jira states that should be considered as DONE for `org-mode'."
-  :group 'org-jira
-  :type '(repeat (string :tag "Jira state name:")))
-
-(defcustom org-jira-users
-  '(("Full Name" . "account-id"))
-  "A list of displayName and key pairs."
-  :group 'org-jira
-  :type 'list)
-
-(defcustom org-jira-progress-issue-flow
-  '(("To Do" . "In Progress")
-    ("In Progress" . "Done"))
-  "Quickly define a common issue flow."
-  :group 'org-jira
-  :type 'list)
-
-(defcustom org-jira-property-overrides (list)
-  "An assoc list of property tag overrides.
-
-The KEY . VAL pairs should both be strings.
-
-For instance, to change the :assignee: property in the org :PROPERTIES:
-block to :WorkedBy:, you can set this as such:
-
-  (setq org-jira-property-overrides (list (cons \"assignee\" \"WorkedBy\")))
-
-or simply:
-
-  (add-to-list (quote org-jira-property-overrides)
-               (cons (\"assignee\" \"WorkedBy\")))
-
-This will work for most any of the properties, with the
-exception of ones with unique functionality, such as:
-
-  - deadline
-  - summary
-  - description"
-  :group 'org-jira
-  :type 'list)
-
-(defcustom org-jira-serv-alist nil
-  "Association list to set information for each jira server.
-Each element of the alist is a jira server name.  The CAR of each
-element is a string, uniquely identifying the server.  The CDR of
-each element is a well-formed property list with an even number
-of elements, alternating keys and values, specifying parameters
-for the server.
-
-     (:property value :property value ... )
-
-When a property is given a value in org-jira-serv-alist, its
-setting overrides the value of the corresponding user
-variable (if any) during syncing.
-
-Most properties are optional, but some should always be set:
-
-  :url        soap url of the jira server.
-  :username   username to be used.
-  :host       hostname of the jira server (TODO: compute it from ~url~).
-
-All the other properties are optional.  They override the global
-variables.
-
-  :password   password to be used, will be prompted if missing."
-  :group 'org-jira
-  :type '(alist :value-type plist))
-
-(defcustom org-jira-use-status-as-todo nil
-  "Use the JIRA status as the TODO tag value."
-  :group 'org-jira)
-
-(defcustom org-jira-deadline-duedate-sync-p t
-  "Keep org deadline and jira duedate fields synced.
-You may wish to set this to nil if you track org deadlines in
-your buffer that you do not want to send back to your Jira
-instance."
-  :group 'org-jira
-  :type 'boolean)
-
-(defcustom org-jira-worklog-sync-p t
-  "Keep org clocks and jira worklog fields synced.
-You may wish to set this to nil if you track org clocks in
-your buffer that you do not want to send back to your Jira
-instance."
-  :group 'org-jira
-  :type 'boolean)
-
-(defcustom org-jira-download-dir "~/Downloads"
-  "Name of the default jira download library."
-  :group 'org-jira
-  :type 'string)
-
-(defcustom org-jira-download-ask-override t
-  "Ask before overriding tile."
-  :group 'org-jira
-  :type 'boolean)
-
-(defcustom org-jira-jira-status-to-org-keyword-alist nil
-  "Custom alist of jira status stored in car and `org-mode' keyword stored in cdr."
-  :group 'org-jira
-  :type '(alist :key-type string :value-type string))
-
-(defcustom org-jira-priority-to-org-priority-omit-default-priority nil
-  "Whether to omit insertion of priority when it matches the default.
-
-When set to t, will omit the insertion of the matched value from
-`org-jira-priority-to-org-priority-alist' when it matches the `org-default-priority'."
-  :group 'org-jira
-  :type 'boolean)
-
-(defcustom org-jira-priority-to-org-priority-alist nil
-  "Alist mapping jira priority keywords to `org-mode' priority cookies.
-
-A sample value might be
-  (list (cons \"High\" ?A)
-        (cons \"Medium\" ?B)
-        (cons \"Low\" ?C)).
-
-See `org-default-priority' for more info."
-  :group 'org-jira
-  :type '(alist :key-type string :value-type character))
-
-(defcustom org-jira-boards-default-limit 50
-  "Default limit for number of issues retrieved from agile boards."
-  :group 'org-jira
-  :type 'integer)
-
-;; FIXME: Issue with using this - issues are grouped under a headline incorrectly.
-(defcustom org-jira-custom-jqls
-  '(
-    (:jql " assignee = currentUser() and createdDate < '2019-01-01' order by created DESC "
-          :limit 100
-          :filename "last-years-work")
-    (:jql " assignee = currentUser() and createdDate >= '2019-01-01' order by created DESC "
-          :limit 100
-          :filename "this-years-work")
-    )
-  "A list of plists with :jql and :filename keys to run arbitrary user JQL."
-  :group 'org-jira
-  :type '(alist :value-type plist))
-
-(defcustom org-jira-download-comments t
-  "Set to nil if you don't want to update comments during issue rendering."
-  :group 'org-jira
-  :type 'boolean)
-
-(defvar org-jira-serv nil
-  "Parameters of the currently selected blog.")
-
-(defvar org-jira-serv-name nil
-  "Name of the blog, to pick from `org-jira-serv-alist'.")
-
-(defvar org-jira-projects-list nil
-  "List of jira projects.")
-
-(defvar org-jira-current-project nil
-  "Currently selected (i.e., active project).")
-
-(defvar org-jira-issues-list nil
-  "List of jira issues under the current project.")
-
-(defvar org-jira-server-rpc-url nil
-  "Jira server soap URL.")
-
-(defvar org-jira-server-userid nil
-  "Jira server user id.")
-
-(defvar org-jira-proj-id nil
-  "Jira project ID.")
-
-(defvar org-jira-logged-in nil
-  "Flag whether user is logged-in or not.")
-
-(defvar org-jira-buffer-name "*org-jira-%s*"
-  "Name of the jira buffer.")
-
-(defvar org-jira-buffer-kill-prompt t
-  "Ask before killing buffer.")
-
-(make-variable-buffer-local 'org-jira-buffer-kill-prompt)
-
-(defvar org-jira-mode-hook nil
-  "Hook to run upon entry into mode.")
-
-(defvar org-jira-issue-id-history '()
-  "Prompt history for issue id.")
-
-(defvar org-jira-fixversion-id-history '()
-  "Prompt history for fixversion id.")
-
-(defvar org-jira-verbosity 'debug)
-
-(defun org-jira-log (s) (when (eq 'debug org-jira-verbosity) (message "%s" s)))
-
-(defmacro ensure-on-issue (&rest body)
-  "Make sure we are on an issue heading, before executing BODY."
-  (declare (debug t)
-           (indent 0))
-  `(save-excursion
-    (save-restriction
-      (widen)
-      (unless (looking-at "^\\*\\* ")
-        (search-backward-regexp "^\\*\\* " nil t)) ; go to top heading
-      (let ((org-jira-id (org-jira-id)))
-        (unless (and org-jira-id (string-match (jiralib-get-issue-regexp) (downcase org-jira-id)))
-          (error "Not on an issue region!")))
-      ,@body)))
-
-(defmacro org-jira-with-callback (&rest body)
-  "Simpler way to write the data BODY callbacks."
-  (declare (debug t)
-           (indent 0))
-  `(lambda (&rest request-response)
-     (declare (ignore cb-data))
-     (let ((cb-data (cl-getf request-response :data)))
-       ,@body)))
-
-(defmacro org-jira-freeze-ui (&rest body)
-  "Freeze the UI layout for the user as much as possible."
-  (declare (debug t)
-           (indent 0))
-  `(save-excursion
-     (save-restriction
-       (widen)
-       (org-save-outline-visibility t
-         (outline-show-all)
-         ,@body))))
-
-(defvar org-jira-proj-key-override nil
-  "String.  An override for the proj-key.  Set to nil to restore old behavior.")
-
-;; We want some hooking system to override default-jql + this.
-(defun org-jira--get-proj-key (issue-id)
-  "Get the proper proj-key.  Typically derived from ISSUE-ID."
-  (if org-jira-proj-key-override org-jira-proj-key-override
-    (replace-regexp-in-string "-.*" "" issue-id)))
-
-(defun org-jira--get-proj-key-from-issue (Issue)
-  "Get the proper proj-key from an ISSUE instance."
-  (oref Issue filename))
-
-;; TODO: Merge these 3 ensure macros (or, scrap all but ones that work on Issue)
-(defmacro ensure-on-issue-id (issue-id &rest body)
-  "Just do some work on ISSUE-ID, execute BODY."
-  (declare (debug t)
-           (indent 1))
-  (let ((issue-id-var (make-symbol "issue-id")))
-    `(let* ((,issue-id-var ,issue-id)
-            (proj-key (org-jira--get-proj-key ,issue-id-var))
-            (project-file (org-jira--get-project-file-name proj-key))
-            (project-buffer (or (find-buffer-visiting project-file)
-                                (find-file project-file))))
-       (with-current-buffer project-buffer
-         (org-jira-freeze-ui
-           (let ((p (org-find-entry-with-id ,issue-id-var)))
-             (unless p (error "Issue %s not found!" ,issue-id-var))
-             (goto-char p)
-             (org-narrow-to-subtree)
-             ,@body))))))
-
-(defmacro ensure-on-issue-id-with-filename (issue-id filename &rest body)
-  "Just do some work on ISSUE-ID, execute BODY."
-  (declare (debug t)
-           (indent 1))
-  (let ((issue-id-var (make-symbol "issue-id"))
-        (filename-var (make-symbol "filename")))
-    `(let* ((,issue-id-var ,issue-id)
-            (,filename-var ,filename)
-            (proj-key ,filename-var)
-            (project-file (org-jira--get-project-file-name proj-key))
-            (project-buffer (or (find-buffer-visiting project-file)
-                                (find-file project-file))))
-       (with-current-buffer project-buffer
-         (org-jira-freeze-ui
-           (let ((p (org-find-entry-with-id ,issue-id-var)))
-             (unless p (error "Issue %s not found!" ,issue-id-var))
-             (goto-char p)
-             (org-narrow-to-subtree)
-             ,@body))))))
-
-(defmacro ensure-on-issue-Issue (Issue &rest body)
-  "Just do some work on ISSUE, execute BODY."
-  (declare (debug t)
-           (indent 1))
-  (let ((Issue-var (make-symbol "Issue")))
-    `(let ((,Issue-var ,Issue))
-         (with-slots (issue-id) ,Issue-var
-           (let* ((proj-key (org-jira--get-proj-key-from-issue ,Issue-var))
-                  (project-file (org-jira--get-project-file-name proj-key))
-                  (project-buffer (or (find-buffer-visiting project-file)
-                                      (find-file project-file))))
-             (with-current-buffer project-buffer
-               (org-jira-freeze-ui
-                 (let ((p (org-find-entry-with-id issue-id)))
-                   (unless p (error "Issue %s not found!" issue-id))
-                   (goto-char p)
-                   (org-narrow-to-subtree)
-                   ,@body))))))))
-
-(defmacro ensure-on-todo (&rest body)
-  "Make sure we are on an todo heading, before executing BODY."
-  (declare (debug t)
-           (indent 0))
-  `(save-excursion
-     (save-restriction
-       (let ((continue t)
-             (on-todo nil))
-         (while continue
-           (when (org-get-todo-state)
-             (setq continue nil on-todo t))
-           (unless (and continue (org-up-heading-safe))
-             (setq continue nil)))
-         (if (not on-todo)
-             (error "TODO not found")
-           (org-narrow-to-subtree)
-           ,@body)))))
-
-(defmacro ensure-on-comment (&rest body)
-  "Make sure we are on a comment heading, before executing BODY."
-  (declare (debug t)
-           (indent 0))
-  `(save-excursion
-     (org-back-to-heading)
-     (forward-thing 'whitespace)
-     (unless (looking-at "Comment:")
-       (error "Not on a comment region!"))
-     (save-restriction
-       (org-narrow-to-subtree)
-       ,@body)))
-
-(defmacro ensure-on-worklog (&rest body)
-  "Make sure we are on a worklog heading, before executing BODY."
-  (declare (debug t)
-           (indent 0))
-  `(save-excursion
-     (org-back-to-heading)
-     (forward-thing 'whitespace)
-     (unless (looking-at "Worklog:")
-       (error "Not on a worklog region!"))
-     (save-restriction
-       (org-narrow-to-subtree)
-       ,@body)))
-
-(defun org-jira--ensure-working-dir ()
-  "Ensure that the org-jira-working-dir exists"
-  (unless (file-exists-p org-jira-working-dir)
-    (error (format "org-jira directory does not exist! Run (make-directory \"%s\")" org-jira-working-dir))
-    )
-  org-jira-working-dir
-  )
-
-(defvar org-jira-entry-mode-map
-  (let ((org-jira-map (make-sparse-keymap)))
-    (define-key org-jira-map (kbd "C-c pg") 'org-jira-get-projects)
-    (define-key org-jira-map (kbd "C-c bg") 'org-jira-get-boards)
-    (define-key org-jira-map (kbd "C-c iv") 'org-jira-get-issues-by-board)
-    (define-key org-jira-map (kbd "C-c ib") 'org-jira-browse-issue)
-    (define-key org-jira-map (kbd "C-c ig") 'org-jira-get-issues)
-    (define-key org-jira-map (kbd "C-c ij") 'org-jira-get-issues-from-custom-jql)
-    (define-key org-jira-map (kbd "C-c ih") 'org-jira-get-issues-headonly)
-    ;;(define-key org-jira-map (kbd "C-c if") 'org-jira-get-issues-from-filter-headonly)
-    ;;(define-key org-jira-map (kbd "C-c iF") 'org-jira-get-issues-from-filter)
-    (define-key org-jira-map (kbd "C-c iu") 'org-jira-update-issue)
-    (define-key org-jira-map (kbd "C-c iw") 'org-jira-progress-issue)
-    (define-key org-jira-map (kbd "C-c in") 'org-jira-progress-issue-next)
-    (define-key org-jira-map (kbd "C-c ia") 'org-jira-assign-issue)
-    ;(define-key org-jira-map (kbd "C-c isr") 'org-jira-set-issue-reporter)
-    (define-key org-jira-map (kbd "C-c ir") 'org-jira-refresh-issue)
-    (define-key org-jira-map (kbd "C-c iR") 'org-jira-refresh-issues-in-buffer)
-    (define-key org-jira-map (kbd "C-c ic") 'org-jira-create-issue)
-    (define-key org-jira-map (kbd "C-c ik") 'org-jira-copy-current-issue-key)
-    (define-key org-jira-map (kbd "C-c sc") 'org-jira-create-subtask)
-    (define-key org-jira-map (kbd "C-c sg") 'org-jira-get-subtasks)
-    (define-key org-jira-map (kbd "C-c cc") 'org-jira-add-comment)
-    (define-key org-jira-map (kbd "C-c cu") 'org-jira-update-comment)
-    (define-key org-jira-map (kbd "C-c wu") 'org-jira-update-worklogs-from-org-clocks)
-    (define-key org-jira-map (kbd "C-c tj") 'org-jira-todo-to-jira)
-    (define-key org-jira-map (kbd "C-c if") 'org-jira-get-issues-by-fixversion)
-    org-jira-map))
-
-;;;###autoload
-(define-minor-mode org-jira-mode
-  "Toggle org-jira mode.
-With no argument, the mode is toggled on/off.
-Non-nil argument turns mode on.
-Nil argument turns mode off.
-
-Commands:
-\\{org-jira-entry-mode-map}
-
-Entry to this mode calls the value of `org-jira-mode-hook'."
-
-  :init-value nil
-  :lighter " jira"
-  :group 'org-jira
-  :keymap org-jira-entry-mode-map
-
-  (if org-jira-mode
-      (progn
-        (set (make-local-variable 'org-element-use-cache) nil)
-        (run-mode-hooks 'org-jira-mode-hook))
-    (progn
-      (kill-local-variable 'org-element-use-cache))))
-
-(defun org-jira-maybe-activate-mode ()
-  "Having hooks can be an expensive operation, and they are invoked each
-time the mode is started - we should only ever re-activate the mode if
-it isn't already on."
-  (unless (bound-and-true-p org-jira-mode) (org-jira-mode t)))
-
-(defun org-jira-get-project-name (proj)
-  (org-jira-find-value proj 'key))
-
-(defun org-jira-find-value (l &rest keys)
-  (let* (key exists)
-    (while (and keys (listp l))
-      (setq key (car keys))
-      (setq exists nil)
-      (mapc (lambda (item)
-              (when (equal key (car item))
-                (setq exists t)))
-            (if (and (listp l)
-                     (listp (car l)))
-                l
-              nil))
-      (setq keys (cdr keys))
-      (if exists
-          (setq l (cdr (assoc key l)))
-        (setq l (or (cdr (assoc key l)) l))))
-    l))
-
-(defun org-jira--get-project-file-name (project-key)
-  "Translate PROJECT-KEY into filename."
-  (-if-let (translation (cdr (assoc project-key org-jira-project-filename-alist)))
-      (expand-file-name translation (org-jira--ensure-working-dir))
-    (expand-file-name (concat project-key ".org") (org-jira--ensure-working-dir))))
-
-(defun org-jira-get-project-lead (proj)
-  (org-jira-find-value proj 'lead 'name))
-
-;; This is mapped to accountId and not username, so we need nil not blank string.
-(defun org-jira-get-assignable-users (project-key)
-  "Get the list of assignable users for PROJECT-KEY, adding user set jira-users first."
-  (append
-   '(("Unassigned" . nil))
-   org-jira-users
-   (mapcar (lambda (user)
-             (cons (org-jira-decode (cdr (assoc 'displayName user)))
-                   (org-jira-decode (cdr (assoc 'accountId user)))))
-           (jiralib-get-users project-key))))
-
-(defun org-jira-get-reporter-candidates (project-key)
-  "Get the list of assignable users for PROJECT-KEY, adding user set jira-users first."
-  (append
-   org-jira-users
-   (mapcar (lambda (user)
-             (cons (org-jira-decode (cdr (assoc 'displayName user)))
-                   (org-jira-decode (cdr (assoc 'accountId user)))))
-           (jiralib-get-users project-key))))
-
-(defun org-jira-entry-put (pom property value)
-  "Similar to org-jira-entry-put, but with an optional alist of overrides.
-
-At point-or-marker POM, set PROPERTY to VALUE.
-
-Look at customizing org-jira-property-overrides if you want
-to change the property names this sets."
-  (unless (stringp property)
-    (setq property (symbol-name property)))
-  (let ((property (or (assoc-default property org-jira-property-overrides)
-                      property)))
-    (org-entry-put pom property (org-jira-decode value))))
-
-(defun org-jira-kill-line ()
-  "Kill the line, without 'kill-line' side-effects of altering kill ring."
-  (interactive)
-  (delete-region (point) (line-end-position)))
-
-;; Appropriated from org.el
-(defun org-jira-org-kill-line (&optional _arg)
-  "Kill line, to tags or end of line."
-  (cond
-   ((or (not org-special-ctrl-k)
-        (bolp)
-        (not (org-at-heading-p)))
-    (when (and (get-char-property (min (point-max) (point-at-eol)) 'invisible)
-               org-ctrl-k-protect-subtree
-               (or (eq org-ctrl-k-protect-subtree 'error)
-                   (not (y-or-n-p "Kill hidden subtree along with headline? "))))
-      (user-error "C-k aborted as it would kill a hidden subtree"))
-    (call-interactively
-     (if (bound-and-true-p visual-line-mode) 'kill-visual-line 'org-jira-kill-line)))
-   ((looking-at ".*?\\S-\\([ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)[ \t]*$")
-    (delete-region (point) (match-beginning 1))
-    (org-set-tags nil t))
-   (t (delete-region (point) (point-at-eol)))))
-
-;;;###autoload
-(defun org-jira-get-projects ()
-  "Get list of projects."
-  (interactive)
-  (let ((projects-file (expand-file-name "projects-list.org" (org-jira--ensure-working-dir))))
-    (or (find-buffer-visiting projects-file)
-        (find-file projects-file))
-    (org-jira-maybe-activate-mode)
-    (save-excursion
-      (let* ((oj-projs (jiralib-get-projects)))
-        (mapc (lambda (proj)
-                (let* ((proj-key (org-jira-find-value proj 'key))
-                       (proj-headline (format "Project: [[file:%s.org][%s]]" proj-key proj-key)))
-                  (save-restriction
-                    (widen)
-                    (goto-char (point-min))
-                    (outline-show-all)
-                    (setq p (org-find-exact-headline-in-buffer proj-headline))
-                    (if (and p (>= p (point-min))
-                             (<= p (point-max)))
-                        (progn
-                          (goto-char p)
-                          (org-narrow-to-subtree)
-                          (end-of-line))
-                      (goto-char (point-max))
-                      (unless (looking-at "^")
-                        (insert "\n"))
-                      (insert "* ")
-                      (org-jira-insert proj-headline)
-                      (org-narrow-to-subtree))
-                    (org-jira-entry-put (point) "name" (org-jira-get-project-name proj))
-                    (org-jira-entry-put (point) "key" (org-jira-find-value proj 'key))
-                    (org-jira-entry-put (point) "lead" (org-jira-get-project-lead proj))
-                    (org-jira-entry-put (point) "ID" (org-jira-find-value proj 'id))
-                    (org-jira-entry-put (point) "url" (format "%s/browse/%s" (replace-regexp-in-string "/*$" "" jiralib-url) (org-jira-find-value proj 'key))))))
-              oj-projs)))))
-
-(defun org-jira-get-issue-components (issue)
-  "Return the components the ISSUE belongs to."
-  (mapconcat
-   (lambda (comp)
-     (org-jira-find-value comp 'name))
-   (org-jira-find-value issue 'fields 'components) ", "))
-
-(defun org-jira-get-issue-labels (issue)
-  "Return the labels the ISSUE belongs to."
-  (org-jira-find-value issue 'fields 'labels))
-
-(defun org-jira-decode (data)
-  "Decode text DATA.
-
-It must receive a coercion to string, as not every time will it
-be populated."
-  (decode-coding-string
-   (cl-coerce data 'string) jiralib-coding-system))
-
-(defun org-jira-insert (&rest args)
-  "Set coding to text provide by `ARGS' when insert in buffer."
-  (insert (org-jira-decode (apply #'concat args))))
-
-(defun org-jira-transform-time-format (jira-time-str)
-  "Convert JIRA-TIME-STR to format \"%Y-%m-%d %T\".
-
-Example: \"2012-01-09T08:59:15.000Z\" becomes \"2012-01-09
-16:59:15\", with the current timezone being +0800."
-  (condition-case ()
-      (format-time-string
-       "%Y-%m-%d %T"
-       (apply
-        'encode-time
-        (parse-time-string (replace-regexp-in-string "T\\|\\.000" " " jira-time-str))))
-    (error jira-time-str)))
-
-(defun org-jira--fix-encode-time-args (arg)
-  "Fix ARG for 3 nil values at the head."
-  (cl-loop
-   for n from 0 to 2 by 1 do
-   (when (not (nth n arg))
-     (setcar (nthcdr n arg) 0)))
-  arg)
-
-(defun org-jira-time-format-to-jira (org-time-str)
-  "Convert ORG-TIME-STR back to jira time format."
-  (condition-case ()
-      (format-time-string
-       "%Y-%m-%dT%T.000Z"
-       (apply 'encode-time
-              (org-jira--fix-encode-time-args (parse-time-string org-time-str)))
-       t)
-    (error org-time-str)))
-
-(defun org-jira-get-comment-val (key comment)
-  "Return the value associated with KEY of COMMENT."
-  (org-jira-get-issue-val key comment))
-
-(defun org-jira-time-stamp-to-org-clock (time-stamp)
-  "Convert TIME-STAMP into org-clock format."
-  (format-time-string "%Y-%m-%d %a %H:%M" time-stamp))
-
-(defun org-jira-date-to-org-clock (date)
-  "Convert DATE into a time stamp and then into org-clock format.
-Expects a date in format such as: 2017-02-26T00:08:00.000-0500."
-  (org-jira-time-stamp-to-org-clock (date-to-time date)))
-
-(defun org-jira-worklogs-to-org-clocks (worklogs)
-  "Get a list of WORKLOGS and convert to org-clocks."
-  (mapcar
-   (lambda (worklog)
-     (let ((wl-start (cdr (assoc 'started worklog)))
-           (wl-time (cdr (assoc 'timeSpentSeconds worklog)))
-           (wl-end))
-       (setq wl-start (org-jira-date-to-org-clock wl-start))
-       (setq wl-end (org-jira-time-stamp-to-org-clock (time-add (date-to-time wl-start) wl-time)))
-       (list
-        wl-start
-        wl-end
-        (cdr (assoc 'comment worklog))
-        (cdr (assoc 'id worklog))
-        )
-       ))
-   worklogs)
-  )
-
-(defun org-jira-format-clock (clock-entry)
-  "Format a CLOCK-ENTRY given the (list start end).
-This format is typically generated from org-jira-worklogs-to-org-clocks call."
-  (format "CLOCK: [%s]--[%s]" (car clock-entry) (cadr clock-entry)))
-
-(defun org-jira-insert-clock (clock-entry)
-  "Insert a CLOCK-ENTRY given the (list start end).
-This format is typically generated from org-jira-worklogs-to-org-clocks call."
-  (insert (org-jira-format-clock clock-entry))
-  (org-beginning-of-line)
-  (org-ctrl-c-ctrl-c) ;; @TODO Maybe not call directly?  does it matter? - used to resync the clock estimate
-  (org-end-of-line)
-  (insert "\n")
-  (insert (format "  :id: %s\n" (cadddr clock-entry)))
-  (when (caddr clock-entry) (insert (replace-regexp-in-string "^\\*" "-" (format "  %s\n" (org-jira-decode (caddr clock-entry)))))) ;; No comment is nil, so don't print it
-  )
-
-(defun org-jira-logbook-reset (issue-id filename &optional clocks)
-  "Find logbook for ISSUE-ID in FILENAME, delete it.
-Re-create it with CLOCKS.  This is used for worklogs."
-  (interactive)
-  (let ((existing-logbook-p nil))
-    ;; See if the LOGBOOK already exists or not.
-    (ensure-on-issue-id-with-filename issue-id filename
-      (let ((drawer-name (or (org-clock-drawer-name) "LOGBOOK")))
-        (when (search-forward (format ":%s:" drawer-name) nil 1 1)
-          (setq existing-logbook-p t))))
-    (ensure-on-issue-id-with-filename issue-id filename
-      (let ((drawer-name (or (org-clock-drawer-name) "LOGBOOK")))
-        (if existing-logbook-p
-            (progn ;; If we had a logbook, drop it and re-create in a bit.
-              (search-forward (format ":%s:" drawer-name) nil 1 1)
-              (org-beginning-of-line)
-              (delete-region (point) (search-forward ":END:" nil 1 1))
-              )
-          (progn ;; Otherwise, create a new one at the end of properties list
-            (search-forward ":END:" nil 1 1)
-            (forward-line)))
-        (org-insert-drawer nil (format "%s" drawer-name)) ;; Doc says non-nil, but this requires nil
-        (mapc #'org-jira-insert-clock clocks)
-        ;; Clean up leftover newlines (we left 2 behind)
-        (dotimes (n 2)
-          (search-forward-regexp "^$" nil 1 1)
-          (delete-region (point) (min (point-max) (1+ (point)))))))))
-
-(defun org-jira-get-worklog-val (key WORKLOG)
-  "Return the value associated with KEY of WORKLOG."
-  (org-jira-get-comment-val key WORKLOG))
-
-(defun org-jira-get-issue-val (key issue)
-  "Return the value associated with key KEY of issue ISSUE."
-  (let ((tmp  (or (org-jira-find-value issue 'fields key 'key) ""))) ;; For project, we need a key, not the name...
-    (unless (stringp tmp)
-      (setq tmp (org-jira-find-value issue key)))
-    (unless (stringp tmp)
-      (setq tmp (org-jira-find-value issue 'fields key 'displayName)))
-    (unless (stringp tmp)
-      (setq tmp ""))
-    (cond ((eq key 'components)
-           (org-jira-get-issue-components issue))
-          ((eq key 'labels)
-           (org-jira-get-issue-labels issue))
-          ((member key '(created updated startDate))
-           (org-jira-transform-time-format tmp))
-          ((eq key 'status)
-           (if jiralib-use-restapi
-               (org-jira-find-value issue 'fields 'status 'name)
-             (org-jira-find-value (jiralib-get-statuses) tmp)))
-          ((eq key 'resolution)
-           (if jiralib-use-restapi
-               tmp
-             (if (string= tmp "")
-                 ""
-               (org-jira-find-value (jiralib-get-resolutions) tmp))))
-          ((eq key 'type)
-           (if jiralib-use-restapi
-               (org-jira-find-value issue 'fields 'issuetype 'name)
-             (org-jira-find-value (jiralib-get-issue-types) tmp)))
-          ((eq key 'priority)
-           (if jiralib-use-restapi
-               (org-jira-find-value issue 'fields 'priority 'name)
-             (org-jira-find-value (jiralib-get-priorities) tmp)))
-          ((eq key 'description)
-           (org-trim tmp))
-          (t
-           tmp))))
-
-(defvar org-jira-jql-history nil)
-
-;;;###autoload
-(defun org-jira-get-issue-list (&optional callback)
-  "Get list of issues, using jql (jira query language), invoke CALLBACK after.
-
-Default is unresolved issues assigned to current login user; with
-a prefix argument you are given the chance to enter your own
-jql."
-  (org-jira-log (format "I was called, was it with a callback? %s" (if callback "yes" "no")))
-  (let ((jql org-jira-default-jql))
-    (when current-prefix-arg
-      (setq jql (read-string "Jql: "
-                             (if org-jira-jql-history
-                                 (car org-jira-jql-history)
-                               "assignee = currentUser() and resolution = unresolved")
-                             'org-jira-jql-history
-                             "assignee = currentUser() and resolution = unresolved")))
-    (list (jiralib-do-jql-search jql nil callback))))
-
-(defun org-jira-get-issue-by-id (id)
-  "Get an issue by its ID."
-  (push id org-jira-issue-id-history)
-  (let ((jql (format "id = %s" id)))
-    (jiralib-do-jql-search jql)))
-
-(defun org-jira-get-issue-by-fixversion (fixversion-id)
-  "Get an issue by its FIXVERSION-ID."
-  (push fixversion-id org-jira-fixversion-id-history)
-  (let ((jql (format "fixVersion = \"%s\""  fixversion-id)))
-    (jiralib-do-jql-search jql)))
-
-;;;###autoload
-(defun org-jira-get-summary ()
-  "Get issue summary from point and place next to issue id from jira"
-  (interactive)
-  (let ((jira-id (thing-at-point 'symbol)))
-    (unless jira-id (error "ORG_JIRA_ERROR: JIRA-ID missing in org-jira-get-summary!"))
-    (forward-symbol 1)
-    (insert (format " - %s"
-                    (cdr (assoc 'summary (assoc 'fields (car (org-jira-get-issue-by-id jira-id)))))))))
-
-;;;###autoload
-(defun org-jira-get-summary-url ()
-  "Get issue summary from point and place next to issue id from jira, and make issue id a link"
-  (interactive)
-  (let ((jira-id (thing-at-point 'symbol)))
-    (insert (format "[[%s][%s]] - %s"
-                    (concatenate 'string jiralib-url "browse/" jira-id) jira-id
-                    (cdr (assoc 'summary (car (org-jira-get-issue-by-id jira-id))))))))
-
-;;;###autoload
-(defun org-jira-get-issues-headonly (issues)
-  "Get list of ISSUES, head only.
-
-The default behavior is to return issues assigned to you and unresolved.
-
-With a prefix argument, allow you to customize the jql.  See
-`org-jira-get-issue-list'."
-
-  (interactive
-   (org-jira-get-issue-list))
-
-  (let* ((issues-file (expand-file-name "issues-headonly.org" (org-jira--ensure-working-dir)))
-         (issues-headonly-buffer (or (find-buffer-visiting issues-file)
-                                     (find-file issues-file))))
-    (with-current-buffer issues-headonly-buffer
-      (widen)
-      (delete-region (point-min) (point-max))
-
-      (mapc (lambda (issue)
-              (let ((issue-id (org-jira-get-issue-key issue))
-                    (issue-summary (org-jira-get-issue-summary issue)))
-                (org-jira-insert (format "- [jira:%s] %s\n" issue-id issue-summary))))
-            issues))
-    (switch-to-buffer issues-headonly-buffer)))
-
-;;;###autoload
-(defun org-jira-get-issue (id)
-  "Get a JIRA issue, allowing you to enter the issue-id first."
-  (interactive (list (read-string "Issue ID: " "" 'org-jira-issue-id-history)))
-  (org-jira-get-issues (org-jira-get-issue-by-id id))
-  (let ((issue-pos (org-find-entry-with-id id)))
-    (when issue-pos
-      (goto-char issue-pos)
-      (recenter 0))))
-;;;###autoload
-(defun org-jira-get-issues-by-fixversion (fixversion)
-  "Get list of issues by FIXVERSION."
-  (interactive (list (read-string "Fixversion ID: " ""
-                                  'org-jira-fixversion-id-history)))
-  (org-jira-get-issues (org-jira-get-issue-by-fixversion fixversion)))
-
-;;;###autoload
-(defun org-jira-get-issue-project (issue)
-  (org-jira-find-value issue 'fields 'project 'key))
-
-(defun org-jira-get-issue-key (issue)
-  (org-jira-find-value issue 'key))
-
-(defun org-jira-get-issue-summary (issue)
-  (org-jira-find-value issue 'fields 'summary))
-
-(defvar org-jira-get-issue-list-callback
-  (cl-function
-   (lambda (&key data &allow-other-keys)
-     "Callback for async, DATA is the response from the request call.
-
-Will send a list of org-jira-sdk-issue objects to the list printer."
-     (org-jira-log "Received data for org-jira-get-issue-list-callback.")
-     (--> data
-          (org-jira-sdk-path it '(issues))
-          (append it nil)               ; convert the conses into a proper list.
-          org-jira-sdk-create-issues-from-data-list
-          org-jira-get-issues))))
-
-(defvar org-jira-get-sprint-list-callback
-  (cl-function
-   (lambda (&key data &allow-other-keys)
-     "Callback for async, DATA is the response from the request call.
-
-Will send a list of org-jira-sdk-issue objects to the list printer."
-     (org-jira-log "Received data for org-jira-get-sprint-list-callback.")
-     (--> data
-          (org-jira-sdk-path it '(sprint))
-          (append it nil)               ; convert the conses into a proper list.
-          org-jira-sdk-create-issues-from-data-list
-          org-jira-get-issues))))
-
-
-;;;###autoload
-(defun org-jira-get-issues (issues)
-  "Get list of ISSUES into an org buffer.
-
-Default is get unfinished issues assigned to you, but you can
-customize jql with a prefix argument.
-See`org-jira-get-issue-list'"
-  ;; If the user doesn't provide a default, async call to build an issue list
-  ;; from the JQL style query
-  (interactive
-   (org-jira-get-issue-list org-jira-get-issue-list-callback))
-  (org-jira-log "Fetching issues...")
-  (when (> (length issues) 0)
-    (org-jira--render-issues-from-issue-list issues)))
-
-(defvar org-jira-original-default-jql nil)
-
-(defun org-jira-get-issues-from-custom-jql-callback (filename list)
-  "Generate a function that we can iterate over FILENAME and LIST with when callback finishes."
-  (cl-function
-   (lambda (&key data &allow-other-keys)
-     "Callback for async, DATA is the response from the request call.
-
-Will send a list of org-jira-sdk-issue objects to the list printer."
-     (org-jira-log "Received data for org-jira-get-issues-from-custom-jql-callback.")
-     (--> data
-          (org-jira-sdk-path it '(issues))
-          (append it nil)     ; convert the conses into a proper list.
-          (org-jira-sdk-create-issues-from-data-list-with-filename filename it)
-          org-jira-get-issues)
-     (setq org-jira-proj-key-override nil)
-     (let ((next (rest list)))
-       (when next
-         (org-jira-get-issues-from-custom-jql next))))))
-
-;;;###autoload
-(defun org-jira-get-issues-from-custom-jql (&optional jql-list)
-  "Get JQL-LIST list of issues from a custom JQL and PROJ-KEY.
-
-The PROJ-KEY will act as the file name, while the JQL will be any
-valid JQL to populate a file to store PROJ-KEY results in.
-
-Please note that this is *not* concurrent or race condition
-proof.  If you try to run multiple calls to this function, it
-will mangle things badly, as they rely on globals DEFAULT-JQL and
-ORG-JIRA-PROJ-KEY-OVERRIDE being set before and after running."
-  (interactive)
-  (let* ((jl (or jql-list org-jira-custom-jqls))
-         (uno (car jl))
-         (filename (cl-getf uno :filename))
-         (limit (cl-getf uno :limit))
-         (jql (replace-regexp-in-string "[\n]" " " (cl-getf uno :jql))))
-    (setq org-jira-proj-key-override filename)
-    (jiralib-do-jql-search jql limit (org-jira-get-issues-from-custom-jql-callback filename jl))))
-
-(defun org-jira--get-project-buffer (Issue)
-  (let* ((proj-key (org-jira--get-proj-key-from-issue Issue))
-         (project-file (org-jira--get-project-file-name proj-key))
-         (project-buffer (find-file-noselect project-file)))
-    project-buffer))
-
-(defun org-jira--is-top-headline? (proj-key)
-  "For PROJ-KEY, check if it is a top headline or not."
-  (let ((elem (org-element-at-point)))
-    (and (eq 'headline (car elem))
-         (equal (format "%s-Tickets" proj-key)
-                (org-element-property :title elem))
-         (= 1 (org-element-property :level elem)))))
-
-(defun org-jira--maybe-render-top-heading (proj-key)
-  "Ensure that there is a headline for PROJ-KEY at the top of the file."
-  (goto-char (point-min))
-  (let ((top-heading (format ".*%s-Tickets" proj-key))
-        (th-found? nil))
-    (while (and (not (eobp))
-                (not th-found?))
-      (beginning-of-line)
-      (when (org-jira--is-top-headline? proj-key) (setq th-found? t))
-      (re-search-forward top-heading nil 1 1))
-    (beginning-of-line)
-    (unless (looking-at top-heading)
-      (insert (format "\n* %s-Tickets\n" proj-key)))))
-
-(defun org-jira--render-issue (Issue)
-  "Render single ISSUE."
-;;  (org-jira-log "Rendering issue from issue list")
-;;  (org-jira-log (org-jira-sdk-dump Issue))
-  (with-slots (filename proj-key issue-id summary status priority headline id) Issue
-    (let (p)
-      (with-current-buffer (org-jira--get-project-buffer Issue)
-        (org-jira-freeze-ui
-          (org-jira-maybe-activate-mode)
-          (org-jira--maybe-render-top-heading proj-key)
-          (setq p (org-find-entry-with-id issue-id))
-          (save-restriction
-            (if (and p (>= p (point-min))
-                     (<= p (point-max)))
-                (progn
-                  (goto-char p)
-                  (forward-thing 'whitespace)
-                  (org-jira-kill-line))
-              (goto-char (point-max))
-              (unless (looking-at "^")
-                (insert "\n"))
-              (insert "** "))
-            (org-jira-insert
-             (concat (org-jira-get-org-keyword-from-status status)
-                     " "
-                     (org-jira-get-org-priority-cookie-from-issue priority)
-                     headline))
-            (save-excursion
-              (unless (search-forward "\n" (point-max) 1)
-                (insert "\n")))
-            (org-narrow-to-subtree)
-            (save-excursion
-              (org-back-to-heading t)
-              (org-set-tags-to (replace-regexp-in-string "-" "_" issue-id)))
-            (org-jira-entry-put (point) "assignee" (or (slot-value Issue 'assignee) "Unassigned"))
-            (mapc (lambda (entry)
-                    (let ((val (slot-value Issue entry)))
-                      (when (and val (not (string= val "")))
-                        (org-jira-entry-put (point) (symbol-name entry) val))))
-                  '(filename reporter type type-id priority labels resolution status components created updated sprint))
-
-            (org-jira-entry-put (point) "ID" issue-id)
-            (org-jira-entry-put (point) "CUSTOM_ID" issue-id)
-
-            ;; Insert the duedate as a deadline if it exists
-            (when org-jira-deadline-duedate-sync-p
-              (let ((duedate (oref Issue duedate)))
-                (when (> (length duedate) 0)
-                  (org-deadline nil duedate))))
-
-            (mapc
-             (lambda (heading-entry)
-               (ensure-on-issue-id-with-filename issue-id filename
-                                                 (let* ((entry-heading
-                                                         (concat (symbol-name heading-entry)
-                                                                 (format ": [[%s][%s]]"
-                                                                         (concat jiralib-url "/browse/" issue-id) issue-id))))
-                                                   (setq p (org-find-exact-headline-in-buffer entry-heading))
-                                                   (if (and p (>= p (point-min))
-                                                            (<= p (point-max)))
-                                                       (progn
-                                                         (goto-char p)
-                                                         (org-narrow-to-subtree)
-                                                         (goto-char (point-min))
-                                                         (forward-line 1)
-                                                         (delete-region (point) (point-max)))
-                                                     (if (org-goto-first-child)
-                                                         (org-insert-heading)
-                                                       (goto-char (point-max))
-                                                       (org-insert-subheading t))
-                                                     (org-jira-insert entry-heading "\n"))
-
-                                                   ;;  Insert 2 spaces of indentation so Jira markup won't cause org-markup
-                                                   (org-jira-insert
-                                                    (replace-regexp-in-string
-                                                     "^" "  "
-                                                     (format "%s" (slot-value Issue heading-entry)))))))
-             '(description))
-
-            (when org-jira-download-comments
-              (org-jira-update-comments-for-issue Issue)
-
-              ;; FIXME: Re-enable when attachments are not erroring.
-              ;;(org-jira-update-attachments-for-current-issue)
-              )
-
-            ;; only sync worklog clocks when the user sets it to be so.
-            (when org-jira-worklog-sync-p
-              (org-jira-update-worklogs-for-issue issue-id filename))))))))
-
-(defun org-jira--render-issues-from-issue-list (Issues)
-  "Add the issues from ISSUES list into the org file(s).
-
-ISSUES is a list of `org-jira-sdk-issue' records."
-  ;; FIXME: Some type of loading error - the first async callback does not know about
-  ;; the issues existing as a class, so we may need to instantiate here if we have none.
-  (when (eq 0 (->> Issues (cl-remove-if-not #'org-jira-sdk-isa-issue?) length))
-    (setq Issues (org-jira-sdk-create-issues-from-data-list Issues)))
-
-  ;; First off, we never ever want to run on non-issues, so check our types early.
-  (setq Issues (cl-remove-if-not #'org-jira-sdk-isa-issue? Issues))
-  (org-jira-log (format "About to render %d issues." (length Issues)))
-
-  ;; If we have any left, we map over them.
-  (mapc 'org-jira--render-issue Issues)
-
-  ;; Prior text: "Oh, are you the culprit?" - Not sure if this caused an issue at some point.
-  ;; We want to ensure we fix broken org narrowing though, by doing org-show-all and then org-cycle.
-  (switch-to-buffer (org-jira--get-project-buffer (-last-item Issues)))
-  (org-show-all)
-  (org-cycle))
-
-;;;###autoload
-(defun org-jira-update-comment ()
-  "Update a comment for the current issue."
-  (interactive)
-  (let* ((issue-id (org-jira-get-from-org 'issue 'key)) ; Really the key
-         (filename (org-jira-filename))
-         (comment-id (org-jira-get-from-org 'comment 'id))
-         (comment (replace-regexp-in-string "^  " "" (org-jira-get-comment-body comment-id))))
-    (lexical-let ((issue-id issue-id)
-                  (filename filename))
-      (let ((callback-edit
-             (cl-function
-              (lambda (&key _data &allow-other-keys)
-                (ensure-on-issue-id-with-filename
-                    issue-id filename
-                    (org-jira-update-comments-for-current-issue)))))
-            (callback-add
-             (cl-function
-              (lambda (&key _data &allow-other-keys)
-                (ensure-on-issue-id-with-filename
-                    issue-id filename
-                    ;; @TODO :optim: Has to be a better way to do this
-                    ;; than delete region (like update the unmarked
-                    ;; one)
-                    (org-jira-delete-current-comment)
-                    (org-jira-update-comments-for-current-issue))))))
-        (if comment-id
-            (jiralib-edit-comment issue-id comment-id comment callback-edit)
-          (jiralib-add-comment issue-id comment callback-add))))))
-
-(defun org-jira-add-comment (issue-id filename comment)
-  "For ISSUE-ID in FILENAME, add a new COMMENT string to the issue region."
-  (interactive
-   (let* ((issue-id (org-jira-get-from-org 'issue 'id))
-          (filename (org-jira-filename))
-          (comment (read-string (format  "Comment (%s): " issue-id))))
-     (list issue-id filename comment)))
-  (lexical-let ((issue-id issue-id)
-                (filename filename))
-    (ensure-on-issue-id-with-filename issue-id filename
-      (goto-char (point-max))
-      (jiralib-add-comment
-       issue-id comment
-       (cl-function
-        (lambda (&key _data &allow-other-keys)
-          (ensure-on-issue-id-with-filename issue-id filename
-            (org-jira-update-comments-for-current-issue))))))))
-
-(defun org-jira-org-clock-to-date (org-time)
-  "Convert ORG-TIME formatted date into a plain date string."
-  (format-time-string
-   "%Y-%m-%dT%H:%M:%S.000%z"
-   (date-to-time org-time)))
-
-(defun org-jira-worklog-time-from-org-time (org-time)
-  "Take in an ORG-TIME and convert it into the portions of a worklog time.
-Expects input in format such as: [2017-04-05 Wed 01:00]--[2017-04-05 Wed 01:46] =>  0:46"
-  (let ((start (replace-regexp-in-string "^\\[\\(.*?\\)\\].*" "\\1" org-time))
-        (end (replace-regexp-in-string ".*--\\[\\(.*?\\)\\].*" "\\1" org-time)))
-    `((started . ,(org-jira-org-clock-to-date start))
-      (time-spent-seconds . ,(time-to-seconds
-                              (time-subtract
-                               (date-to-time end)
-                               (date-to-time start)))))))
-
-(defun org-jira-org-clock-to-jira-worklog (org-time clock-content)
-  "Given ORG-TIME and CLOCK-CONTENT, format a jira worklog entry."
-  (let ((lines (split-string clock-content "\n"))
-        worklog-id)
-    ;; See if we look like we have an id
-    (when (string-match ":id:" (first lines))
-      (setq worklog-id
-            (replace-regexp-in-string "^.*:id: \\([0-9]*\\)$" "\\1" (first lines)))
-      (when (> (string-to-number worklog-id) 0) ;; pop off the first id line if we found it valid
-        (setq lines (cdr lines))))
-    (setq lines (reverse (cdr (reverse lines)))) ;; drop last line
-    (let ((comment (org-trim (mapconcat 'identity lines "\n")))
-          (worklog-time (org-jira-worklog-time-from-org-time org-time)))
-      `((worklog-id . ,worklog-id)
-        (comment . ,comment)
-        (started . ,(cdr (assoc 'started worklog-time)))
-        (time-spent-seconds . ,(cdr (assoc 'time-spent-seconds worklog-time)))
-        ))))
-
-(defun org-jira-worklog-to-hashtable (issue-id)
-  "Given ISSUE-ID, return a hashtable of worklog-id -> jira worklog."
-  (let ((worklog-hashtable (make-hash-table :test 'equal)))
-    (mapc
-     (lambda (worklog)
-       (let ((worklog-id (cdr (assoc 'id worklog))))
-         (puthash worklog-id worklog worklog-hashtable)))
-     (jiralib-worklog-import--filter-apply
-      (org-jira-find-value
-       (jiralib-get-worklogs
-        issue-id)
-       'worklogs)))
-    worklog-hashtable))
-
-;;;###autoload
-(defun org-jira-update-worklogs-from-org-clocks ()
-  "Update or add a worklog based on the org clocks."
-  (interactive)
-  (let* ((issue-id (org-jira-get-from-org 'issue 'key))
-         (filename (org-jira-filename))
-         ;; Fetch all workflogs for this issue
-         (jira-worklogs-ht (org-jira-worklog-to-hashtable issue-id)))
-    (org-jira-log (format "About to sync worklog for issue: %s in file: %s"
-                  issue-id filename))
-    (ensure-on-issue-id-with-filename issue-id filename
-      (search-forward (format ":%s:" (or (org-clock-drawer-name) "LOGBOOK"))  nil 1 1)
-      (org-beginning-of-line)
-      ;; (org-cycle 1)
-      (while (search-forward "CLOCK: " nil 1 1)
-        (let ((org-time (buffer-substring-no-properties (point) (point-at-eol))))
-          (forward-line)
-          ;; See where the stuff ends (what point)
-          (let (next-clock-point)
-            (save-excursion
-              (search-forward-regexp "\\(CLOCK\\|:END\\):" nil 1 1)
-              (setq next-clock-point (point)))
-            (let ((clock-content
-                   (buffer-substring-no-properties (point) next-clock-point)))
-              ;; Update via jiralib call
-              (let* ((worklog (org-jira-org-clock-to-jira-worklog org-time clock-content))
-                     (comment-text (cdr (assoc 'comment worklog)))
-                     (comment-text (if (string= (org-trim comment-text) "") nil comment-text)))
-                (if (cdr (assoc 'worklog-id worklog))
-                    ;; If there is a worklog in jira for this ID, check if the worklog has changed.
-                    ;; If it has changed, update the worklog.
-                    ;; If it has not changed, skip.
-                    (let ((jira-worklog (gethash (cdr (assoc 'worklog-id worklog)) jira-worklogs-ht)))
-                      (when (and jira-worklog
-                                 ;; Check if the entries are differing lengths.
-                                 (or (not (= (cdr (assoc 'timeSpentSeconds jira-worklog))
-                                         (cdr (assoc 'time-spent-seconds worklog))))
-                                 ;; Check if the entries start at different times.
-                                     (not (string= (cdr (assoc 'started jira-worklog))
-                                               (cdr (assoc 'started worklog))))))
-                        (jiralib-update-worklog
-                         issue-id
-                         (cdr (assoc 'worklog-id worklog))
-                         (cdr (assoc 'started worklog))
-                         (cdr (assoc 'time-spent-seconds worklog))
-                         comment-text
-                         nil))) ; no callback - synchronous
-                  ;; else
-                  (jiralib-add-worklog
-                   issue-id
-                   (cdr (assoc 'started worklog))
-                   (cdr (assoc 'time-spent-seconds worklog))
-                   comment-text
-                   nil) ; no callback - synchronous
-                  )
-                )))))
-      (org-jira-log (format "Updating worklog from org-jira-update-worklogs-from-org-clocks call"))
-      (org-jira-update-worklogs-for-issue issue-id filename)
-      )))
-
-(defun org-jira-update-worklog ()
-  "Update a worklog for the current issue."
-  (interactive)
-  (error "Deprecated, use org-jira-update-worklogs-from-org-clocks instead!")
-  (let* ((issue-id (org-jira-get-from-org 'issue 'key))
-         (worklog-id (org-jira-get-from-org 'worklog 'id))
-         (timeSpent (org-jira-get-from-org 'worklog 'timeSpent))
-         (timeSpent (if timeSpent
-                        timeSpent
-                      (read-string "Input the time you spent (such as 3w 1d 2h): ")))
-         (timeSpent (replace-regexp-in-string " \\(\\sw\\)\\sw*\\(,\\|$\\)" "\\1" timeSpent))
-         (startDate (org-jira-get-from-org 'worklog 'startDate))
-         (startDate (if startDate
-                        startDate
-                      (org-read-date nil nil nil "Input when did you start")))
-         (startDate (org-jira-time-format-to-jira startDate))
-         (comment (replace-regexp-in-string "^  " "" (org-jira-get-worklog-comment worklog-id)))
-         (worklog `((comment . ,comment)
-                    (timeSpent . ,timeSpent)
-                    (timeSpentInSeconds . 10)
-                    (startDate . ,startDate)))
-         (worklog (if worklog-id
-                      (cons `(id . ,(replace-regexp-in-string "^worklog-" "" worklog-id)) worklog)
-                    worklog)))
-    (if worklog-id
-        (jiralib-update-worklog worklog)
-      (jiralib-add-worklog-and-autoadjust-remaining-estimate issue-id startDate timeSpent comment))
-    (org-jira-delete-current-worklog)
-    (org-jira-update-worklogs-for-current-issue)))
-
-(defun org-jira-delete-current-comment ()
-  "Delete the current comment."
-  (ensure-on-comment
-   (delete-region (point-min) (point-max))))
-
-(defun org-jira-delete-current-worklog ()
-  "Delete the current worklog."
-  (ensure-on-worklog
-   (delete-region (point-min) (point-max))))
-
-;;;###autoload
-(defun org-jira-copy-current-issue-key ()
-  "Copy the current issue's key into clipboard."
-  (interactive)
-  (let ((issue-id (org-jira-get-from-org 'issue 'key)))
-    (with-temp-buffer
-      (insert issue-id)
-      (kill-region (point-min) (point-max)))))
-
-(defun org-jira-get-comment-id (comment)
-  (org-jira-find-value comment 'id))
-
-(defun org-jira-get-comment-author (comment)
-  (org-jira-find-value comment 'author 'displayName))
-
-(defun org-jira-isa-ignored-comment? (comment)
-  (member-ignore-case (oref comment author) org-jira-ignore-comment-user-list))
-
-(defun org-jira-maybe-reverse-comments (comments)
-  (if org-jira-reverse-comment-order (reverse comments) comments))
-
-(defun org-jira-extract-comments-from-data (data)
-  (->> (append data nil)
-       org-jira-sdk-create-comments-from-data-list
-       org-jira-maybe-reverse-comments
-       (cl-remove-if #'org-jira-isa-ignored-comment?)))
-
-(defun org-jira--render-comment (Issue Comment)
-  (with-slots (issue-id) Issue
-    (with-slots (comment-id author headline created updated body) Comment
-      (org-jira-log (format "Rendering a comment: %s" body))
-      (ensure-on-issue-Issue Issue
-        (setq p (org-find-entry-with-id comment-id))
-        (when (and p (>= p (point-min))
-                   (<= p (point-max)))
-          (goto-char p)
-          (org-narrow-to-subtree)
-          (delete-region (point-min) (point-max)))
-        (goto-char (point-max))
-        (unless (looking-at "^")
-          (insert "\n"))
-        (insert "*** ")
-        (org-jira-insert headline "\n")
-        (org-narrow-to-subtree)
-        (org-jira-entry-put (point) "ID" comment-id)
-        (org-jira-entry-put (point) "created" created)
-        (unless (string= created updated)
-          (org-jira-entry-put (point) "updated" updated))
-        (goto-char (point-max))
-        ;;  Insert 2 spaces of indentation so Jira markup won't cause org-markup
-        (org-jira-insert (replace-regexp-in-string "^" "  " (or body "")))))))
-
-(defun org-jira-update-comments-for-issue (Issue)
-  "Update the comments for the specified ISSUE issue."
-  (with-slots (issue-id) Issue
-    (jiralib-get-comments
-     issue-id
-     (org-jira-with-callback
-       (org-jira-log "In the callback for org-jira-update-comments-for-issue.")
-       (-->
-        (org-jira-find-value cb-data 'comments)
-        (org-jira-extract-comments-from-data it)
-        (mapc (lambda (Comment) (org-jira--render-comment Issue Comment)) it))))))
-
-(defun org-jira-update-comments-for-current-issue ()
-  "Update comments for the current issue."
-  (org-jira-log "About to update comments for current issue.")
-  (let ((Issue (make-instance 'org-jira-sdk-issue
-                              :issue-id (org-jira-get-from-org 'issue 'key)
-                              :filename (org-jira-filename))))
-    (-> Issue org-jira-update-comments-for-issue)))
-
-(defun org-jira-delete-subtree ()
-  "Derived from org-cut-subtree.
-
-Like that function, without mangling the user's clipboard for the
-purpose of wiping an old subtree."
-  (let (beg end folded (beg0 (point)))
-    (org-back-to-heading t) ; take what is really there
-    (setq beg (point))
-    (skip-chars-forward " \t\r\n")
-    (save-match-data
-      (save-excursion (outline-end-of-heading)
-                      (setq folded (org-invisible-p))
-                      (org-end-of-subtree t t)))
-    ;; Include the end of an inlinetask
-    (when (and (featurep 'org-inlinetask)
-               (looking-at-p (concat (org-inlinetask-outline-regexp)
-                                     "END[ \t]*$")))
-      (end-of-line))
-    (setq end (point))
-    (goto-char beg0)
-    (when (> end beg)
-      (setq org-subtree-clip-folded folded)
-      (org-save-markers-in-region beg end)
-      (delete-region beg end))))
-
-(defun org-jira-update-attachments-for-current-issue ()
-  "Update the attachments for the current issue."
-  (when jiralib-use-restapi
-    (lexical-let ((issue-id (org-jira-get-from-org 'issue 'key)))
-      ;; Run the call
-      (jiralib-get-attachments
-       issue-id
-       (save-excursion
-         (cl-function
-          (lambda (&key data &allow-other-keys)
-            ;; First, make sure we're in the proper buffer (logic copied from org-jira-get-issues.
-            (let* ((proj-key (replace-regexp-in-string "-.*" "" issue-id))
-                   (project-file (org-jira--get-project-file-name proj-key))
-                   (project-buffer (or (find-buffer-visiting project-file)
-                                       (find-file project-file))))
-              (with-current-buffer project-buffer
-                ;; delete old attachment node
-                (ensure-on-issue
-                  (if (org-goto-first-child)
-                      (while (org-goto-sibling)
-                        (forward-thing 'whitespace)
-                        (when (looking-at "Attachments:")
-                          (org-jira-delete-subtree)))))
-                (let ((attachments (org-jira-find-value data 'fields 'attachment)))
-                  (when (not (zerop (length attachments)))
-                    (ensure-on-issue
-                      (if (org-goto-first-child)
-                          (progn
-                            (while (org-goto-sibling))
-                            (org-insert-heading-after-current))
-                        (org-insert-subheading nil))
-
-                      (insert "Attachments:")
-                      (mapc
-                       (lambda (attachment)
-                         (let ((attachment-id (org-jira-get-comment-id attachment))
-                               (author (org-jira-get-comment-author attachment))
-                               (created (org-jira-transform-time-format
-                                         (org-jira-find-value attachment 'created)))
-                               (size (org-jira-find-value attachment 'size))
-                               (mimeType (org-jira-find-value attachment 'mimeType))
-                               (content (org-jira-find-value attachment 'content))
-                               (filename (org-jira-find-value attachment 'filename)))
-                           (if (looking-back "Attachments:")
-                               (org-insert-subheading nil)
-                             (org-insert-heading-respect-content))
-                           (insert "[[" content "][" filename "]]")
-                           (org-narrow-to-subtree)
-                           (org-jira-entry-put (point) "ID" attachment-id)
-                           (org-jira-entry-put (point) "Author" author)
-                           (org-jira-entry-put (point) "Name" filename)
-                           (org-jira-entry-put (point) "Created" created)
-                           (org-jira-entry-put (point) "Size" (ls-lisp-format-file-size size t))
-                           (org-jira-entry-put (point) "Content" content)
-                           (widen)))
-                       attachments)))))))))))))
-
-(defun org-jira-sort-org-clocks (clocks)
-  "Given a CLOCKS list, sort it by start date descending."
-  ;; Expects data such as this:
-
-  ;; ((\"2017-02-26 Sun 00:08\" \"2017-02-26 Sun 01:08\" \"Hi\" \"10101\")
-  ;;  (\"2017-03-16 Thu 22:25\" \"2017-03-16 Thu 22:57\" \"Test\" \"10200\"))
-  (sort clocks
-        (lambda (a b)
-          (> (time-to-seconds (date-to-time (car a)))
-             (time-to-seconds (date-to-time (car b)))))))
-
-(defun org-jira-update-worklogs-for-current-issue ()
-  "Update the worklogs for the current issue."
-  (let ((issue-id (org-jira-get-from-org 'issue 'key))
-        (filename (org-jira-filename)))
-    (org-jira-update-worklogs-for-issue issue-id filename)))
-
-(defun org-jira-update-worklogs-for-issue (issue-id filename)
-  "Update the worklogs for the current ISSUE-ID located in FILENAME."
-  (org-jira-log (format "org-jira-update-worklogs-for-issue id: %s filename: %s"
-                issue-id filename))
-  ;; Run the call
-  (jiralib-get-worklogs
-   issue-id
-   (org-jira-with-callback
-     (ensure-on-issue-id-with-filename issue-id filename
-       (let ((worklogs (org-jira-find-value cb-data 'worklogs)))
-         (org-jira-log (format "org-jira-update-worklogs-for-issue cb id: %s fn: %s"
-                       issue-id filename))
-         (org-jira-logbook-reset issue-id filename
-          (org-jira-sort-org-clocks (org-jira-worklogs-to-org-clocks
-                                     (jiralib-worklog-import--filter-apply worklogs)))))))))
-
-;;;###autoload
-(defun org-jira-unassign-issue ()
-  "Update an issue to be unassigned."
-  (interactive)
-  (let ((issue-id (org-jira-parse-issue-id))
-        (filename (org-jira-parse-issue-filename)))
-    (org-jira-update-issue-details issue-id filename :assignee nil)))
-
-;;;###autoload
-(defun org-jira-set-issue-reporter ()
-  "Update an issue's reporter interactively."
-  (interactive)
-  (let ((issue-id (org-jira-parse-issue-id))
-        (filename (org-jira-parse-issue-filename)))
-    (if issue-id
-        (let* ((project (replace-regexp-in-string "-[0-9]+" "" issue-id))
-               (jira-users (org-jira-get-reporter-candidates project)) ;; TODO, probably a better option than org-jira-get-assignable-users here
-               (user (completing-read
-                      "Reporter: "
-                      (append (mapcar 'car jira-users)
-                              (mapcar 'cdr jira-users))))
-               (reporter (or
-                          (cdr (assoc user jira-users))
-                          (cdr (rassoc user jira-users)))))
-          (when (null reporter)
-            (error "No reporter found, this should probably never happen."))
-          (org-jira-update-issue-details issue-id filename :reporter (jiralib-get-user-account-id project reporter)))
-      (error "Not on an issue"))))
-
-;;;###autoload
-(defun org-jira-assign-issue ()
-  "Update an issue with interactive re-assignment."
-  (interactive)
-  (let ((issue-id (org-jira-parse-issue-id))
-        (filename (org-jira-parse-issue-filename)))
-    (if issue-id
-        (let* ((project (replace-regexp-in-string "-[0-9]+" "" issue-id))
-               (jira-users (org-jira-get-assignable-users project))
-               (user (completing-read
-                      "Assignee: "
-                      (append (mapcar 'car jira-users)
-                              (mapcar 'cdr jira-users))))
-               (assignee (or
-                          (cdr (assoc user jira-users))
-                          (cdr (rassoc user jira-users)))))
-          (when (null assignee)
-            (error "No assignee found, use org-jira-unassign-issue to make the issue unassigned"))
-          (org-jira-update-issue-details issue-id filename :assignee (jiralib-get-user-account-id project assignee)))
-      (error "Not on an issue"))))
-
-;;;###autoload
-(defun org-jira-update-issue ()
-  "Update an issue."
-  (interactive)
-  (let ((issue-id (org-jira-parse-issue-id))
-        (filename (org-jira-parse-issue-filename)))
-    (if issue-id
-        (org-jira-update-issue-details issue-id filename)
-      (error "Not on an issue"))))
-
-;;;###autoload
-(defun org-jira-todo-to-jira ()
-  "Convert an ordinary todo item to a jira ticket."
-  (interactive)
-  (ensure-on-todo
-   (when (org-jira-parse-issue-id)
-     (error "Already on jira ticket"))
-   (save-excursion (org-jira-create-issue
-                    (org-jira-read-project)
-                    (org-jira-read-issue-type)
-                    (org-get-heading t t)
-                    (org-get-entry)))
-   (delete-region (point-min) (point-max))))
-
-;;;###autoload
-(defun org-jira-get-subtasks ()
-  "Get subtasks for the current issue."
-  (interactive)
-  (ensure-on-issue
-    (org-jira-get-issues-headonly (jiralib-do-jql-search (format "parent = %s" (org-jira-parse-issue-id))))))
-
-(defvar org-jira-project-read-history nil)
-(defvar org-jira-boards-read-history nil)
-(defvar org-jira-sprints-read-history nil)
-(defvar org-jira-components-read-history nil)
-(defvar org-jira-priority-read-history nil)
-(defvar org-jira-type-read-history nil)
-
-(defun org-jira-read-project ()
-  "Read project name."
-  (completing-read
-   "Project: "
-   (jiralib-make-list (jiralib-get-projects) 'key)
-   nil
-   t
-   nil
-   'org-jira-project-read-history
-   (car org-jira-project-read-history)))
-
-(defun org-jira-read-board ()
-  "Read board name. Returns cons pair (name . integer-id)"
-  (let* ((boards-alist
-          (jiralib-make-assoc-list (jiralib-get-boards) 'name 'id))
-         (board-name
-          (completing-read "Boards: "  boards-alist
-                           nil  t  nil
-                           'org-jira-boards-read-history
-                           (car org-jira-boards-read-history))))
-    (assoc board-name boards-alist)))
-
-(defun org-jira-read-sprint (board)
-  "Read sprint name. Returns cons pair (name . integer-id)"
-  (let* ((sprints-alist
-         (jiralib-make-assoc-list (append (alist-get 'values (jiralib-get-board-sprints board)) nil) 'name 'id))
-         (sprint-name
-          (completing-read "Sprints: " sprints-alist
-                           nil t nil
-                           'org-jira-sprints-read-history
-                           (car org-jira-sprints-read-history))))
-       (assoc sprint-name sprints-alist)))
-
-(defun org-jira-read-component (project)
-  "Read the components options for PROJECT such as EX."
-  (completing-read
-   "Components (choose Done to stop): "
-   (append '("Done") (mapcar 'cdr (jiralib-get-components project)))
-   nil
-   t
-   nil
-   'org-jira-components-read-history
-   "Done"))
-
-;; TODO: Finish this feature - integrate into org-jira-create-issue
-(defun org-jira-read-components (project)
-  "Types: string PROJECT : string (csv of components).
-
-Get all the components for the PROJECT such as EX,
-that should be bound to an issue."
-  (let (components component)
-    (while (not (equal "Done" component))
-      (setq component (org-jira-read-component project))
-      (unless (equal "Done" component)
-        (push component components)))
-    components))
-
-(defun org-jira-read-priority ()
-  "Read priority name."
-  (completing-read
-   "Priority: "
-   (mapcar 'cdr (jiralib-get-priorities))
-   nil
-   t
-   nil
-   'org-jira-priority-read-history
-   (car org-jira-priority-read-history)))
-
-(defun org-jira-read-issue-type (&optional project)
-  "Read issue type name.  PROJECT is the optional project key."
-  (let* ((issue-types
-          (mapcar 'cdr (if project
-                           (jiralib-get-issue-types-by-project project)
-                         (jiralib-get-issue-types))))
-         (initial-input (when (member (car org-jira-type-read-history) issue-types)
-                          org-jira-type-read-history)))
-
-    ;; TODO: The completing-read calls as such are all over the place, and always tend
-    ;; to follow this exact same call structure - we should abstract to a single fn
-    ;; that will allow calling with fewer or keyword args
-    (completing-read
-     "Type: "                           ; PROMPT
-     issue-types                        ; COLLECTION
-     nil                                ; PREDICATE
-     t                                  ; REQUIRE-MATCH
-     nil                                ; INITIAL-INPUT
-     'initial-input                     ; HIST
-     (car initial-input))))             ; DEF
-
-(defun org-jira-read-subtask-type ()
-  "Read issue type name."
-  (completing-read
-   "Type: "
-   (mapcar 'cdr (jiralib-get-subtask-types))
-   nil
-   t
-   nil
-   'org-jira-type-read-history
-   (car org-jira-type-read-history)))
-
-(defun org-jira-get-issue-struct (project type summary description &optional parent-id)
-  "Create an issue struct for PROJECT, of TYPE, with SUMMARY and DESCRIPTION."
-  (if (or (equal project "")
-          (equal type "")
-          (equal summary ""))
-      (error "Must provide all information!"))
-  (let* ((project-components (jiralib-get-components project))
-         (jira-users (org-jira-get-assignable-users project))
-         (user (completing-read "Assignee: " (mapcar 'car jira-users)))
-         (priority (car (rassoc (org-jira-read-priority) (jiralib-get-priorities))))
-         (ticket-struct
-          `((fields
-             (project (key . ,project))
-             (parent (key . ,parent-id))
-             (issuetype (id . ,(car (rassoc type (if (and (boundp 'parent-id) parent-id)
-                                                     (jiralib-get-subtask-types)
-                                                   (jiralib-get-issue-types-by-project project))))))
-             (summary . ,(format "%s%s" summary
-                                 (if (and (boundp 'parent-id) parent-id)
-                                     (format " (subtask of [jira:%s])" parent-id)
-                                   "")))
-             (description . ,description)
-             (priority (id . ,priority))
-             ;; accountId should be nil if Unassigned, not the key slot.
-             (assignee (accountId . ,(or (cdr (assoc user jira-users)) nil)))))))
-    ticket-struct))
-
-;;;###autoload
-(defun org-jira-create-issue (project type summary description)
-  "Create an issue in PROJECT, of type TYPE, with given SUMMARY and DESCRIPTION."
-  (interactive
-   (let* ((project (org-jira-read-project))
-          (type (org-jira-read-issue-type project))
-          (summary (read-string "Summary: "))
-          (description (read-string "Description: ")))
-     (list project type summary description)))
-  (if (or (equal project "")
-          (equal type "")
-          (equal summary ""))
-      (error "Must provide all information!"))
-  (let* ((parent-id nil)
-         (ticket-struct (org-jira-get-issue-struct project type summary description)))
-    (org-jira-get-issues (list (jiralib-create-issue ticket-struct)))))
-
-;;;###autoload
-(defun org-jira-create-subtask (project type summary description)
-  "Create a subtask issue for PROJECT, of TYPE, with SUMMARY and DESCRIPTION."
-  (interactive (ensure-on-issue (list (org-jira-read-project)
-                                      (org-jira-read-subtask-type)
-                                      (read-string "Summary: ")
-                                      (read-string "Description: "))))
-  (if (or (equal project "")
-          (equal type "")
-          (equal summary ""))
-      (error "Must provide all information!"))
-  (let* ((parent-id (org-jira-parse-issue-id))
-         (ticket-struct (org-jira-get-issue-struct project type summary description parent-id)))
-    (org-jira-get-issues (list (jiralib-create-subtask ticket-struct)))))
-
-(defun org-jira-get-issue-val-from-org (key)
-  "Return the requested value by KEY from the current issue."
-  ;; There is some odd issue when not using any let-scoping, where myself
-  ;; and an array of users are hitting a snag circa 2023-03-01 time frame
-  ;; in which the setq portion of a when clause is being hit even when it
-  ;; evaluates to false - the bug only manifests on a first launch of Emacs - it
-  ;; doesn't occur when re-evaluating this function.  However, wrapping it "fixes"
-  ;; the issue.
-  ;;
-  ;; The first link has the most troubleshooting/diagnosis around the particulars of
-  ;; this bug.
-  ;;
-  ;; See: https://github.com/ahungry/org-jira/issues/319
-  ;; See: https://github.com/ahungry/org-jira/issues/296
-  ;; See: https://github.com/ahungry/org-jira/issues/316
-  (lexical-let ((my-key key))
-    (ensure-on-issue
-      (cond ((eq my-key 'description)
-             (org-goto-first-child)
-             (forward-thing 'whitespace)
-             (if (looking-at "description: ")
-                 (org-trim (org-get-entry))
-               (error "Can not find description field for this issue")))
-
-            ((eq my-key 'summary)
-             (ensure-on-issue
-               (org-get-heading t t)))
-
-            ;; org returns a time tuple, we need to convert it
-            ((eq my-key 'deadline)
-             (let ((encoded-time (org-get-deadline-time (point))))
-               (when encoded-time
-                 (cl-reduce (lambda (carry segment)
-                              (format "%s-%s" carry segment))
-                            (reverse (cl-subseq (decode-time encoded-time) 3 6))))))
-
-            ;; default case, just grab the value in the properties block
-            (t
-             (when (symbolp my-key)
-               (setq my-key (symbol-name my-key)))
-
-             (setq my-key (or (assoc-default my-key org-jira-property-overrides)
-                              my-key))
-
-             ;; This is the "impossible" to hit setq that somehow gets hit without the let
-             ;; wrapper around the function input args.
-             (when (string= my-key "key")
-               (setq my-key "ID"))
-
-             ;; The variable `org-special-properties' will mess this up
-             ;; if our search, such as 'priority' is within there, so
-             ;; don't bother with it for this (since we only ever care
-             ;; about the local properties, not any hierarchal or special
-             ;; ones).
-             (let ((org-special-properties nil))
-               (or (org-entry-get (point) my-key t)
-                   "")))))))
-
-(defun org-jira-read-action (actions)
-  "Read issue workflow progress ACTIONS."
-  (let ((action (completing-read
-                 "Action: "
-                 (mapcar 'cdr actions)
-                 nil
-                 t
-                 nil)))
-    (or
-     (car (rassoc action actions))
-     (user-error "You specified an empty action, the valid actions are: %s" (mapcar 'cdr actions)))))
-
-(defvar org-jira-fields-history nil)
-(defun org-jira-read-field (fields)
-  "Read (custom) FIELDS for workflow progress."
-  (let ((field-desc (completing-read
-                     "More fields to set: "
-                     (cons "Thanks, no more fields are *required*." (mapcar 'org-jira-decode (mapcar 'cdr fields)))
-                     nil
-                     t
-                     nil
-                     'org-jira-fields-history))
-        field-name)
-    (setq field-name (car (rassoc field-desc fields)))
-    (if field-name
-        (intern field-name)
-      field-name)))
-
-
-(defvar org-jira-rest-fields nil
-  "Extra fields are held here for usage between two endpoints.
-Used in org-jira-read-resolution and org-jira-progress-issue calls.")
-
-(defvar org-jira-resolution-history nil)
-(defun org-jira-read-resolution ()
-  "Read issue workflow progress resolution."
-  (if (not jiralib-use-restapi)
-      (let ((resolution (completing-read
-                         "Resolution: "
-                         (mapcar 'cdr (jiralib-get-resolutions))
-                         nil
-                         t
-                         nil
-                         'org-jira-resolution-history
-                         (car org-jira-resolution-history))))
-        (car (rassoc resolution (jiralib-get-resolutions))))
-    (let* ((resolutions (org-jira-find-value org-jira-rest-fields 'resolution 'allowedValues))
-           (resolution-name (completing-read
-                             "Resolution: "
-                             (mapcar (lambda (resolution)
-                                       (org-jira-find-value resolution 'name))
-                                     resolutions))))
-      (cons 'name resolution-name))))
-
-(defun org-jira-refresh-issues-in-buffer-loose ()
-  "Iterates over all level 1-2 headings in current buffer, refreshing on issue :ID:.
-It differs with `org-jira-refresh-issues-in-buffer' in that it accepts the current buffer
-and its corresponding filename, regardless of whether it has been previously registered
-as an org-jira project file or not."
-  (interactive)
-  (save-excursion
-    (save-restriction
-      (widen)
-      (let* ((org-ids (org-map-entries 'org-id-get "LEVEL=1|LEVEL=2"))
-             (org-ids (delq nil org-ids))
-             (file-name (file-name-sans-extension buffer-file-name)))
-        (mapcar (lambda (org-id) (org-jira--refresh-issue org-id file-name))
-                org-ids)))))
-
-(defun org-jira-refresh-issues-in-buffer ()
-  "Iterate across all level 1-2 headings in current buffer, refreshing on issue :ID:.
-Where issue-id will be something such as \"EX-22\"."
-  (interactive)
-  (save-excursion
-    (save-restriction
-      (widen)
-      (let* ((org-ids (org-map-entries 'org-id-get "LEVEL=1|LEVEL=2"))
-             (org-ids (delq nil org-ids)))
-        ;; It's possible we could be on a non-org-jira headline, but
-        ;; that should be an exceptional case and not necessitating a
-        ;; fix atm.
-        (mapcar 'org-jira--refresh-issue-by-id org-ids)))))
-
-;;;###autoload
-(defun org-jira-refresh-issue ()
-  "Refresh current issue from jira to org."
-  (interactive)
-  (ensure-on-issue
-    (org-jira--refresh-issue (org-jira-id) (org-jira-filename))))
-
-(defun org-jira--refresh-issue (issue-id &optional filename)
-  "Refresh issue from jira to org using ISSUE-ID."
-  (unless filename (setq filename (replace-regexp-in-string "-[0-9]+" "" issue-id)))
-  (jiralib-get-issue
-   issue-id
-   (org-jira-with-callback
-     (org-jira-log (format "Received refresh issue data for id: %s in file: %s" issue-id filename))
-     (--> cb-data
-          list
-          (org-jira-sdk-create-issues-from-data-list-with-filename filename it)
-          org-jira--render-issues-from-issue-list))))
-
-(defun org-jira--refresh-issue-by-id (issue-id)
-  "Refresh issue from jira to org using ISSUE-ID."
-  (ensure-on-issue-id issue-id
-    (org-jira--refresh-issue issue-id)))
-
-(defvar org-jira-fields-values-history nil)
-;;;###autoload
-(defun org-jira-progress-issue ()
-  "Progress issue workflow."
-  (interactive)
-  (ensure-on-issue
-    (let* ((issue-id (org-jira-id))
-           (actions (jiralib-get-available-actions
-                     issue-id
-                     (org-jira-get-issue-val-from-org 'status)))
-           (action (org-jira-read-action actions))
-           (fields (jiralib-get-fields-for-action issue-id action))
-           (org-jira-rest-fields fields)
-           (field-key)
-           (custom-fields-collector nil)
-           (custom-fields
-            (progn
-              ;; delete those elements in fields, which have
-              ;; already been set in custom-fields-collector
-              (while fields
-                (setq fields
-                      (cl-remove-if
-                       (lambda (strstr)
-                         (cl-member-if (lambda (symstr)
-                                         (string= (car strstr)  (symbol-name (car symstr))))
-                                       custom-fields-collector))
-                       fields))
-                (setq field-key (org-jira-read-field fields))
-                (if (not field-key)
-                    (setq fields nil)
-                  (setq custom-fields-collector
-                        (cons
-                         (funcall (if jiralib-use-restapi
-                                      #'list
-                                    #'cons)
-                                  field-key
-                                  (if (eq field-key 'resolution)
-                                      (org-jira-read-resolution)
-                                    (let ((field-value (completing-read
-                                                        (format "Please enter %s's value: "
-                                                                (cdr (assoc (symbol-name field-key) fields)))
-                                                        org-jira-fields-values-history
-                                                        nil
-                                                        nil
-                                                        nil
-                                                        'org-jira-fields-values-history)))
-                                      (if jiralib-use-restapi
-                                          (cons 'name field-value)
-                                        field-value))))
-                         custom-fields-collector))))
-              custom-fields-collector)))
-      (jiralib-progress-workflow-action
-       issue-id
-       action
-       custom-fields
-       (cl-function
-        (lambda (&key data &allow-other-keys)
-          (org-jira-refresh-issue)))))))
-
-(defun org-jira-progress-next-action (actions current-status)
-  "Grab the user defined 'next' action from ACTIONS, given CURRENT-STATUS."
-  (let* ((next-action-name (cdr (assoc current-status org-jira-progress-issue-flow)))
-         (next-action-id (caar (cl-remove-if-not
-                                (lambda (action)
-                                  (equal action next-action-name)) actions :key #'cdr))))
-    next-action-id))
-
-;;;###autoload
-(defun org-jira-progress-issue-next ()
-  "Progress issue workflow."
-  (interactive)
-  (ensure-on-issue
-    (let* ((issue-id (org-jira-id))
-           (filename (org-jira-filename))
-           (actions (jiralib-get-available-actions
-                     issue-id
-                     (org-jira-get-issue-val-from-org 'status)))
-           (action (org-jira-progress-next-action actions (org-jira-get-issue-val-from-org 'status)))
-           (fields (jiralib-get-fields-for-action issue-id action))
-           (org-jira-rest-fields fields)
-           (field-key)
-           (custom-fields-collector nil)
-           (custom-fields
-            (progn
-              ;; delete those elements in fields, which have
-              ;; already been set in custom-fields-collector
-              (while fields
-                (setq fields
-                      (cl-remove-if
-                       (lambda (strstr)
-                         (cl-member-if (lambda (symstr)
-                                         (string= (car strstr)  (symbol-name (car symstr))))
-                                       custom-fields-collector))
-                       fields))
-                (setq field-key (org-jira-read-field fields))
-                (if (not field-key)
-                    (setq fields nil)
-                  (setq custom-fields-collector
-                        (cons
-                         (funcall (if jiralib-use-restapi
-                                      #'list
-                                    #'cons)
-                                  field-key
-                                  (if (eq field-key 'resolution)
-                                      (org-jira-read-resolution)
-                                    (let ((field-value (completing-read
-                                                        (format "Please enter %s's value: "
-                                                                (cdr (assoc (symbol-name field-key) fields)))
-                                                        org-jira-fields-values-history
-                                                        nil
-                                                        nil
-                                                        nil
-                                                        'org-jira-fields-values-history)))
-                                      (if jiralib-use-restapi
-                                          (cons 'name field-value)
-                                        field-value))))
-                         custom-fields-collector))))
-              custom-fields-collector)))
-      (if action
-          (jiralib-progress-workflow-action
-           issue-id
-           action
-           custom-fields
-           (org-jira-with-callback
-             (ensure-on-issue-id-with-filename issue-id filename
-               (org-jira-refresh-issue))))
-        (error "No action defined for that step!")))))
-
-(defun org-jira-get-id-name-alist (name ids-to-names)
-  "Find the id corresponding to NAME in IDS-TO-NAMES and return an alist with id and name as keys."
-  (let ((id (car (rassoc name ids-to-names))))
-    `((id . ,id)
-      (name . ,name))))
-
-(defun org-jira-build-components-list (project-components org-issue-components)
-  "Given PROJECT-COMPONENTS, attempt to build a list.
-
-If the PROJECT-COMPONENTS are nil, this should return:
-
-  (list components []), which will translate into the JSON:
-
-  {\"components\": []}
-
-otherwise it should return:
-
-  (list components (list (cons id comp-id) (cons name item-name))),
-
-  which will translate into the JSON:
-
-{\"components\": [{\"id\": \"comp-id\", \"name\": \"item\"}]}"
-  (if (not project-components) (vector) ;; Return a blank array for JSON
-    (apply 'list
-           (cl-mapcan
-            (lambda (item)
-              (let ((comp-id (car (rassoc item project-components))))
-                (if comp-id
-                    `(((id . ,comp-id)
-                       (name . ,item)))
-                  nil)))
-            (split-string org-issue-components ",\\s *")))))
-
-(defun org-jira-strip-priority-tags (s)
-  "Given string S, remove any priority tags in the brackets."
-  (->> s (replace-regexp-in-string "\\[#.*?\\]" "") org-trim))
-
-(defun org-jira-update-issue-details (issue-id filename &rest rest)
-  "Update the details of issue ISSUE-ID in FILENAME.  REST will contain optional input."
-  (ensure-on-issue-id-with-filename issue-id filename
-    ;; Set up a bunch of values from the org content
-    (let* ((org-issue-components (org-jira-get-issue-val-from-org 'components))
-           (org-issue-labels (org-jira-get-issue-val-from-org 'labels))
-           (org-issue-description (org-trim (org-jira-get-issue-val-from-org 'description)))
-           (org-issue-priority (org-jira-get-issue-val-from-org 'priority))
-           (org-issue-type (org-jira-get-issue-val-from-org 'type))
-           (org-issue-type-id (org-jira-get-issue-val-from-org 'type-id))
-           (org-issue-assignee (cl-getf rest :assignee (org-jira-get-issue-val-from-org 'assignee)))
-           (org-issue-reporter (cl-getf rest :reporter (org-jira-get-issue-val-from-org 'reporter)))
-           (project (replace-regexp-in-string "-[0-9]+" "" issue-id))
-           (project-components (jiralib-get-components project)))
-
-      ;; Lets fire off a worklog update async with the main issue
-      ;; update, why not?  This is better to fire first, because it
-      ;; doesn't auto-refresh any areas, while the end of the main
-      ;; update does a callback that reloads the worklog entries (so,
-      ;; we hope that won't occur until after this successfully syncs
-      ;; up).  Only do this sync if the user defcustom defines it as such.
-      (when org-jira-worklog-sync-p
-        (org-jira-update-worklogs-from-org-clocks))
-
-      ;; Send the update to jira
-      (let ((update-fields
-             (list (cons
-                    'components
-                    (or (org-jira-build-components-list
-                         project-components
-                         org-issue-components) []))
-                   (cons 'labels (split-string org-issue-labels ",\\s *"))
-                   (cons 'priority (org-jira-get-id-name-alist org-issue-priority
-                                                       (jiralib-get-priorities)))
-                   (cons 'description org-issue-description)
-                   (cons 'assignee (list (cons 'id (jiralib-get-user-account-id project org-issue-assignee))))
-                   (cons 'reporter (list (cons 'id (jiralib-get-user-account-id project org-issue-reporter))))
-                   (cons 'summary (org-jira-strip-priority-tags (org-jira-get-issue-val-from-org 'summary)))
-                   (cons 'issuetype `((id . ,org-issue-type-id)
-      (name . ,org-issue-type))))))
-
-
-        ;; If we enable duedate sync and we have a deadline present
-        (when (and org-jira-deadline-duedate-sync-p
-                   (org-jira-get-issue-val-from-org 'deadline))
-          (setq update-fields
-                (append update-fields
-                        (list (cons 'duedate (org-jira-get-issue-val-from-org 'deadline))))))
-
-        ;; TODO: We need some way to handle things like assignee setting
-        ;; and refreshing the proper issue in the proper buffer/filename.
-        (jiralib-update-issue
-         issue-id
-         update-fields
-         ;; This callback occurs on success
-         (org-jira-with-callback
-           (message (format "Issue '%s' updated!" issue-id))
-           (jiralib-get-issue
-            issue-id
-            (org-jira-with-callback
-              (org-jira-log "Update get issue for refresh callback hit.")
-              (-> cb-data list org-jira-get-issues))))
-         ))
-      )))
-
-
-
-(defun org-jira-parse-issue-id ()
-  "Get issue id from org text."
-  (save-excursion
-    (let ((continue t)
-          issue-id)
-      (while continue
-        (when (string-match (jiralib-get-issue-regexp)
-                            (or (setq issue-id (org-entry-get (point) "ID"))
-                                ""))
-          (setq continue nil))
-        (unless (and continue (org-up-heading-safe))
-          (setq continue nil)))
-      issue-id)))
-
-(defun org-jira-parse-issue-filename ()
-  "Get filename from org text."
-  (save-excursion
-    (let ((continue t)
-          filename)
-      (while continue
-        (when (setq filename (org-entry-get (point) "filename"))
-          (setq continue nil))
-        (unless (and continue (org-up-heading-safe))
-          (setq continue nil)))
-      filename)))
-
-(defun org-jira-get-from-org (type entry)
-  "Get an org property from the current item.
-
-TYPE is the type to of the current item, and can be 'issue, or 'comment.
-
-ENTRY will vary, and is the name of the property to return.  If
-it is a symbol, it will be converted to string."
-  (when (symbolp entry)
-    (setq entry (symbol-name entry)))
-  (cond
-   ((eq type 'issue)
-    (org-jira-get-issue-val-from-org entry))
-   ((eq type 'comment)
-    (org-jira-get-comment-val-from-org entry))
-   ((eq type 'worklog)
-    (org-jira-get-worklog-val-from-org entry))
-   (t (error "Unknown type %s" type))))
-
-(defun org-jira-get-comment-val-from-org (entry)
-  "Get the JIRA issue field value ENTRY of the current comment item."
-  (ensure-on-comment
-   (when (symbolp entry)
-     (setq entry (symbol-name entry)))
-   (when (string= entry "id")
-     (setq entry "ID"))
-   (org-entry-get (point) entry)))
-
-(defun org-jira-get-worklog-val-from-org (entry)
-  "Get the JIRA issue field value ENTRY of the current worklog item."
-  (ensure-on-worklog
-   (when (symbolp entry)
-     (setq entry (symbol-name entry)))
-   (when (string= entry "id")
-     (setq entry "ID"))
-   (org-entry-get (point) entry)))
-
-(defun org-jira-get-comment-body (&optional comment-id)
-  "Get the comment body of the comment with id COMMENT-ID."
-  (ensure-on-comment
-   (goto-char (point-min))
-   ;; so that search for :END: won't fail
-   (org-jira-entry-put (point) "ID" comment-id)
-   (search-forward ":END:" nil 1 1)
-   (forward-line)
-   (org-trim (buffer-substring-no-properties (point) (point-max)))))
-
-(defun org-jira-get-worklog-comment (&optional worklog-id)
-  "Get the worklog comment of the worklog with id WORKLOG-ID."
-  (ensure-on-worklog
-   (goto-char (point-min))
-   ;; so that search for :END: won't fail
-   (org-jira-entry-put (point) "ID" worklog-id)
-   (search-forward ":END:" nil 1 1)
-   (forward-line)
-   (org-trim (buffer-substring-no-properties (point) (point-max)))))
-
-(defun org-jira-id ()
-  "Get the ID entry for the current heading."
-  (org-entry-get (point) "ID"))
-
-(defun org-jira-filename ()
-  "Get the ID entry for the current heading."
-  (org-jira-get-from-org 'issue 'filename))
-
-;;;###autoload
-(defun org-jira-browse-issue ()
-  "Open the current issue in external browser."
-  (interactive)
-  (ensure-on-issue
-    (browse-url (concat (replace-regexp-in-string "/*$" "" jiralib-url) "/browse/" (org-jira-id)))))
-
-(defun org-jira-url-copy-file (url newname)
-  "Similar to url-copy-file but async."
-  (lexical-let ((newname newname))
-    (url-retrieve
-     url
-     (lambda (status)
-       (let ((buffer (current-buffer))
-             (handle nil)
-             (filename (if (and (file-exists-p newname)
-                                org-jira-download-ask-override)
-                           (read-string "File already exists, select new name or press ENTER to override: " newname)
-                         newname)))
-         (if (not buffer)
-             (error "Opening input file: No such file or directory, %s" url))
-         (with-current-buffer buffer
-           (setq handle (mm-dissect-buffer t)))
-         (mm-save-part-to-file handle filename)
-         (kill-buffer buffer)
-         (mm-destroy-parts handle))))))
-
-;;;###autoload
-(defun org-jira-download-attachment ()
-  "Download the attachment under cursor."
-  (interactive)
-  (when jiralib-use-restapi
-    (save-excursion
-      (org-up-heading-safe)
-      (org-back-to-heading)
-      (forward-thing 'whitespace)
-      (unless (looking-at "Attachments:")
-        (error "Not on a attachment region!")))
-    (let ((filename (org-entry-get (point) "Name"))
-          (url (org-entry-get (point) "Content"))
-          (url-request-extra-headers `(,jiralib-token)))
-      (org-jira-url-copy-file
-       url
-       (concat (file-name-as-directory org-jira-download-dir) filename)))))
-
-;;;###autoload
-(defun org-jira-get-issues-from-filter (filter)
-  "Get issues from the server-side stored filter named FILTER.
-
-Provide this command in case some users are not able to use
-client side jql (maybe because of JIRA server version?)."
-  (interactive
-   (list (completing-read "Filter: " (mapcar 'cdr (jiralib-get-saved-filters)))))
-  (org-jira-get-issues (jiralib-get-issues-from-filter (car (rassoc filter (jiralib-get-saved-filters))))))
-
-;;;###autoload
-(defun org-jira-get-issues-from-filter-headonly (filter)
-  "Get issues *head only* from saved filter named FILTER.
-See `org-jira-get-issues-from-filter'."
-  (interactive
-   (list (completing-read "Filter: " (mapcar 'cdr (jiralib-get-saved-filters)))))
-  (org-jira-get-issues-headonly (jiralib-get-issues-from-filter (car (rassoc filter (jiralib-get-saved-filters))))))
-
-(org-add-link-type "jira" 'org-jira-open)
-
-;; This was only added in org 9.0, not sure all org users will have
-;; that version, so keep the deprecated one from above for now.
-
-;;(org-link-set-parameters "jira" ((:follow . 'org-jira-open)))
-
-(defun org-jira-open (path)
-  "Open a Jira Link from PATH."
-  (org-jira-get-issue path))
-
-;;;###autoload
-(defun org-jira-get-issues-by-board ()
-  "Get list of ISSUES from agile board."
-  (interactive)
-  (let* ((board (org-jira-read-board))
-         (board-id (cdr board)))
-    (jiralib-get-board-issues board-id
-                              :callback org-jira-get-issue-list-callback
-                              :limit (org-jira-get-board-limit board-id)
-                              :query-params (org-jira--make-jql-queryparams board-id))))
-
-;;;###autoload
-(defun org-jira-get-issues-by-sprint ()
-  "Get list of ISSUES from sprint."
-  (interactive)
-  (let* ((board (org-jira-read-board))
-        (board-id (cdr board))
-        (sprint (org-jira-read-sprint board-id))
-        (sprint-id (cdr sprint)))
-    (jiralib-get-sprint-issues sprint-id
-                              :callback org-jira-get-issue-list-callback
-                              :limit (org-jira-get-board-limit board-id)
-                              :query-params (org-jira--make-jql-queryparams board-id))))
-
-(defun org-jira-get-board-limit (id)
-  "Get limit for number of retrieved issues for a board
-id - integer board id"
-  (let ((board (org-jira--get-board-from-buffer id)))
-    (if (and board (slot-boundp board 'limit))
-        (oref board limit)
-      org-jira-boards-default-limit)))
-
-(defun org-jira--make-jql-queryparams (board-id)
-  "make GET query parameters for jql, returns nil if JQL query is not set"
-  (let* ((board (org-jira--get-board-from-buffer board-id))
-         (jql (if (and board (slot-boundp board 'jql))
-                  (oref board jql))))
-    (if (and jql (not (string-blank-p jql))) `((jql ,jql)))))
-
-;;;###autoload
-(defun org-jira-get-issues-by-board-headonly ()
-  "Get list of ISSUES from agile board, head only."
-  (interactive)
-  (let* ((board (org-jira-read-board))
-         (board-id (cdr board)))
-    (org-jira-get-issues-headonly
-     (jiralib-get-board-issues board-id
-                               :limit (org-jira-get-board-limit board-id)
-                               :query-params (org-jira--make-jql-queryparams board-id)))))
-
-
-(defun org-jira--render-boards-from-list (boards)
-  "Add the boards from list into the org file.
-
-boards -  list of `org-jira-sdk-board' records."
-  (mapc 'org-jira--render-board  boards))
-
-
-(defun org-jira--render-board (board)
-  "Render single board"
-  ;;(org-jira-sdk-dump board)
-  (with-slots (id name url board-type jql limit) board
-    (with-current-buffer (org-jira--get-boards-buffer)
-      (org-jira-maybe-activate-mode)
-      (org-jira-freeze-ui
-        (org-save-outline-visibility t
-          (save-restriction
-            (outline-show-all)
-            (widen)
-            (goto-char (point-min))
-            (let* ((board-headline
-                    (format "Board: [[%s][%s]]" url name))
-                   (headline-pos
-                    (org-find-exact-headline-in-buffer board-headline (current-buffer) t))
-                   (entry-exists (and headline-pos (>= headline-pos (point-min)) (<= headline-pos (point-max))))
-                   (limit-value  (if (slot-boundp board 'limit) (int-to-string  limit) nil))
-                   (jql-value    (if (slot-boundp board 'jql) jql nil)))
-              (if entry-exists
-                  (progn
-                    (goto-char headline-pos)
-                    (org-narrow-to-subtree)
-                    (end-of-line))
-                (goto-char (point-max))
-                (unless (looking-at "^")
-                  (insert "\n"))
-                (insert "* ")
-                (org-jira-insert board-headline)
-                (org-narrow-to-subtree))
-              (org-jira-entry-put (point) "name" name)
-              (org-jira-entry-put (point) "type" board-type)
-              (org-jira-entry-put (point) "url"  url)
-              ;; do not overwrite existing user properties with empty values
-              (if (or (not entry-exists) limit-value)
-                  (org-jira-entry-put (point) "limit" limit-value))
-              (if (or (not entry-exists) jql-value)
-                  (org-jira-entry-put (point) "JQL" jql-value ))
-              (org-jira-entry-put (point) "ID"   id))))))))
-
-(defun org-jira--get-boards-file ()
-  (expand-file-name "boards-list.org" (org-jira--ensure-working-dir)))
-
-(defun org-jira--get-boards-buffer ()
-  "Return buffer for list of agile boards. Create one if it does not exist."
-  (let* ((boards-file  (org-jira--get-boards-file))
-         (existing-buffer (find-buffer-visiting boards-file)))
-    (if existing-buffer
-        existing-buffer
-      (find-file-noselect boards-file))))
-
-;;;###autoload
-(defun org-jira-get-boards ()
-  "Get list of boards and their properties."
-  (interactive)
-  (let* ((datalist (jiralib-get-boards))
-         (boards (org-jira-sdk-create-boards-from-data-list datalist)))
-    (org-jira--render-boards-from-list boards))
-  (switch-to-buffer (org-jira--get-boards-buffer)))
-
-(defun org-jira--get-board-from-buffer (id)
-  "Parse board record from org file."
-  (with-current-buffer (org-jira--get-boards-buffer)
-    (org-jira-freeze-ui
-      (let ((pos (org-find-property "ID" (int-to-string  id))))
-        (if pos
-            (progn
-              (goto-char pos)
-              (apply 'org-jira-sdk-board
-                     (cl-reduce
-                      #'(lambda (acc entry)
-                          (let* ((pname   (car entry))
-                                 (pval (cdr entry))
-                                 (pair (and pval
-                                            (not (string-empty-p pval))
-                                            (cond
-                                             ((equal pname "ID")
-                                              (list :id pval))
-                                             ((equal pname "URL")
-                                              (list :url pval))
-                                             ((equal pname "TYPE")
-                                              (list :board-type pval))
-                                             ((equal pname "NAME")
-                                              (list :name pval))
-                                             ((equal pname "LIMIT")
-                                              (list :limit (string-to-number pval)))
-                                             ((equal pname "JQL")
-                                              (list :jql pval))
-                                             (t nil)))))
-                            (if pair  (append pair acc)  acc)))
-                      (org-entry-properties) :initial-value  ()))))))))
-
-(defun org-jira-get-org-keyword-from-status (status)
-  "Gets an 'org-mode' keyword corresponding to a given jira STATUS."
-  (if org-jira-use-status-as-todo
-      (upcase (replace-regexp-in-string " " "-" status))
-    (let ((known-keyword (assoc status org-jira-jira-status-to-org-keyword-alist)))
-      (cond (known-keyword (cdr known-keyword))
-            ((member (org-jira-decode status) org-jira-done-states) "DONE")
-            ("TODO")))))
-
-(defun org-jira-get-org-priority-string (character)
-  "Return an org-priority-string based on CHARACTER and user settings."
-  (cond ((not character) "")
-        ((and org-jira-priority-to-org-priority-omit-default-priority
-              (eq character org-default-priority)) "")
-        (t (format "[#%c] " character))))
-
-(defun org-jira-get-org-priority-cookie-from-issue (priority)
-  "Get the `org-mode' [#X] PRIORITY cookie."
-  (let ((character (cdr (assoc priority org-jira-priority-to-org-priority-alist))))
-    (org-jira-get-org-priority-string character)))
-
-(provide 'org-jira)
-;;; org-jira.el ends here
diff --git a/elpa/org-jira-20240928.2340/jiralib.el b/elpa/org-jira-20240928.2340/jiralib.el
new file mode 100644 (file)
index 0000000..23d4204
--- /dev/null
@@ -0,0 +1,1346 @@
+;;; jiralib.el -- Provide connectivity to JIRA SOAP/REST services.
+
+;; Copyright (C) 2016-2022 Matthew Carter <mNOSPAMahungry.com>
+;; Copyright (C) 2011 Bao Haojun
+;; original Copyright (C) 2009 Alex Harsanyi
+
+;; Also, used some code from jira.el, which use xml-rpc instead of soap.
+;; Thus Copyright (C) for jira.el related code:
+;; Brian Zwahr <echosaNOSPAMgmail.com>
+;; Dave Benjamin <daveNOSPAMramenlabs.com>
+
+;; Authors:
+;; Matthew Carter <mNOSPAMahungry.com>
+;; Bao Haojun <baohaojunNOSPAMgmail.com>
+;; Alex Harsanyi <AlexHarsanyiNOSPAMgmail.com>
+
+;; Maintainer: Matthew Carter <mNOSPAMahungry.com>
+;; Homepage: https://github.com/ahungry/org-jira
+
+;; This file is not part of GNU Emacs.
+
+;; 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 of the License, 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 this program.  If not, see
+;; <http://www.gnu.org/licenses/> or write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301, USA.
+
+;; Author: Alexandru Harsanyi (AlexHarsanyiNOSPAMgmail.com)
+;; Created: December, 2009
+;; Keywords: soap, web-services, jira
+;; Homepage: http://code.google.com/p/emacs-soap-client
+
+;;; Commentary:
+
+;; This file provides a programmatic interface to JIRA.  It provides access to
+;; JIRA from other programs, but no user level functionality.
+
+;; Jira References:
+
+;; Primary reference (on current Jira, only REST is supported):
+;; https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis
+
+;; Full API list reference:
+;; https://docs.atlassian.com/jira/REST/cloud/
+
+;; Legacy reference (unsupported and deprecated/unavailable):
+;; http://confluence.atlassian.com/display/JIRA/Creating+a+SOAP+Client
+
+;; JavaDoc for the Jira SOAP service
+;; http://docs.atlassian.com/software/jira/docs/api/rpc-jira-plugin/latest/com/atlassian/jira/rpc/soap/JiraSoapService.html
+
+;;; News:
+
+;;;; Changes since 2.6.3:
+;; - Add worklog import filter and control variable for external worklogs.
+;; - Add the worklog related endpoint/calls.
+
+;;;; Changes since 2.1.0:
+;; - Remove os_username / os_password manual http request as part of sign in process
+;;     This produces sysadmin level warnings on Jira when these are used under the latest Jira.
+;; - Remove unused function jiralib-link-issue
+;; - Bring version up to match org-jira version so they can share tag
+
+;;;; Changes since 2.0.0:
+;; - Allow issue type query by project
+
+;;;; Changes since 0.0.0:
+;; - Converted many calls to async
+;; - Converted many calls to make use of caching
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'cl-seq)
+(require 'soap-client)
+(require 'request)
+(require 'json)
+(require 'url-parse)
+(require 'url-util)
+
+(defconst jiralib-version "3.0.0"
+  "Current version of jiralib.el.")
+
+(defgroup jiralib nil
+  "Jiralib customization group."
+  :group 'applications)
+
+(defgroup jiralib-faces nil
+  "Faces for displaying Jiralib information."
+  :group 'jiralib)
+
+(defcustom jiralib-use-restapi t
+  "Use restapi instead of soap."
+  :group 'jiralib
+  :type 'boolean
+  :initialize 'custom-initialize-set)
+
+(defcustom jiralib-coding-system 'utf-8
+  "Use custom coding system for Jiralib."
+  :group 'jiralib)
+
+(defcustom jiralib-host ""
+  "User customizable host name of the Jiralib server.
+
+This will be used with USERNAME to compute password from
+.authinfo file.  Will be calculated from jiralib-url if not set."
+  :group 'jiralib
+  :type 'string
+  :initialize 'custom-initialize-set)
+
+(defcustom jiralib-user ""
+  "User customizable user name of the Jiralib server.
+
+This will be used with USERNAME to compute password from
+.authinfo file.  Will be calculated from jiralib-url if not set."
+  :group 'jiralib
+  :type 'string
+  :initialize 'custom-initialize-set)
+
+(defface jiralib-issue-info-face
+  '((t (:foreground "black" :background "yellow4")))
+  "Base face for issue information."
+  :group 'jiralib-faces)
+
+(defface jiralib-issue-info-header-face
+  '((t (:bold t :inherit 'jiralib-issue-info-face)))
+  "Base face for issue headers."
+  :group 'jiralib-faces)
+
+(defface jiralib-issue-summary-face
+  '((t (:bold t)))
+  "Base face for issue summary."
+  :group 'jiralib-faces)
+
+(defface jiralib-comment-face
+  '((t (:background "gray23")))
+  "Base face for comments."
+  :group 'jiralib-faces)
+
+(defface jiralib-comment-header-face
+  '((t (:bold t)))
+  "Base face for comment headers."
+  :group 'jiralib-faces)
+
+(defface jiralib-link-issue-face
+  '((t (:underline t)))
+  "Face for linked issues."
+  :group 'jiralib-faces)
+
+(defface jiralib-link-project-face
+  '((t (:underline t)))
+  "Face for linked projects"
+  :group 'jiralib-faces)
+
+(defface jiralib-link-filter-face
+  '((t (:underline t)))
+  "Face for linked filters"
+  :group 'jiralib-faces)
+
+(defvar jiralib-mode-hook nil)
+(defvar jiralib-mode-map nil)
+(defvar jiralib-issue-regexp "\\<\\(?:[A-Za-z0-9]+\\)-[0-9]+\\>")
+
+(defcustom jiralib-wsdl-descriptor-url
+  ""
+  "The location for the WSDL descriptor for the JIRA service.
+This is specific to your local JIRA installation.  The URL is
+typically:
+
+  http://YOUR_INSTALLATION/rpc/soap/jirasoapservice-v2?wsdl
+
+The default value works if JIRA is located at a hostname named
+'jira'."
+  :type 'string
+  :group 'jiralib)
+
+(defcustom jiralib-url
+  "http://localhost:8081/"
+  "The address of the jira host."
+  :type 'string
+  :group 'jiralib)
+
+(defcustom jiralib-agile-page-size
+  50
+  "Page size for agile API retrieve. Limited by server property jira.search.views.default.max"
+  :type 'integer
+  :group 'jiralib)
+
+(defvar jiralib-token nil
+  "JIRA token used for authentication.")
+
+(defvar jiralib-user-login-name nil
+  "The name of the user logged into JIRA.
+This is maintained by `jiralib-login'.")
+
+(defvar jiralib-wsdl nil)
+
+(defcustom jiralib-worklog-import--filters-alist
+  (list
+   '(nil "WorklogUpdatedByCurrentUser"
+         (lambda (wl)
+           (let-alist wl
+             (when
+                 (and wl
+                      (string-equal
+                       (downcase
+                        (or jiralib-user-login-name user-login-name ""))
+                       (downcase (or .updateAuthor.name
+                                     (car (split-string (or .updateAuthor.emailAddress "") "@"))
+                                     ""))))
+               wl))))
+   '(nil "WorklogAuthoredByCurrentUser"
+         (lambda (wl)
+           (let-alist wl
+             (when
+                 (and wl
+                      (string-equal
+                       (downcase
+                        (or jiralib-user-login-name user-login-name))
+                       (downcase (or .author.name
+                                     (car (split-string (or .author.emailAddress "") "@"))))))
+               wl)))))
+  "A list of triplets: ('Global-Enable 'Descriptive-Label 'Function-Definition)
+that apply worklog predicate filters during import.
+
+Example: (list '('t \"descriptive-predicate-label\" (lambda (x) x)))"
+  :type '(repeat (list boolean string function))
+  :group 'org-jira)
+
+
+(defcustom jiralib-update-issue-fields-exclude-list nil
+  "A list of symbols to check for exclusion on updates based on matching key.
+Key names should be one of components, description, assignee, reporter, summary, issuetype."
+  :type '(set (const :tag "Exclude components" components)
+              (const :tag "Exclude description" description)
+              (const :tag "Exclude assignee" assignee)
+              (const :tag "Exclude reporter" reporter)
+              (const :tag "Exclude summary" summary)
+              (const :tag "Exclude priority" priority)
+              (const :tag "Exclude issue type" issuetype))
+  :group 'org-jira)
+
+(defun jiralib-load-wsdl ()
+  "Load the JIRA WSDL descriptor."
+  (setq jiralib-wsdl (soap-load-wsdl-from-url (if (string-equal jiralib-wsdl-descriptor-url "")
+                                                  (concat jiralib-url "/rpc/soap/jirasoapservice-v2?wsdl")
+                                                jiralib-wsdl-descriptor-url))))
+
+(defun jiralib-login (username password)
+  "Login into JIRA as user USERNAME with PASSWORD.
+
+After a successful login, store the authentication token in
+`jiralib-token'."
+  ;; NOTE that we cannot rely on `jiralib-call' because `jiralib-call' relies on
+  ;; us ;-)
+  (interactive
+   (if (> 24 emacs-major-version)
+       (let ((user (read-string "Username for Jira server login? "))
+             (password (read-passwd "Password for Jira server login? ")))
+         (list user password))
+     (let ((found (nth 0 (auth-source-search :max 1
+                                             :host (if (string= jiralib-host "")
+                                                       (url-host (url-generic-parse-url jiralib-url))
+                                                     jiralib-host)
+                                             :user (if (string= jiralib-user "")
+                                                       (url-user (url-generic-parse-url jiralib-url))
+                                                     jiralib-user)
+                                             ;; secrets.el wouldn’t accept a number.
+                                             :port (list (number-to-string (url-port (url-generic-parse-url jiralib-url)))
+                                                         (url-type (url-generic-parse-url jiralib-url)))
+                                             :require '(:user :secret)
+                                             :create t)))
+           user secret)
+       (when found
+         (setq user (plist-get found :user)
+               secret
+               (let ((sec (plist-get found :secret)))
+                 (if (functionp sec)
+                     (funcall sec)
+                   sec)))
+         (list user secret)))))
+  (if jiralib-use-restapi
+      (setq jiralib-token `("Authorization" . , (format "Basic %s" (base64-encode-string (concat username ":" password) t))))
+    (unless jiralib-wsdl
+      (jiralib-load-wsdl))
+    (setq jiralib-token
+          (car (soap-invoke jiralib-wsdl "jirasoapservice-v2" "login" username password))))
+    (setq jiralib-user-login-name username))
+
+(defvar jiralib-complete-callback nil)
+
+(defun jiralib-call (method callback &rest params)
+  "Invoke the Jira METHOD, then CALLBACK with supplied PARAMS.
+
+This function should be used for all JIRA interface calls, as the
+method ensures the user is logged in and invokes `soap-invoke'
+with the correct service name and authentication token.
+
+All JIRA interface methods take an authentication token as the
+first argument.  The authentication token is supplied by this
+function, so PARAMS should omit this parameter.  For example, the
+\"getIssue\" method takes two parameters: auth and key, however,
+when invoking it through `jiralib-call', the call should be:
+
+  (jiralib-call \"getIssue\" KEY)
+
+CALLBACK should be the post processing function to run with the
+completed data from the request result, which can be accessed with:
+
+  (cl-getf data :data)
+
+as such, the CALLBACK should follow this type of form:
+
+  (cl-function
+    (lambda (&rest data &allow-other-keys)
+      (print (cl-getf data :data))))
+
+If CALLBACK is set to nil then the request will occur with sync.
+This produces a noticeable slowdown and is not recommended by
+request.el, so if at all possible, it should be avoided."
+  ;; @TODO :auth: Probably pass this all the way down, but I think
+  ;; it may be OK at the moment to just set the variable each time.
+  
+  (setq jiralib-complete-callback
+        ;; Don't run with async if we don't have a login token yet.
+        (if jiralib-token callback nil))
+
+  ;; If we don't have a regex set, ensure it is set BEFORE any async
+  ;; calls are processing, or we're going to have a bad time.
+  ;; This should only end up running once per session.
+  (unless jiralib-issue-regexp
+    (let ((projects (mapcar (lambda (e) (downcase (cdr (assoc 'key e))))
+                            (append (jiralib--rest-call-it
+                                     "/rest/api/2/project"
+                                     :params '((expand . "description,lead,url,projectKeys")))
+                                    nil)
+                            )))
+      (when projects
+        (setq jiralib-issue-regexp
+              (concat "\\<" (regexp-opt projects) "-[0-9]+\\>")))))
+
+  (if (not jiralib-use-restapi)
+      (car (apply 'jiralib--call-it method params))
+    (unless jiralib-token
+      (call-interactively 'jiralib-login))
+    (cl-case (intern method)
+      ('getStatuses (jiralib--rest-call-it "/rest/api/2/status"))
+      ('getIssueTypes (jiralib--rest-call-it "/rest/api/2/issuetype"))
+      ('getSubTaskIssueTypes (jiralib--rest-call-it "/rest/api/2/issuetype"))
+      ('getIssueTypesByProject
+       (let ((response (jiralib--rest-call-it (format "/rest/api/2/project/%s" (first params)))))
+         (cl-coerce (cdr (assoc 'issueTypes response)) 'list)))
+      ('getUser (jiralib--rest-call-it "/rest/api/2/user" :params `((accountId . ,(first params)))))
+      ('getVersions (jiralib--rest-call-it (format "/rest/api/2/project/%s/versions" (first params))))
+
+      ;; Worklog calls
+      ('getWorklogs
+       (jiralib--rest-call-it (format "/rest/api/2/issue/%s/worklog" (first params))))
+
+      ('addWorklog
+       (jiralib--rest-call-it (format "/rest/api/2/issue/%s/worklog" (first params))
+                              :type "POST"
+                              :data (json-encode (second params))))
+
+      ('updateWorklog
+       (jiralib--rest-call-it (format "/rest/api/2/issue/%s/worklog/%s" (first params) (second params))
+                              :type "PUT"
+                              :data (json-encode (third params))))
+
+      ('addWorklogAndAutoAdjustRemainingEstimate
+       (jiralib--rest-call-it (format "/rest/api/2/issue/%s/worklog" (first params))
+                              :type "POST"
+                              :data (json-encode (second params))))
+
+      ('addComment (jiralib--rest-call-it
+                    (format "/rest/api/2/issue/%s/comment" (first params))
+                    :type "POST"
+                    :data (json-encode (second params))))
+      ('createIssue
+       ;; Creating the issue doesn't return it, a second call must be
+       ;; made to pull it in by using the self key in response.
+       (let ((response (jiralib--rest-call-it
+                        "/rest/api/2/issue"
+                        :type "POST"
+                        :data (json-encode (first params)))))
+         (jiralib--rest-call-it (cdr (assoc 'self response)) :type "GET")
+         ))
+      ('createIssueWithParent
+       (let ((response (jiralib--rest-call-it
+                        "/rest/api/2/issue"
+                        :type "POST"
+                        :data (json-encode (first params)))))
+         (jiralib--rest-call-it (cdr (assoc 'self response)) :type "GET")
+         ))
+      ('editComment (jiralib--rest-call-it
+                     (format "/rest/api/2/issue/%s/comment/%s" (first params) (second params))
+                     :data (json-encode `((body . ,(third params))))
+                     :type "PUT"))
+      ('getBoard  (jiralib--rest-call-it (format "/rest/agile/1.0/board/%s"  (first params))))
+      ('getBoards (apply 'jiralib--agile-call-it "/rest/agile/1.0/board" 'values params))
+      ('getComment (org-jira-find-value
+                     (jiralib--rest-call-it
+                      (format "/rest/api/2/issue/%s/comment/%s" (first params) (second params)))
+                     'comments))
+      ('getComments (org-jira-find-value
+                     (jiralib--rest-call-it
+                      (format "/rest/api/2/issue/%s/comment" (first params)))
+                     'comments))
+      ('getAttachmentsFromIssue (org-jira-find-value
+                                 (jiralib--rest-call-it
+                                  (format "/rest/api/2/issue/%s?fields=attachment" (first params)))
+                                 'comments))
+      ('getComponents (jiralib--rest-call-it
+                       (format "/rest/api/2/project/%s/components" (first params))))
+      ('getIssue (jiralib--rest-call-it
+                  (format "/rest/api/2/issue/%s" (first params))))
+      ('getIssuesFromBoard  (apply 'jiralib--agile-call-it
+                                  (format "rest/agile/1.0/board/%d/issue" (first params))
+                                  'issues
+                                  (cdr params)))
+      ('getSprintsFromBoard  (jiralib--rest-call-it (format "/rest/agile/1.0/board/%s/sprint"  (first params))))
+      ('getIssuesFromSprint  (apply 'jiralib--agile-call-it
+                                  (format "rest/agile/1.0/sprint/%d/issue" (first params))
+                                  'issues
+                                  (cdr params)))
+      ('getIssuesFromJqlSearch  (append (cdr ( assoc 'issues (jiralib--rest-call-it
+                                                              "/rest/api/2/search"
+                                                              :type "POST"
+                                                              :data (json-encode `((jql . ,(first params))
+                                                                                   (maxResults . ,(second params)))))))
+                                        nil))
+      ('getPriorities (jiralib--rest-call-it
+                       "/rest/api/2/priority"))
+      ('getProjects (jiralib--rest-call-it "rest/api/2/project"))
+      ('getProjectsNoSchemes (append (jiralib--rest-call-it
+                                      "/rest/api/2/project"
+                                      :params '((expand . "description,lead,url,projectKeys"))) nil))
+      ('getResolutions (append (jiralib--rest-call-it
+                                "/rest/api/2/resolution") nil))
+      ('getAvailableActions
+       (mapcar
+        (lambda (trans)
+          `(,(assoc 'name trans) ,(assoc 'id trans)))
+        (cdadr (jiralib--rest-call-it (format "/rest/api/2/issue/%s/transitions" (first params))))))
+      ('getFieldsForAction (org-jira-find-value (car (let ((issue (first params))
+                                                           (action (second params)))
+                                                       (seq-filter (lambda (trans)
+                                                                     (or (string-equal action (org-jira-find-value trans 'id))
+                                                                         (string-equal action (org-jira-find-value trans 'name))))
+                                                                   (cdadr (jiralib--rest-call-it
+                                                                           (format "/rest/api/2/issue/%s/transitions" (first params))
+                                                                           :params '((expand . "transitions.fields")))))))
+                                                'fields))
+      ('progressWorkflowAction (jiralib--rest-call-it
+                                (format "/rest/api/2/issue/%s/transitions" (first params))
+                                :type "POST"
+                                :data (json-encode `(,(car (second params)) ,(car (third params))))))
+      ('getUsers
+       (jiralib--rest-call-it (format "/rest/api/2/user/assignable/search?project=%s&maxResults=10000" (first params))
+                              :type "GET"))
+      ('updateIssue (jiralib--rest-call-it
+                     (format "/rest/api/2/issue/%s" (first params))
+                     :type "PUT"
+                     :data (json-encode `((fields . ,(second params))))))
+      ('getLabels (jiralib--rest-call-it (format "/rest/api/2/label?startAt=%s" (first params)))))))
+
+(defun jiralib--soap-call-it (&rest args)
+  "Deprecated SOAP call endpoint.  Will be removed soon.
+Pass ARGS to jiralib-call."
+  (let ((jiralib-token nil)
+        (jiralib-use-restapi nil))
+    (apply #'jiralib-call args)))
+
+(defun jiralib--json-read ()
+  "Read with json, force utf-8"
+  (decode-coding-region (point) (point-max) jiralib-coding-system)
+  (json-read))
+
+(defun jiralib--rest-call-it (api &rest args)
+  "Invoke the corresponding jira rest method API.
+Invoking COMPLETE-CALLBACK when the
+JIRALIB-COMPLETE-CALLBACK is non-nil, request finishes, and
+passing ARGS to REQUEST."
+  (unless api (error "jiralib--rest-call-it was called with a NIL api value."))
+  (setq args
+        (mapcar
+         (lambda (arg)
+           (if (stringp arg)
+               (encode-coding-string arg jiralib-coding-system)
+             arg))
+         args))
+  (append (request-response-data
+           (apply #'request (if (string-match "^http[s]*://" api) api ;; If an absolute path, use it
+                              (concat (replace-regexp-in-string "/*$" "/" jiralib-url)
+                                      (replace-regexp-in-string "^/*" "" api)))
+                  :sync (not jiralib-complete-callback)
+                  :headers `(,jiralib-token ("Content-Type" . "application/json"))
+                  :parser 'jiralib--json-read
+                  :complete jiralib-complete-callback
+                  ;; Ensure we have useful errors
+                  :error
+                  (lexical-let
+                      ((my-api api)
+                       (my-args args))
+                    (cl-function
+                       (lambda (&key data &allow-other-keys)
+                         (print "JIRA_ERROR - see your *Messages* buffer for more details.")
+                         (print "JIRA_ERROR REQUEST: ")
+                         (print my-api)
+                         (print my-args)
+                         (print "JIRA_ERROR RESPONSE: ")
+                         (print data)
+                         (error "JIRA_ERROR - see your *Messages* buffer for more details.")
+                         )))
+                  args))
+          nil))
+
+(defun jiralib--call-it (method &rest params)
+  "Invoke the JIRA METHOD with supplied PARAMS.
+
+Internal use, returns a list of responses, of which only the
+first is normally used."
+  (when (symbolp method)
+    (setq method (symbol-name method)))
+  (unless jiralib-token
+    (call-interactively 'jiralib-login))
+  (condition-case data
+      (apply 'soap-invoke jiralib-wsdl "jirasoapservice-v2"
+             method jiralib-token params)
+    (soap-error
+     ;; If we are here, we had a token, but it expired.  Re-login and try
+     ;; again.
+     (setq jiralib-token nil)
+     (call-interactively 'jiralib-login)
+     (apply 'soap-invoke jiralib-wsdl "jirasoapservice-v2"
+            method jiralib-token params))))
+
+\f
+;;;; Some utility functions
+
+(defun jiralib-make-list (data field)
+  "Map all assoc elements in DATA to the value of FIELD in that element."
+  (cl-loop for element in data
+        collect (cdr (assoc field element))))
+
+(defun jiralib-make-assoc-list (data key-field value-field)
+  "Create an association list from a SOAP structure array.
+
+DATA is a list of association lists (a SOAP array-of type)
+KEY-FIELD is the field to use as the key in the returned alist
+VALUE-FIELD is the field to use as the value in the returned alist"  
+  (cl-loop for element in data
+        collect (cons (cdr (assoc key-field element))
+                      (cdr (assoc value-field element)))))
+
+(defun jiralib-make-remote-field-values (fields)
+  "Transform the (KEY . VALUE) list FIELDS into a RemoteFieldValue structure.
+
+Each (KEY . VALUE) pair is transformed into
+ ((id . KEY) (values . (VALUE)))
+
+This method exists because Several JIRA methods require a
+RemoteFieldValue list, but it is easier to work with ALISTS in
+emacs-lisp"
+  (let ((remote-field-values))
+
+    ;; we accept an ALIST of field-name field-values parameter, but we need to
+    ;; construct a structure that encodes as a RemoteFieldValue which is what
+    ;; updateIssue wants
+    (dolist (field fields)
+      (let ((name (car field))
+            (value (cdr field)))
+        (when (symbolp name)
+          (setq name (symbol-name name)))
+        ;; Value must be an "array" (for which soap-client accepts lists) even
+        ;; if it is just one value
+        (unless (vectorp value)
+          (setq value (vector value)))
+        (push `((id . ,name) (values . ,value))
+              remote-field-values)))
+
+    (apply 'vector (nreverse remote-field-values))))
+
+\f
+;;;; Wrappers around JIRA methods
+
+(defun jiralib--rest-api-for-issue-key (key)
+  "Return jira rest api for issue KEY."
+  (concat "rest/api/2/issue/" key))
+
+(defun jiralib-filter-fields-by-exclude-list (exclude-list fields)
+  (cl-remove-if
+   (lambda (el) (cl-member (car el) exclude-list)) fields))
+
+(defun jiralib-update-issue (key fields &optional callback)
+  "Update the issue with id KEY with the values in FIELDS, invoking CALLBACK."
+  (let ((filtered-fields (jiralib-filter-fields-by-exclude-list
+                          jiralib-update-issue-fields-exclude-list
+                          fields)))
+    (jiralib-call
+     "updateIssue"
+     callback
+     key (if jiralib-use-restapi
+             filtered-fields
+           (jiralib-make-remote-field-values filtered-fields)))))
+
+(defvar jiralib-status-codes-cache nil)
+
+(defun jiralib-get-statuses ()
+  "Return an assoc list mapping a status code to its name.
+NOTE: Status codes are stored as strings, not numbers.
+
+This function will only ask JIRA for the list of codes once, then
+will cache it."
+  (unless jiralib-status-codes-cache
+    (setq jiralib-status-codes-cache
+          (jiralib-make-assoc-list (jiralib-call "getStatuses" nil) 'id 'name)))
+  jiralib-status-codes-cache)
+
+(defvar jiralib-issue-types-cache nil)
+
+(defun jiralib-get-issue-types ()
+  "Return an assoc list mapping an issue type code to its name.
+NOTE: Issue type codes are stored as strings, not numbers.
+
+This function will only ask JIRA for the list of codes once, than
+will cache it.
+
+The issue types returned via getIssueTypes are all the ones
+available to the user, but not necessarily available to the given
+project.
+
+This endpoint is essentially a master reference for when issue
+types need a name lookup when given an id.
+
+For applying issue types to a given project that is being created, see
+the #'jiralib-get-issue-types-by-project call."
+  (unless jiralib-issue-types-cache
+    (setq jiralib-issue-types-cache
+          (jiralib-make-assoc-list (jiralib-call "getIssueTypes" nil) 'id 'name)))
+  jiralib-issue-types-cache)
+
+(defvar jiralib-issue-types-by-project-cache nil "An alist of available issue types.")
+
+(defun jiralib-get-issue-types-by-project (project)
+  "Return the available issue types for PROJECT.
+
+PROJECT should be the key, such as `EX' or `DEMO'."
+  (unless (assoc project jiralib-issue-types-by-project-cache)
+    (push (cons project
+                (jiralib-make-assoc-list
+                 (jiralib-call "getIssueTypesByProject" nil project)
+                 'id 'name))
+          jiralib-issue-types-by-project-cache))
+  (cdr (assoc project jiralib-issue-types-by-project-cache)))
+
+(defvar jiralib-priority-codes-cache nil)
+
+(defun jiralib-get-priorities ()
+  "Return an assoc list mapping a priority code to its name.
+NOTE: Priority codes are stored as strings, not numbers.
+
+This function will only ask JIRA for the list of codes once, than
+will cache it."
+  (unless jiralib-priority-codes-cache
+    (setq jiralib-priority-codes-cache
+          (jiralib-make-assoc-list (jiralib-call "getPriorities" nil) 'id 'name)))
+  jiralib-priority-codes-cache)
+
+(defvar jiralib-resolution-code-cache nil)
+
+(defun jiralib-get-resolutions ()
+  "Return an assoc list mapping a resolution code to its name.
+NOTE: Resolution codes are stored as strings, not numbers.
+
+This function will only ask JIRA for the list of codes once, than
+will cache it."
+  (unless jiralib-resolution-code-cache
+    (setq jiralib-resolution-code-cache
+          (jiralib-make-assoc-list (jiralib-call "getResolutions" nil) 'id 'name)))
+  jiralib-resolution-code-cache)
+
+;; NOTE: it is not such a good idea to use this, as it needs a JIRA
+;; connection to construct the regexp (the user might be prompted for a JIRA
+;; username and password).
+;;
+;; The best use of this function is to generate the regexp once-off and
+;; persist it somewhere.
+;;
+;; FIXME: Probably just deprecate/remove this, we can assert we're on
+;; an issue with a general regexp that matches the common format, vs
+;; needing to know specific user project list.
+(defun jiralib-get-issue-regexp ()
+  "Return a regexp that will match an issue id.
+
+The regexp is constructed from the project keys in the JIRA
+database.  An issue is assumed to be in the format KEY-NUMBER,
+where KEY is a project key and NUMBER is the issue number."
+  (unless jiralib-issue-regexp
+    (let ((projects (mapcar (lambda (e) (downcase (cdr (assoc 'key e))))
+                            (jiralib-call "getProjectsNoSchemes" nil))))
+      (when projects
+        (setq jiralib-issue-regexp
+              (concat "\\<" (regexp-opt projects) "-[0-9]+\\>")))))
+  jiralib-issue-regexp)
+
+(defun jiralib-do-jql-search (jql &optional limit callback)
+  "Run a JQL query and return the list of issues that matched.
+LIMIT is the maximum number of queries to return.  Note that JIRA
+has an internal limit of how many queries to return, as such, it
+might not be possible to find *ALL* the issues that match a
+query."
+  (unless (or limit (numberp limit))
+    (setq limit 100))
+  (jiralib-call "getIssuesFromJqlSearch" callback jql limit))
+
+(defcustom jiralib-available-actions-cache-p t
+  "Set to t to enable caching for jiralib-get-available-actions.
+
+If nil, will disable caching for this endpoint.
+
+Possible side-effects:
+
+  - If the server has the project workflow updated, the cache
+saved here will be incorrect.
+
+  - If the issue is not up to date with the remote, the wrong
+cache key may be queried."
+  :type 'boolean
+  :group 'jiralib)
+
+(defvar jiralib-available-actions-cache nil "An alist of available actions.")
+
+(defun jiralib-get-available-actions (issue-key &optional status)
+  "Return the available workflow actions for ISSUE-KEY.
+This uses STATUS as the cache key.
+This runs the getAvailableActions SOAP method."
+  (if (and jiralib-available-actions-cache-p status)
+      (progn
+        (unless (assoc status jiralib-available-actions-cache)
+          (push (cons status
+                      (jiralib-make-assoc-list
+                       (mapcar (lambda (x)
+                                 (let ((namestring (cdr (car x)))
+                                       (id (cdr x)))
+                                   (cons
+                                    (cons 'name (org-jira-decode namestring))
+                                    id)))
+                               (jiralib-call "getAvailableActions" nil issue-key))
+                       'id 'name))
+                jiralib-available-actions-cache))
+        (cdr (assoc status jiralib-available-actions-cache)))
+    (progn
+      (jiralib-make-assoc-list
+       (mapcar (lambda (x)
+                 (let ((namestring (cdr (car x)))
+                       (id (cdr x)))
+                   (cons
+                    (cons 'name (org-jira-decode namestring))
+                    id)))
+               (jiralib-call "getAvailableActions" nil issue-key))
+       'id 'name))))
+
+(defcustom jiralib-fields-for-action-cache-p t
+  "Set to t to enable caching for jiralib-get-fields-for-action.
+
+If nil, will disable caching for this endpoint.
+
+Possible side-effects:
+
+  - If many tasks have different workflows, you may want to disable this."
+  :type 'boolean
+  :group 'jiralib)
+
+(defvar jiralib-fields-for-action-cache nil "An alist of available fields.")
+
+(defun jiralib-get-fields-for-action-with-cache (issue-key action-id)
+  "Return the required fields to change ISSUE-KEY to ACTION-ID."
+  (if (and jiralib-fields-for-action-cache-p action-id)
+      (progn
+        (unless (assoc action-id jiralib-fields-for-action-cache)
+          (push (cons action-id
+                      (jiralib-call "getFieldsForAction" nil issue-key action-id))
+                jiralib-fields-for-action-cache))
+        (cdr (assoc action-id jiralib-fields-for-action-cache)))
+    (jiralib-call "getFieldsForAction" nil issue-key action-id)))
+
+(defun jiralib-get-fields-for-action (issue-key action-id)
+  "Return the required fields to change ISSUE-KEY to ACTION-ID."
+  (if jiralib-use-restapi
+      (let ((fields (jiralib-get-fields-for-action-with-cache issue-key action-id)))
+        (mapcar (lambda (field)
+                  (cons (symbol-name (car field))
+                        (format "%s (required: %s)"
+                                (org-jira-find-value field 'name)
+                                (if (eq (org-jira-find-value field 'required) :json-false)
+                                    "nil"
+                                  "t"))))
+                fields))
+    (jiralib-make-assoc-list
+     (jiralib-get-fields-for-action-with-cache issue-key action-id)
+     'id 'name)))
+
+(defun jiralib-progress-workflow-action (issue-key action-id params &optional callback)
+  "Progress issue with ISSUE-KEY to action ACTION-ID, and provide the needed PARAMS.
+
+When CALLBACK is present, this will run async."
+  (if jiralib-use-restapi
+      (jiralib-call "progressWorkflowAction"
+                    callback issue-key `((transition (id . ,action-id)))
+                    `((fields . ,params)))
+    (jiralib-call "progressWorkflowAction"
+                  callback issue-key action-id (jiralib-make-remote-field-values params))))
+
+
+(defun jiralib-format-datetime (&optional datetime)
+  "Convert a mixed DATETIME format into the Jira required datetime format.
+
+This will produce a datetime string such as:
+
+  2010-02-05T14:30:00.000+0000
+
+for being consumed in the Jira API.
+
+If DATETIME is not passed in, it will default to the current time."
+  (let* ((defaults (format-time-string "%Y-%m-%d %H:%M:%S" (current-time)))
+         (datetime (concat datetime (subseq defaults (length datetime))))
+         (parts (parse-time-string datetime)))
+    (format "%04d-%02d-%02dT%02d:%02d:%02d.000+0000"
+            (nth 5 parts)
+            (nth 4 parts)
+            (nth 3 parts)
+            (nth 2 parts)
+            (nth 1 parts)
+            (nth 0 parts))))
+
+(defvar jiralib-worklog-coming-soon-message
+  "WORKLOG FEATURES ARE NOT IMPLEMENTED YET, COMING SOON!")
+
+(defun jiralib-add-worklog-and-autoadjust-remaining-estimate (issue-key start-date time-spent comment)
+  "Log time spent on ISSUE-KEY to its worklog.
+The time worked begins at START-DATE and has a TIME-SPENT
+duration.  JIRA will automatically update the remaining estimate
+by subtracting TIME-SPENT from it.
+
+START-DATE should be in the format 2010-02-05T14:30:00Z
+
+TIME-SPENT can be in one of the following formats: 10m, 120m
+hours; 10h, 120h days; 10d, 120d weeks.
+
+COMMENT will be added to this worklog."
+  (let ((formatted-start-date (jiralib-format-datetime start-date)))
+    (jiralib-call "addWorklogAndAutoAdjustRemainingEstimate"
+                  nil
+                  issue-key
+                  ;; Expects data such as: '{"timeSpent":"1h", "started":"2017-02-21T00:00:00.000+0000", "comment":"woot!"}'
+                  ;; and only that format will work (no loose formatting on the started date)
+                  `((started   . ,formatted-start-date)
+                    (timeSpent . ,time-spent)
+                    (comment   . ,comment)))))
+
+\f
+;;;; Issue field accessors
+
+(defun jiralib-issue-key (issue)
+  "Return the key of ISSUE."
+  (cdr (assoc 'key issue)))
+
+(defun jiralib-issue-owner (issue)
+  "Return the owner of ISSUE."
+  (cdr (assq 'assignee issue)))
+
+(defun jiralib-issue-status (issue)
+  "Return the status of ISSUE as a status string (not as a number!)."
+  (let ((status-code (cdr (assq 'status issue))))
+    (cdr (assoc status-code (jiralib-get-statuses)))))
+
+(defun jiralib-custom-field-value (custom-field issue)
+  "Return the value of CUSTOM-FIELD for ISSUE.
+Return nil if the field is not found"
+  (catch 'found
+    (dolist (field (cdr (assq 'customFieldValues issue)))
+      (when (equal (cdr (assq 'customfieldId field)) custom-field)
+        (throw 'found (cadr (assq 'values field)))))))
+
+(defvar jiralib-current-issue nil
+  "This holds the currently selected issue.")
+
+(defvar jiralib-projects-list nil
+  "This holds a list of projects and their details.")
+
+(defvar jiralib-types nil
+  "This holds a list of issues types.")
+
+(defvar jiralib-priorities nil
+  "This holds a list of priorities.")
+
+(defvar jiralib-user-fullnames nil
+  "This holds a list of user fullnames.")
+
+(defun jiralib-get-project-name (key)
+  "Return the name of the JIRA project with id KEY."
+  (let ((projects jiralib-projects-list)
+        (name nil))
+    (dolist (project projects)
+      (if (equal (cdr (assoc 'key project)) key)
+          (setf name (cdr (assoc 'name project)))))
+    name))
+
+(defun jiralib-get-type-name (id)
+  "Return the name of the issue type with ID."
+  (let ((types jiralib-types)
+        (name nil))
+    (dolist (type types)
+      (if (equal (cdr (assoc 'id type)) id)
+          (setf name (cdr (assoc 'name type)))))
+    name))
+
+;; (defun jiralib-get-user-fullname (account-id)
+;;   "Return the full name (display name) of the user with USERNAME."
+  ;; (if (assoc account-id jiralib-user-fullnames)
+  ;;     (cdr (assoc account-id jiralib-user-fullnames))
+  ;;   (progn
+  ;;     (let ((user (jiralib-get-user account-id)))
+  ;;       (setf jiralib-user-fullnames (append jiralib-user-fullnames (list (cons account-id (cdr (assoc 'fullname user))))))
+  ;;       (cdr (assoc 'fullname user))))))
+
+(defun jiralib-get-user-fullname (account-id)
+  "Return the full name (displayName) of the user with ACCOUNT-ID."
+  (cl-loop for user in (jiralib-get-users nil)
+        when (rassoc account-id user)
+        return (cdr (assoc 'displayName user))))
+
+(defun jiralib-get-user-account-id (project full-name)
+    "Return the account-id (accountId) of the user with FULL-NAME (displayName) in PROJECT."
+  (cl-loop for user in (jiralib-get-users project)
+        when (rassoc full-name user)
+        return (cdr (assoc 'accountId user))))
+
+(defun jiralib-get-filter (filter-id)
+  "Return a filter given its FILTER-ID."
+  (cl-flet ((id-match (filter)
+                      (equal filter-id (cdr (assoc 'id filter)))))
+    (cl-find-if 'id-match (jiralib-get-saved-filters))))
+
+(defun jiralib-get-filter-alist ()
+  "Return an association list mapping filter names to IDs."
+  (mapcar (lambda (filter)
+            (cons (cdr (assoc 'name filter))
+                  (cdr (assoc 'id filter))))
+          (jiralib-get-saved-filters)))
+
+(defun jiralib-add-comment (issue-key comment &optional callback)
+  "Add to issue with ISSUE-KEY the given COMMENT, invoke CALLBACK."
+  (jiralib-call "addComment" callback issue-key `((body . ,comment))))
+
+(defun jiralib-edit-comment (issue-id comment-id comment &optional callback)
+  "Edit ISSUE-ID's comment COMMENT-ID to reflect the new COMMENT, invoke CALLBACK."
+  (if (not jiralib-use-restapi)
+      (jiralib-call "editComment" callback `((id . ,comment-id)
+                                             (body . ,comment)))
+    (jiralib-call "editComment" callback issue-id comment-id comment)))
+
+(defun jiralib-create-issue (issue)
+  "Create a new ISSUE in JIRALIB.
+
+ISSUE is a Hashtable object."
+  (jiralib-call "createIssue" nil issue))
+
+(defun jiralib-create-subtask (subtask)
+  "Create SUBTASK for issue with PARENT-ISSUE-ID.
+
+SUBTASK is a Hashtable object."
+  (jiralib-call "createIssueWithParent" nil subtask))
+
+(defvar jiralib-subtask-types-cache nil)
+
+(defun jiralib-get-subtask-types ()
+  "Return an assoc list mapping an issue type code to its name.
+NOTE: Issue type codes are stored as strings, not numbers.
+
+This function will only ask JIRA for the list of codes once, than
+will cache it."
+  (unless jiralib-subtask-types-cache
+    (setq jiralib-subtask-types-cache
+          (jiralib-make-assoc-list (jiralib-call "getSubTaskIssueTypes" nil) 'id 'name)))
+  jiralib-subtask-types-cache)
+
+(defun jiralib-get-comment (issue-key comment-id &optional callback)
+  "Return all comments associated with issue ISSUE-KEY, invoking CALLBACK."
+  (jiralib-call "getComment" callback issue-key comment-id))
+
+(defun jiralib-get-comments (issue-key &optional callback)
+  "Return all comments associated with issue ISSUE-KEY, invoking CALLBACK."
+  (jiralib-call "getComments" callback issue-key))
+
+(defun jiralib-get-attachments (issue-key &optional callback)
+  "Return all attachments associated with issue ISSUE-KEY, invoking CALLBACK."
+  (jiralib-call "getAttachmentsFromIssue" callback issue-key))
+
+(defun jiralib-get-worklogs (issue-key &optional callback)
+  "Return all worklogs associated with issue ISSUE-KEY, invoking CALLBACK."
+  (jiralib-call "getWorklogs" callback issue-key))
+
+(defun jiralib-add-worklog (issue-id started time-spent-seconds comment &optional callback)
+  "Add the worklog linked to ISSUE-ID.
+
+Requires STARTED (a jira datetime), TIME-SPENT-SECONDS (integer) and a COMMENT.
+CALLBACK will be invoked if passed in upon endpoint completion."
+  ;; Call will fail if 0 seconds are set as the time, so always do at least one min.
+  (setq time-spent-seconds (max 60 time-spent-seconds))
+  (let ((worklog `((started . ,started)
+                   ;; @TODO :worklog: timeSpentSeconds changes into incorrect values
+                   ;; in the Jira API (for instance, 89600 = 1 day, but Jira thinks 3 days...
+                   ;; We should convert to a Xd Xh Xm format from our seconds ourselves.
+                   (timeSpentSeconds . ,time-spent-seconds)
+                   (comment . ,comment))))
+    (jiralib-call "addWorklog" callback issue-id worklog)))
+
+(defun jiralib-update-worklog (issue-id worklog-id started time-spent-seconds comment &optional callback)
+  "Update the worklog linked to ISSUE-ID and WORKLOG-ID.
+
+Requires STARTED (a jira datetime), TIME-SPENT-SECONDS (integer) and a COMMENT.
+CALLBACK will be invoked if passed in upon endpoint completion."
+  ;; Call will fail if 0 seconds are set as the time, so always do at least one min.
+  (setq time-spent-seconds (max 60 time-spent-seconds))
+  (let ((worklog `((started . ,started)
+                   ;; @TODO :worklog: timeSpentSeconds changes into incorrect values
+                   ;; in the Jira API (for instance, 89600 = 1 day, but Jira thinks 3 days...
+                   ;; We should convert to a Xd Xh Xm format from our seconds ourselves.
+                   (timeSpentSeconds . ,time-spent-seconds)
+                   (comment . ,comment))))
+    (jiralib-call "updateWorklog" callback issue-id worklog-id worklog)))
+
+(defvar jiralib-components-cache nil "An alist of project components.")
+
+(defun jiralib-get-components (project-key)
+  "Return all components available in the project PROJECT-KEY."
+  (unless (assoc project-key jiralib-components-cache)
+    (push (cons project-key
+                (jiralib-make-assoc-list
+                 (jiralib-call "getComponents" nil project-key) 'id 'name))
+          jiralib-components-cache))
+  (cdr (assoc project-key jiralib-components-cache)))
+
+(defun jiralib-get-issue (issue-key &optional callback)
+  "Get the issue with key ISSUE-KEY, running CALLBACK after."
+  (jiralib-call "getIssue" callback issue-key))
+
+(defun jiralib-get-issues-from-filter (filter-id)
+  "Get the issues from applying saved filter FILTER-ID."
+  (message "jiralib-get-issues-from-filter is NOT IMPLEMENTED!!  Do not use!")
+  (jiralib-call "getIssuesFromFilter" nil filter-id))
+
+(defun jiralib-get-issues-from-text-search (search-terms)
+  "Find issues using free text search SEARCH-TERMS."
+  (jiralib-call "getIssuesFromTextSearch" nil search-terms))
+
+(defun jiralib-get-issues-from-text-search-with-project
+    (project-keys search-terms max-num-results)
+  "Find issues in projects PROJECT-KEYS, using free text search SEARCH-TERMS.
+
+Return no more than MAX-NUM-RESULTS."
+  (jiralib-call "getIssuesFromTextSearchWithProject"
+                nil
+                (apply 'vector project-keys) search-terms max-num-results))
+
+;; Modified by Brian Zwahr to use getProjectsNoSchemes instead of getProjects
+(defun jiralib-get-projects ()
+  "Return a list of projects available to the user."
+  (if jiralib-projects-list
+      jiralib-projects-list
+    (setq jiralib-projects-list
+          (if jiralib-use-restapi
+              (jiralib-call "getProjects" nil)
+            (jiralib-call "getProjectsNoSchemes" nil)))))
+
+(defun jiralib-get-saved-filters ()
+  "Get all saved filters available for the currently logged in user."
+  (jiralib-make-assoc-list (jiralib-call "getSavedFilters" nil) 'id 'name))
+
+(defun jiralib-get-server-info ()
+  "Return the Server information such as baseUrl, version, edition, buildDate, buildNumber."
+  (jiralib-call "getServerInfo" nil))
+
+(defun jiralib-get-sub-task-issue-types ()
+  "Return all visible subtask issue types in the system."
+  (jiralib-call "getSubTaskIssueTypes" nil))
+
+(defun jiralib-get-user (account-id)
+  "Return a user's information given their full name."
+  (cond ((eq 0 (length account-id)) nil) ;; Unassigned
+        (t (jiralib-call "getUser" nil account-id))))
+
+(defvar jiralib-users-cache nil "Cached list of users.")
+
+(defun jiralib-get-users (project-key)
+  "Return assignable users information given the PROJECT-KEY."
+  (unless jiralib-users-cache
+    (setq jiralib-users-cache
+          (jiralib-call "getUsers" nil project-key))
+    (cl-loop for (name . id) in org-jira-users do
+          (setf jiralib-users-cache (append (list (jiralib-get-user id)) jiralib-users-cache))))
+  jiralib-users-cache)
+
+(defun jiralib-get-versions (project-key)
+  "Return all versions available in project PROJECT-KEY."
+  (jiralib-call "getVersions" nil project-key))
+
+(defun jiralib-strip-cr (string)
+  "Remove carriage returns from STRING."
+  (when string (replace-regexp-in-string "\r" "" string)))
+
+(defun jiralib-worklog-import--filter-apply
+    (worklog-obj &optional predicate-fn-lst unwrap-worklog-records-fn rewrap-worklog-records-fn)
+  "Remove non-matching org-jira issue worklogs.
+
+Variables:
+  WORKLOG-OBJ is the passed in object
+  PREDICATE-FN-LST is the list of lambdas used as match predicates.
+  UNWRAP-WORKLOG-RECORDS-FN is the function used to produce the list of worklog records from within the worklog-obj
+  REWRAP-WORKLOG-RECORDS-FN is the function used to reshape the worklog records back into the form they were received in.
+
+Auxiliary Notes:
+  Only the WORKLOG-OBJ variable is required.
+  The value of PPREDICATE-FN-LST is filled from the jiralib-worklog-import--filters-alist variable by default.
+  If PREDICATE-FN-LST is empty the unmodified value of WORKLOG-OBJ is returned.
+  If PREDICATE-FN-LST contains multiple predicate functions, each predicate filters operates as a clause in an AND match.  In effect, a worklog must match all predicates to be returned.
+  The variable 'jiralib-user-login-name is used by many lambda filters."
+
+  (let
+      ((unwrap-worklog-records-fn)
+       (rewrap-worklog-records-fn)
+       (predicate-fn-lst)
+       (worklogs worklog-obj)
+       (predicate-fn))
+    ;; let-body
+    (progn
+      (setq unwrap-worklog-records-fn
+            (if (and
+                 (boundp 'unwrap-worklog-records-fn)
+                 (functionp unwrap-worklog-records-fn))
+                unwrap-worklog-records-fn
+              (lambda (x) (cl-coerce x 'list))))
+      (setq rewrap-worklog-records-fn
+            (if (and
+                 (boundp 'rewrap-worklog-records-fn)
+                 (functionp rewrap-worklog-records-fn))
+                rewrap-worklog-records-fn
+              (lambda (x) (remove 'nil (cl-coerce x 'vector)))))
+      (setq predicate-fn-lst
+            (if (and (boundp 'predicate-fn-lst)
+                     (not (null predicate-fn-lst))
+                     (listp predicate-fn-lst))
+                predicate-fn-lst
+              (mapcar 'caddr
+                      (remove 'nil
+                              (mapcar (lambda (x) (unless (null (car x)) x))
+                                      jiralib-worklog-import--filters-alist)))))
+      ;; final condition/sanity checks before processing
+      (cond
+       ;; pass cases, don't apply filters, return unaltered worklog-obj
+       ((or (not (boundp 'predicate-fn-lst)) (not (listp predicate-fn-lst)) (null predicate-fn-lst))
+        worklog-obj)
+       ;; default-case, apply worklog filters and return only matching worklogs
+       (t
+        (setq worklogs (funcall unwrap-worklog-records-fn worklogs))
+        (while (setq predicate-fn (pop predicate-fn-lst))
+          (setq worklogs (mapcar predicate-fn worklogs)))
+        (funcall rewrap-worklog-records-fn worklogs))))))
+
+
+(defun jiralib-get-board (id &optional callback)
+  "Return details on given board"
+  (jiralib-call "getBoard" nil id))
+
+(defun jiralib-get-boards ()
+  "Return list of jira boards"
+  (jiralib-call "getBoards" nil))
+
+(defun jiralib-get-board-sprints (id)
+  "Return list of jira sprints in the specified jira board"
+  (jiralib-call "getSprintsFromBoard" nil id))
+
+(defun jiralib-get-sprint-issues (id &rest params)
+  "Return list of issues in the specified sprint"
+  (apply 'jiralib-call "getIssuesFromSprint"
+        (cl-getf params :callback) id params))
+
+(defun jiralib-get-board-issues (board-id &rest params)
+  "Return list of jira issues in the specified jira board"
+  (apply 'jiralib-call "getIssuesFromBoard"
+        (cl-getf params :callback) board-id params))
+
+(defvar jiralib-labels-cache nil)
+(defun jiralib-get-labels ()
+  "Return assignable labels that can be added to an issue."
+  (unless jiralib-labels-cache
+    (setq jiralib-labels-start-at 0)
+    (while (progn
+             (let* ((labels (jiralib-call "getLabels" nil jiralib-labels-start-at))
+                    (max-results (alist-get 'maxResults labels))
+                    (is-last (alist-get 'isLast labels))
+                    (values (alist-get 'values labels)))
+               (setq jiralib-labels-start-at (+ max-results jiralib-labels-start-at)
+                     jiralib-labels-cache (append values jiralib-labels-cache))
+               (not (eq is-last t)))))))
+
+(defun jiralib--agile-not-last-entry (num-entries total start-at limit)
+  "Return true if need to retrieve next page from agile api"
+  (and (> num-entries 0)
+       (or (not limit) ; not required to be set
+          (< limit 1) ; ignore invalid limit
+          (> limit start-at))
+       (or (not total) ; not always returned
+           (> total start-at))))
+
+(defun jiralib--agile-limit-page-size (page-size start-at limit)
+  (if (and limit
+          (> (+ start-at page-size) limit))
+      (- limit  start-at)
+    page-size))
+
+
+(defun jiralib--agile-rest-call-it (api max-results start-at limit query-params)
+  (let ((callurl
+        (format "%s?%s" api
+                (url-build-query-string
+                 (append `((maxResults ,(jiralib--agile-limit-page-size max-results start-at limit))
+                           (startAt ,start-at))
+                         query-params)))))
+    (jiralib--rest-call-it callurl)))
+
+(defun jiralib--agile-call-it (api values-key &rest params)
+  "Invoke Jira agile method api and retrieve the results using
+paging.
+
+If JIRALIB-COMPLETE-CALLBACK is non-nil, then the call will be
+performed asynchronously and JIRALIB-COMPLETE-CALLBACK will be
+called when all data are retrieved.
+
+If JIRALIB-COMPLETE-CALLBACK is nil, then the call will be
+performed synchronously and this function will return the
+retrieved data.
+
+API - path to called API that must start with /rest/agile/1.0.
+
+VALUES-KEY - key of the actual reply data in the reply assoc list.
+
+PARAMS - optional additional parameters.
+:limit - limit total number of retrieved entries.
+:query-params - extra query parameters in the format of url-build-query-string.
+"
+  (if jiralib-complete-callback
+      (apply 'jiralib--agile-call-async api values-key params)
+    (apply 'jiralib--agile-call-sync api values-key params)))
+
+(defun jiralib--agile-call-sync (api values-key &rest params)
+  "Syncroniously invoke Jira agile method api retrieve all the
+results using paging and return results.
+
+VALUES-KEY - key of the actual reply data in the reply assoc list.
+
+PARAMS - extra parameters (as keyword arguments), the supported parameters are:
+
+:limit - limit total number of retrieved entries.
+:query-params - extra query parameters in the format of url-build-query-string.
+"
+  (setq jiralib-complete-callback nil)
+  (let ((not-last t)
+        (start-at 0)
+       (limit (cl-getf params :limit))
+       (query-params (cl-getf params :query-params))
+       ;; maximum page size, 50 is server side maximum
+        (max-results jiralib-agile-page-size)
+        (values ()))
+    (while not-last
+      (let* ((reply-alist
+             (jiralib--agile-rest-call-it api max-results start-at limit query-params))
+             (values-array (cdr (assoc values-key reply-alist)))
+             (num-entries (length values-array))
+             (total (cdr (assq 'total reply-alist))))
+        (setf values (append values (append values-array nil)))
+        (setf start-at (+ start-at num-entries))
+        (setf not-last (jiralib--agile-not-last-entry num-entries total start-at limit))))
+    values))
+
+(defun jiralib--agile-call-async  (api values-key &rest params)
+  "Asyncroniously invoke Jira agile method api,
+retrieve all the results using paging and call
+JIRALIB-COMPLETE_CALLBACK when all the data are retrieved.
+
+VALUES-KEY - key of the actual reply data in the reply assoc list.
+
+PARAMS - extra parameters (as keyword arguments), the supported parameters are:
+
+limit - limit total number of retrieved entries."
+  (lexical-let
+      ((start-at 0)
+       (limit (cl-getf params :limit))
+       (query-params (cl-getf params :query-params))
+       ;; maximum page size, 50 is server side maximum
+       (max-results jiralib-agile-page-size)
+       (values-list ())
+       (vk values-key)
+       (url api)
+       ;; save the call back to be called later after the last page
+       (complete-callback jiralib-complete-callback))
+    ;; setup new callback to be called after each page
+    (setf jiralib-complete-callback
+          (cl-function
+           (lambda  (&rest data &allow-other-keys)
+             (condition-case err
+                 (let* ((reply-alist (cl-getf data :data))
+                        (values-array (cdr (assoc vk reply-alist)))
+                        (num-entries (length values-array))
+                        (total (cdr (assq 'total reply-alist))))
+                   (setf values-list (append values-list (append values-array nil)))
+                   (setf start-at (+ start-at num-entries))
+                   (message "jiralib agile retrieve: got %d values%s%s"
+                            start-at
+                            (if total " of " "")
+                            (if total (int-to-string total) ""))
+                   (if (jiralib--agile-not-last-entry num-entries total start-at limit)
+                      (jiralib--agile-rest-call-it url max-results start-at limit query-params)
+                     ;; last page: call originall callback
+                     (message "jiralib agile retrieve: calling callback")
+                     (setf jiralib-complete-callback complete-callback)
+                     (funcall jiralib-complete-callback
+                              :data  (list (cons vk  values-list)))
+                     (message "jiralib agile retrieve: all done")))
+               ('error (message (format "jiralib agile retrieve: caught error: %s" err)))))))
+    (jiralib--agile-rest-call-it api max-results start-at limit query-params)))
+
+(provide 'jiralib)
+;;; jiralib.el ends here
diff --git a/elpa/org-jira-20240928.2340/org-jira-autoloads.el b/elpa/org-jira-20240928.2340/org-jira-autoloads.el
new file mode 100644 (file)
index 0000000..7a4569a
--- /dev/null
@@ -0,0 +1,178 @@
+;;; org-jira-autoloads.el --- automatically extracted autoloads (do not edit)   -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from jiralib.el
+
+(register-definition-prefixes "jiralib" '("jiralib-"))
+
+\f
+;;; Generated autoloads from org-jira.el
+
+(autoload 'org-jira-mode "org-jira" "\
+Toggle org-jira mode.
+
+With no argument, the mode is toggled on/off.
+Non-nil argument turns mode on.
+Nil argument turns mode off.
+
+Commands:
+\\{org-jira-entry-mode-map}
+
+Entry to this mode calls the value of `org-jira-mode-hook'.
+
+This is a minor mode.  If called interactively, toggle the `Org-jira
+mode' mode.  If the prefix argument is positive, enable the mode, and if
+it is zero or negative, disable the mode.
+
+If called from Lisp, toggle the mode if ARG is `toggle'.  Enable the
+mode if ARG is nil, omitted, or is a positive number.  Disable the mode
+if ARG is a negative number.
+
+To check whether the minor mode is enabled in the current buffer,
+evaluate the variable `org-jira-mode'.
+
+The mode's hook is called both when the mode is enabled and when it is
+disabled.
+
+(fn &optional ARG)" t)
+(autoload 'org-jira-get-projects "org-jira" "\
+Get list of projects." t)
+(autoload 'org-jira-get-issue-list "org-jira" "\
+Get list of issues, using jql (jira query language), invoke CALLBACK after.
+
+Default is unresolved issues assigned to current login user; with
+a prefix argument you are given the chance to enter your own
+jql.
+
+(fn &optional CALLBACK)")
+(autoload 'org-jira-get-summary "org-jira" "\
+Get issue summary from point and place next to issue id from jira" t)
+(autoload 'org-jira-get-summary-url "org-jira" "\
+Get issue summary from point and place next to issue id from jira, and make issue id a link" t)
+(autoload 'org-jira-get-issues-headonly "org-jira" "\
+Get list of ISSUES, head only.
+
+The default behavior is to return issues assigned to you and unresolved.
+
+With a prefix argument, allow you to customize the jql.  See
+`org-jira-get-issue-list'.
+
+(fn ISSUES)" t)
+(autoload 'org-jira-get-issue "org-jira" "\
+Get a JIRA issue, allowing you to enter the issue-id first.
+
+(fn ID)" t)
+(autoload 'org-jira-get-issues-by-fixversion "org-jira" "\
+Get list of issues by FIXVERSION.
+
+(fn FIXVERSION)" t)
+(autoload 'org-jira-get-issue-project "org-jira" "\
+
+
+(fn ISSUE)")
+(autoload 'org-jira-get-issues "org-jira" "\
+Get list of ISSUES into an org buffer.
+
+Default is get unfinished issues assigned to you, but you can
+customize jql with a prefix argument.
+See`org-jira-get-issue-list'
+
+(fn ISSUES)" t)
+(autoload 'org-jira-get-issues-from-custom-jql "org-jira" "\
+Get JQL-LIST list of issues from a custom JQL and PROJ-KEY.
+
+The PROJ-KEY will act as the file name, while the JQL will be any
+valid JQL to populate a file to store PROJ-KEY results in.
+
+Please note that this is *not* concurrent or race condition
+proof.  If you try to run multiple calls to this function, it
+will mangle things badly, as they rely on globals DEFAULT-JQL and
+ORG-JIRA-PROJ-KEY-OVERRIDE being set before and after running.
+
+(fn &optional JQL-LIST)" t)
+(autoload 'org-jira-update-comment "org-jira" "\
+Update a comment for the current issue." t)
+(autoload 'org-jira-update-worklogs-from-org-clocks "org-jira" "\
+Update or add a worklog based on the org clocks." t)
+(autoload 'org-jira-copy-current-issue-key "org-jira" "\
+Copy the current issue's key into clipboard." t)
+(autoload 'org-jira-unassign-issue "org-jira" "\
+Update an issue to be unassigned." t)
+(autoload 'org-jira-set-issue-reporter "org-jira" "\
+Update an issue's reporter interactively." t)
+(autoload 'org-jira-assign-issue "org-jira" "\
+Update an issue with interactive re-assignment." t)
+(autoload 'org-jira-update-issue "org-jira" "\
+Update an issue." t)
+(autoload 'org-jira-todo-to-jira "org-jira" "\
+Convert an ordinary todo item to a jira ticket." t)
+(autoload 'org-jira-get-subtasks "org-jira" "\
+Get subtasks for the current issue." t)
+(autoload 'org-jira-update-issue-labels "org-jira" "\
+Update jira issue labels." t)
+(autoload 'org-jira-create-issue "org-jira" "\
+Create an issue in PROJECT, of type TYPE, with given SUMMARY and DESCRIPTION.
+
+(fn PROJECT TYPE SUMMARY DESCRIPTION)" t)
+(autoload 'org-jira-create-subtask "org-jira" "\
+Create a subtask issue for PROJECT, of TYPE, with SUMMARY and DESCRIPTION.
+
+(fn PROJECT TYPE SUMMARY DESCRIPTION)" t)
+(autoload 'org-jira-refresh-issue "org-jira" "\
+Refresh current issue from jira to org." t)
+(autoload 'org-jira-progress-issue "org-jira" "\
+Progress issue workflow." t)
+(autoload 'org-jira-progress-issue-next "org-jira" "\
+Progress issue workflow." t)
+(autoload 'org-jira-browse-issue "org-jira" "\
+Open the current issue in external browser." t)
+(autoload 'org-jira-download-attachment "org-jira" "\
+Download the attachment under cursor." t)
+(autoload 'org-jira-get-issues-from-filter "org-jira" "\
+Get issues from the server-side stored filter named FILTER.
+
+Provide this command in case some users are not able to use
+client side jql (maybe because of JIRA server version?).
+
+(fn FILTER)" t)
+(autoload 'org-jira-get-issues-from-filter-headonly "org-jira" "\
+Get issues *head only* from saved filter named FILTER.
+See `org-jira-get-issues-from-filter'.
+
+(fn FILTER)" t)
+(autoload 'org-jira-get-issues-by-board "org-jira" "\
+Get list of ISSUES from agile board." t)
+(autoload 'org-jira-get-issues-by-sprint "org-jira" "\
+Get list of ISSUES from sprint." t)
+(autoload 'org-jira-get-issues-by-board-headonly "org-jira" "\
+Get list of ISSUES from agile board, head only." t)
+(autoload 'org-jira-get-boards "org-jira" "\
+Get list of boards and their properties." t)
+(register-definition-prefixes "org-jira" '("ensure-on-" "org-jira-"))
+
+\f
+;;; Generated autoloads from org-jira-sdk.el
+
+(register-definition-prefixes "org-jira-sdk" '("org-jira-sdk-"))
+\f
+;;; End of scraped data
+
+(provide 'org-jira-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; org-jira-autoloads.el ends here
diff --git a/elpa/org-jira-20240928.2340/org-jira-pkg.el b/elpa/org-jira-20240928.2340/org-jira-pkg.el
new file mode 100644 (file)
index 0000000..1dd0f16
--- /dev/null
@@ -0,0 +1,12 @@
+;; -*- no-byte-compile: t; lexical-binding: nil -*-
+(define-package "org-jira" "20240928.2340"
+  "Syncing between Jira and Org-mode."
+  '((emacs   "24.5")
+    (cl-lib  "0.5")
+    (request "0.2.0")
+    (dash    "2.14.1"))
+  :url "https://github.com/ahungry/org-jira"
+  :commit "5f591f5f4abd0ef12c64676e38d8ec3b13eba280"
+  :revdesc "5f591f5f4abd"
+  :keywords '("ahungry" "jira" "org" "bug" "tracker")
+  :maintainers '(("Matthew Carter" . "mNOSPAMahungry.com")))
diff --git a/elpa/org-jira-20240928.2340/org-jira-sdk.el b/elpa/org-jira-20240928.2340/org-jira-sdk.el
new file mode 100644 (file)
index 0000000..49032f5
--- /dev/null
@@ -0,0 +1,227 @@
+;;; org-jira-sdk.el -- SDK Layer for entities
+
+;; Copyright (C) 2018 Matthew Carter <mNOSPAMahungry.com>
+;;
+;; Authors:
+;; Matthew Carter <mNOSPAMahungry.com>
+;;
+;; Maintainer: Matthew Carter <mNOSPAMahungry.com>
+;; URL: https://github.com/ahungry/org-jira
+;; Keywords: ahungry jira org bug tracker
+;; Package-Requires: ((emacs "24.5") (cl-lib "0.5") (request "0.2.0") (s "0.0.0"))
+
+;; This file is not part of GNU Emacs.
+
+;; 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 of the License, 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 this program.  If not, see
+;; <http://www.gnu.org/licenses/> or write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301, USA.
+
+;;; Commentary:
+
+;; This provides an SDK wrapper for more strictly defining the entities we interact with.
+
+;;; Code:
+
+(require 'jiralib)
+(require 'cl-lib)
+(require 'eieio)
+(require 'dash)
+(require 'cl-generic)
+
+(defclass org-jira-sdk-record ()
+  ((id :initarg :id :type string :required t)
+   (data :initarg :data :documentation "The area to hold a big alist of data.")
+   (hydrate-fn :initform (lambda (id) (message "Not implemented."))))
+  "The ID of the record.")
+
+(defun org-jira-sdk-string-but-first (s) (cl-subseq s 1))
+
+(defun org-jira-sdk-to-string (s) (format "%s" s))
+
+(defun org-jira-sdk-to-prefixed-string (s) (format "org-jira-sdk-%s" s))
+
+(defun org-jira-sdk-record-type-to-symbol (record-type)
+  (-> record-type symbol-name org-jira-sdk-string-but-first org-jira-sdk-to-prefixed-string intern))
+
+(defun org-jira-sdk-create-from-id (record-type id &optional parent-id callback)
+  (let ((rec (funcall (org-jira-sdk-record-type-to-symbol record-type)
+                      :id (format "%s" id)
+                      :parent-id parent-id)))
+    (with-slots (data) rec
+      (setf data (org-jira-sdk-hydrate rec callback))
+      (org-jira-sdk-from-data rec))))
+
+(defun org-jira-sdk-create-from-data (record-type data)
+  (let ((rec (funcall (org-jira-sdk-record-type-to-symbol record-type) :data data)))
+    (org-jira-sdk-from-data rec)))
+
+(cl-defmethod org-jira-sdk-hydrate ((rec org-jira-sdk-record) &optional callback)
+  "Populate the record with data from the remote endpoint."
+  (with-slots (id hydrate-fn) rec
+    (funcall hydrate-fn id callback)))
+
+(cl-defgeneric org-jira-sdk-from-data ((rec org-jira-sdk-record)))
+
+(cl-defmethod org-jira-sdk-dump ((rec org-jira-sdk-record))
+  "A decent pretty print/object dump for working with the class items."
+  (let ((slots (mapcar (lambda (slot) (aref slot 1)) (eieio-class-slots (eieio-object-class rec)))))
+    (setq slots (cl-remove-if (lambda (s) (not (slot-boundp rec s))) slots))
+    (apply #'concat
+     (mapcar (lambda (slot)
+               (let ((slot (intern (org-jira-sdk-to-string slot))))
+                 (format "\n%+16s:   %s" slot (slot-value rec (intern (org-jira-sdk-to-string slot)))))
+               )
+             slots))))
+
+(defun org-jira-sdk-path (alist key-chain)
+  "Query a nested path in some type of ALIST by traversing down the keys of KEY-CHAIN."
+  (cl-reduce (lambda (a k) (alist-get k a)) key-chain :initial-value alist))
+
+(defclass org-jira-sdk-issue (org-jira-sdk-record)
+  ((assignee :type (or null string) :initarg :assignee)
+   (components :type string :initarg :components)
+   (labels :type string :initarg :labels)
+   (created :type string :initarg :created)
+   (description :type (or null string) :initarg :description)
+   (duedate :type (or null string) :initarg :duedate)
+   (headline :type string :initarg :headline)
+   (id :type string :initarg :id)       ; TODO: Probably remove me
+   (issue-id :type string :initarg :issue-id :documentation "The common ID/key, such as EX-1.")
+   (issue-id-int :type string :initarg :issue-id-int :documentation "The internal Jira ID, such as 12345.")
+   (filename :type (or null string) :initarg :filename :documentation "The filename to write issue to.")
+   (priority :type (or null string) :initarg :priority)
+   (proj-key :type string :initarg :proj-key)
+   (reporter :type (or null string) :initarg :reporter)
+   (resolution :type (or null string) :initarg :resolution)
+   (sprint :type (or null string) :initarg :sprint)
+   (start-date :type (or null string) :initarg :start-date)
+   (status :type string :initarg :status)
+   (summary :type string :initarg :summary)
+   (type :type string :initarg :type)
+   (type-id :type string :initarg :type-id)
+   (updated :type string :initarg :updated)
+   (data :initarg :data :documentation "The remote Jira data object (alist).")
+   (hydrate-fn :initform #'jiralib-get-issue :initarg :hydrate-fn))
+  "An issue on the end.  ID of the form EX-1, or a numeric such as 10000.")
+
+(defclass org-jira-sdk-comment (org-jira-sdk-record)
+  ((author :type string :initarg :author)
+   (body :type string :initarg :body)
+   (comment-id :type string :initarg :comment-id :documentation "The comment ID, such as 12345.")
+   (created :type string :initarg :created)
+   (headline :type string :initarg :headline)
+   (parent-id :type string :initarg :parent-id :documentation "The parent issue-id such as EX-1.")
+   (updated :type string :initarg :updated)
+   (data :initarg :data :documentation "The reomte Jira data object (alist).")
+   (hydrate-fn :initform #'jiralib-get-comment :initarg :hydrate-fn)))
+
+(defclass org-jira-sdk-board (org-jira-sdk-record)
+  ((name :type string :initarg :name :required t)
+   (url  :type string :initarg :url  :required t)
+   (board-type :type string :initarg :board-type)
+   (jql  :type string :initarg :jql)
+   (limit :type integer :initarg :limit)
+   ;; unused
+   (parent-id :type string :initarg :parent-id)
+   (hydrate-fn :initform #'jiralib-get-board :initarg :hydrate-fn)))
+
+(cl-defmethod org-jira-sdk-hydrate ((rec org-jira-sdk-comment) &optional callback)
+  "Populate the record with data from the remote endpoint."
+  (with-slots (id proj-key hydrate-fn) rec
+    (funcall hydrate-fn proj-key id callback)))
+
+(cl-defmethod org-jira-sdk-from-data ((rec org-jira-sdk-issue))
+  (cl-flet ((path (keys) (org-jira-sdk-path (oref rec data) keys)))
+    (org-jira-sdk-issue
+     :assignee (path '(fields assignee displayName))
+     :components (mapconcat (lambda (c) (org-jira-sdk-path c '(name))) (path '(fields components)) ", ")
+     :labels (mapconcat (lambda (c) (format "%s" c)) (mapcar #'identity (path '(fields labels))) ", ")
+     :created (path '(fields created))     ; confirm
+     :description (or (path '(fields description)) "")
+     :duedate (or (path '(fields sprint endDate)) (path '(fields duedate)))         ; confirm
+     :filename (path '(fields project key))
+     :headline (path '(fields summary)) ; Duplicate of summary, maybe different.
+     :id (path '(key))
+     :issue-id (path '(key))
+     :issue-id-int (path '(id))
+     :priority (path '(fields priority name))
+     :proj-key (path '(fields project key))
+     :reporter (path '(fields reporter displayName)) ; reporter could be an object of its own slot values
+     :resolution (path '(fields resolution name))  ; confirm
+     :sprint (path '(fields sprint name))
+     :start-date (path '(fields start-date))  ; confirm
+     :status (org-jira-decode (path '(fields status name)))
+     :summary (path '(fields summary))
+     :type (path '(fields issuetype name))
+     :type-id (path '(fields issuetype id))
+     :updated (path '(fields updated))  ; confirm
+     ;; TODO: Remove this
+     ;; :data (oref rec data)
+     )))
+
+(cl-defmethod org-jira-sdk-from-data ((rec org-jira-sdk-comment))
+  (cl-flet ((path (keys) (org-jira-sdk-path (oref rec data) keys)))
+    (org-jira-sdk-comment
+     :author (path '(author displayName))
+     :body (path '(body))
+     :comment-id (path '(id))
+     :created (path '(created))
+     :headline (format "Comment: %s" (path '(author displayName)))
+     :parent-id (if (slot-boundp rec 'parent-id) (oref rec parent-id) "")
+     :updated (path '(updated))
+     ;; TODO: Remove this
+     ;; :data (oref rec data)
+     )))
+
+(cl-defmethod org-jira-sdk-from-data ((rec org-jira-sdk-board))
+  (with-slots (data) rec
+    (org-jira-sdk-board
+     :id   (int-to-string (alist-get 'id data))
+     :name (alist-get 'name data)
+     :url  (alist-get 'self data)
+     ;; TODO: remove it? used by org-jira-sdk-create-from-id
+     :parent-id ""
+     :board-type (alist-get 'type data))))
+
+;; Issue
+(defun org-jira-sdk-create-issue-from-data (d) (org-jira-sdk-create-from-data :issue d))
+(defun org-jira-sdk-create-issues-from-data-list (ds) (mapcar #'org-jira-sdk-create-issue-from-data ds))
+
+;; Issue with custom filename setting
+(defun org-jira-sdk-create-issue-from-data-with-filename (filename)
+  (lambda (d)
+    (let ((Issue (org-jira-sdk-create-from-data :issue d)))
+      (setf (oref Issue filename) filename)
+      Issue)))
+
+(defun org-jira-sdk-create-issues-from-data-list-with-filename (filename ds)
+  (let ((fn (org-jira-sdk-create-issue-from-data-with-filename filename)))
+    (mapcar fn ds)))
+
+;; Comment
+(defun org-jira-sdk-create-comment-from-data (d) (org-jira-sdk-create-from-data :comment d))
+(defun org-jira-sdk-create-comments-from-data-list (ds) (mapcar #'org-jira-sdk-create-comment-from-data ds))
+
+(defun org-jira-sdk-isa-record? (i) (cl-typep i 'org-jira-sdk-record))
+(defun org-jira-sdk-isa-issue? (i) (cl-typep i 'org-jira-sdk-issue))
+(defun org-jira-sdk-isa-comment? (i) (cl-typep i 'org-jira-sdk-comment))
+
+;; Board
+(defun org-jira-sdk-create-board-from-data (d) (org-jira-sdk-create-from-data :board d))
+(defun org-jira-sdk-create-boards-from-data-list (ds) (mapcar #'org-jira-sdk-create-board-from-data ds))
+
+(provide 'org-jira-sdk)
+
+;;; org-jira-sdk.el ends here
diff --git a/elpa/org-jira-20240928.2340/org-jira.el b/elpa/org-jira-20240928.2340/org-jira.el
new file mode 100644 (file)
index 0000000..6bb4a2a
--- /dev/null
@@ -0,0 +1,2630 @@
+;;; org-jira.el --- Syncing between Jira and Org-mode. -*- lexical-binding: t -*-
+
+;; Copyright (C) 2016-2022 Matthew Carter <mNOSPAMahungry.com>
+;; Copyright (C) 2011 Bao Haojun
+;;
+;; Authors:
+;; Matthew Carter <mNOSPAMahungry.com>
+;; Bao Haojun <baohaojunNOSPAMgmail.com>
+;;
+;; Maintainer: Matthew Carter <mNOSPAMahungry.com>
+;; URL: https://github.com/ahungry/org-jira
+;; Package-Version: 20240928.2340
+;; Package-Revision: 5f591f5f4abd
+;; Keywords: ahungry jira org bug tracker
+;; Package-Requires: ((emacs "24.5") (cl-lib "0.5") (request "0.2.0") (dash "2.14.1"))
+
+;; This file is not part of GNU Emacs.
+
+;; 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 of the License, 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 this program.  If not, see
+;; <http://www.gnu.org/licenses/> or write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+;; 02110-1301, USA.
+
+;;; Commentary:
+
+;; This provides an extension to org-mode for syncing issues with JIRA
+;; issue servers.
+
+;;; News:
+
+;;;; Changes in 4.4.2
+;; - Fix for org-insert-subheading behavior change in org 9.7+ in render-issues
+
+;;;; Changes in 4.4.1
+;; - Fix tag (4.3.3 was out of order - we had a 4.4.0 on repo)
+;; - Fix for some crazy scoping issue in the org-jira-get-issue-val-from-org function
+
+;;;; Changes in 4.3.3:
+;; - Address issue with assignee property being removed when Unassigned
+
+;;;; Changes in 4.3.2:
+;; - Fixes issues with org-jira-add-comment and org-jira-update-comment
+
+;;;; Changes in 4.3.1:
+;; - Fix to make custom-jql results sync worklogs properly.
+
+;;;; Changes in 4.3.0:
+;; - Allow org-jira-set-issue-reporter call to dynamically set this value.
+
+;;;; Changes in 4.1.0:
+;; - Allow custom-jql to be specified and render in special files (see: README.md).
+
+;;;; Changes in 4.0.0:
+;; - Introduce SDK type for handling records vs random alist structures.
+
+;;;; Changes since 3.1.0:
+;; - Fix how we were ruining the kill-ring with kill calls.
+
+;;;; Changes since 3.0.0:
+;; - Add new org-jira-add-comment call (C-c c c)
+
+;;;; Changes since 2.8.0:
+;; - New version 3.0.0 deprecates old filing mechanism and files
+;;   all of the changes under the top level ticket header.
+;; - If you want other top level headers in the same file, this should
+;;   work now, as long as they come after the main project one.
+
+;;;; Changes since 2.7.0:
+;; - Clean up multi-buffer handling, disable attachments call until
+;; - refresh is compatible with it.
+
+;;;; Changes since 2.6.3:
+;; - Insert worklog import filter in the existing org-jira-update-worklogs-for-current-issue function
+;; - Sync up org-clocks and worklogs!  Set org-jira-worklog-sync-p to nil to avoid.
+
+;;;; Changes since 2.6.1:
+;; - Fix bug with getting all issues when worklog is an error trigger.
+
+;;;; Changes since 2.5.4:
+;; - Added new org-jira-refresh-issues-in-buffer call and binding
+
+;;;; Changes since 2.5.3:
+;; - Re-introduce the commit that introduced a break into Emacs 25.1.1 list/array push
+;;     The commit caused updates/comment updates to fail when a blank list of components
+;;     was present - it will now handle both cases (full list, empty list).
+
+;;;; Changes since 2.5.2:
+;; - Revert a commit that introduced a break into Emacs 25.1.1 list/array push
+;;     The commit caused updates/comment updates to fail
+
+;;;; Changes since 2.5.1:
+;; - Only set duedate if a DEADLINE is present in the tags and predicate is t
+
+;;;; Changes since 2.5.0:
+;; - Allow overriding the org property names with new defcustom
+
+;;;; Changes since 2.4.0:
+;; - Fix many deprecation/warning issues
+;; - Fix error with allow-other-keys not being wrapped in cl-function
+
+;;;; Changes since 2.3.0:
+;; - Integration with org deadline and Jira due date fields
+
+;;;; Changes since 2.2.0:
+;; - Selecting issue type based on project key for creating issues
+
+;;;; Changes since 2.1.0:
+;; - Allow changing to unassigned user
+;; - Add new shortcut for quick assignment
+
+;;;; Changes since 2.0.0:
+;; - Change statusCategory to status value
+;; - Clean out some redundant code
+;; - Add ELPA tags in keywords
+
+;;;; Changes since 1.0.1:
+;; - Converted many calls to async
+;; - Removed minor annoyances (position resets etc.)
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'org)
+(require 'org-clock)
+(require 'cl-lib)
+(require 'url)
+(require 'ls-lisp)
+(require 'dash)
+(require 'jiralib)
+(require 'org-jira-sdk)
+
+(defconst org-jira-version "4.4.2"
+  "Current version of org-jira.el.")
+
+(defgroup org-jira nil
+  "Customization group for org-jira."
+  :tag "Org JIRA"
+  :group 'org)
+
+(defcustom org-jira-working-dir "~/.org-jira"
+  "Folder under which to store org-jira working files."
+  :group 'org-jira
+  :type 'directory)
+
+(defcustom org-jira-project-filename-alist nil
+  "Alist translating project keys to filenames.
+
+Each element has a structure:
+
+  (PROJECT-KEY . NEW-FILE-NAME)
+
+where both are strings.  NEW-FILE-NAME is relative to
+`org-jira-working-dir'."
+  :group 'org-jira
+  :type '(alist :key-type (string :tag "Project key")
+                :value-type (string :tag "File name for this project")))
+
+(defcustom org-jira-default-jql
+  "assignee = currentUser() and resolution = unresolved ORDER BY
+  priority DESC, created ASC"
+  "Default jql for querying your Jira tickets."
+  :group 'org-jira
+  :type 'string)
+
+(defcustom org-jira-ignore-comment-user-list
+  '("admin")
+  "Jira usernames that should have comments ignored."
+  :group 'org-jira
+  :type '(repeat (string :tag "Jira username:")))
+
+(defcustom org-jira-reverse-comment-order nil
+  "If non-nil, order comments from most recent to least recent."
+  :group 'org-jira
+  :type 'boolean)
+
+(defcustom org-jira-done-states
+  '("Closed" "Resolved" "Done")
+  "Jira states that should be considered as DONE for `org-mode'."
+  :group 'org-jira
+  :type '(repeat (string :tag "Jira state name:")))
+
+(defcustom org-jira-users
+  '(("Full Name" . "account-id"))
+  "A list of displayName and key pairs."
+  :group 'org-jira
+  :type 'list)
+
+(defcustom org-jira-progress-issue-flow
+  '(("To Do" . "In Progress")
+    ("In Progress" . "Done"))
+  "Quickly define a common issue flow."
+  :group 'org-jira
+  :type 'list)
+
+(defcustom org-jira-property-overrides (list)
+  "An assoc list of property tag overrides.
+
+The KEY . VAL pairs should both be strings.
+
+For instance, to change the :assignee: property in the org :PROPERTIES:
+block to :WorkedBy:, you can set this as such:
+
+  (setq org-jira-property-overrides (list (cons \"assignee\" \"WorkedBy\")))
+
+or simply:
+
+  (add-to-list (quote org-jira-property-overrides)
+               (cons (\"assignee\" \"WorkedBy\")))
+
+This will work for most any of the properties, with the
+exception of ones with unique functionality, such as:
+
+  - deadline
+  - summary
+  - description"
+  :group 'org-jira
+  :type 'list)
+
+(defcustom org-jira-serv-alist nil
+  "Association list to set information for each jira server.
+Each element of the alist is a jira server name.  The CAR of each
+element is a string, uniquely identifying the server.  The CDR of
+each element is a well-formed property list with an even number
+of elements, alternating keys and values, specifying parameters
+for the server.
+
+     (:property value :property value ... )
+
+When a property is given a value in org-jira-serv-alist, its
+setting overrides the value of the corresponding user
+variable (if any) during syncing.
+
+Most properties are optional, but some should always be set:
+
+  :url        soap url of the jira server.
+  :username   username to be used.
+  :host       hostname of the jira server (TODO: compute it from ~url~).
+
+All the other properties are optional.  They override the global
+variables.
+
+  :password   password to be used, will be prompted if missing."
+  :group 'org-jira
+  :type '(alist :value-type plist))
+
+(defcustom org-jira-use-status-as-todo nil
+  "Use the JIRA status as the TODO tag value."
+  :group 'org-jira)
+
+(defcustom org-jira-deadline-duedate-sync-p t
+  "Keep org deadline and jira duedate fields synced.
+You may wish to set this to nil if you track org deadlines in
+your buffer that you do not want to send back to your Jira
+instance."
+  :group 'org-jira
+  :type 'boolean)
+
+(defcustom org-jira-worklog-sync-p t
+  "Keep org clocks and jira worklog fields synced.
+You may wish to set this to nil if you track org clocks in
+your buffer that you do not want to send back to your Jira
+instance."
+  :group 'org-jira
+  :type 'boolean)
+
+(defcustom org-jira-download-dir "~/Downloads"
+  "Name of the default jira download library."
+  :group 'org-jira
+  :type 'string)
+
+(defcustom org-jira-download-ask-override t
+  "Ask before overriding tile."
+  :group 'org-jira
+  :type 'boolean)
+
+(defcustom org-jira-jira-status-to-org-keyword-alist nil
+  "Custom alist of jira status stored in car and `org-mode' keyword stored in cdr."
+  :group 'org-jira
+  :type '(alist :key-type string :value-type string))
+
+(defcustom org-jira-priority-to-org-priority-omit-default-priority nil
+  "Whether to omit insertion of priority when it matches the default.
+
+When set to t, will omit the insertion of the matched value from
+`org-jira-priority-to-org-priority-alist' when it matches the `org-default-priority'."
+  :group 'org-jira
+  :type 'boolean)
+
+(defcustom org-jira-priority-to-org-priority-alist nil
+  "Alist mapping jira priority keywords to `org-mode' priority cookies.
+
+A sample value might be
+  (list (cons \"High\" ?A)
+        (cons \"Medium\" ?B)
+        (cons \"Low\" ?C)).
+
+See `org-default-priority' for more info."
+  :group 'org-jira
+  :type '(alist :key-type string :value-type character))
+
+(defcustom org-jira-boards-default-limit 50
+  "Default limit for number of issues retrieved from agile boards."
+  :group 'org-jira
+  :type 'integer)
+
+;; FIXME: Issue with using this - issues are grouped under a headline incorrectly.
+(defcustom org-jira-custom-jqls
+  '(
+    (:jql " assignee = currentUser() and createdDate < '2019-01-01' order by created DESC "
+          :limit 100
+          :filename "last-years-work")
+    (:jql " assignee = currentUser() and createdDate >= '2019-01-01' order by created DESC "
+          :limit 100
+          :filename "this-years-work")
+    )
+  "A list of plists with :jql and :filename keys to run arbitrary user JQL."
+  :group 'org-jira
+  :type '(alist :value-type plist))
+
+(defcustom org-jira-download-comments t
+  "Set to nil if you don't want to update comments during issue rendering."
+  :group 'org-jira
+  :type 'boolean)
+
+(defcustom org-jira-update-issue-details-include-reporter t
+  "For Jira Cloud API we will get an error if `reporter' is sent with an update request."
+  :group 'org-jira
+  :type 'string)
+
+(defvar org-jira-serv nil
+  "Parameters of the currently selected blog.")
+
+(defvar org-jira-serv-name nil
+  "Name of the blog, to pick from `org-jira-serv-alist'.")
+
+(defvar org-jira-projects-list nil
+  "List of jira projects.")
+
+(defvar org-jira-current-project nil
+  "Currently selected (i.e., active project).")
+
+(defvar org-jira-issues-list nil
+  "List of jira issues under the current project.")
+
+(defvar org-jira-server-rpc-url nil
+  "Jira server soap URL.")
+
+(defvar org-jira-server-userid nil
+  "Jira server user id.")
+
+(defvar org-jira-proj-id nil
+  "Jira project ID.")
+
+(defvar org-jira-logged-in nil
+  "Flag whether user is logged-in or not.")
+
+(defvar org-jira-buffer-name "*org-jira-%s*"
+  "Name of the jira buffer.")
+
+(defvar org-jira-buffer-kill-prompt t
+  "Ask before killing buffer.")
+
+(make-variable-buffer-local 'org-jira-buffer-kill-prompt)
+
+(defvar org-jira-mode-hook nil
+  "Hook to run upon entry into mode.")
+
+(defvar org-jira-issue-id-history '()
+  "Prompt history for issue id.")
+
+(defvar org-jira-fixversion-id-history '()
+  "Prompt history for fixversion id.")
+
+(defvar org-jira-verbosity 'debug)
+
+(defun org-jira-log (s) (when (eq 'debug org-jira-verbosity) (message "%s" s)))
+
+(defmacro ensure-on-issue (&rest body)
+  "Make sure we are on an issue heading, before executing BODY."
+  (declare (debug t)
+           (indent 0))
+  `(save-excursion
+    (save-restriction
+      (widen)
+      (unless (looking-at "^\\*\\* ")
+        (search-backward-regexp "^\\*\\* " nil t)) ; go to top heading
+      (let ((org-jira-id (org-jira-id)))
+        (unless (and org-jira-id (string-match (jiralib-get-issue-regexp) (downcase org-jira-id)))
+          (error "Not on an issue region!")))
+      ,@body)))
+
+(defmacro org-jira-with-callback (&rest body)
+  "Simpler way to write the data BODY callbacks."
+  (declare (debug t)
+           (indent 0))
+  `(lambda (&rest request-response)
+     (declare (ignore cb-data))
+     (let ((cb-data (cl-getf request-response :data)))
+       ,@body)))
+
+(defmacro org-jira-freeze-ui (&rest body)
+  "Freeze the UI layout for the user as much as possible."
+  (declare (debug t)
+           (indent 0))
+  `(save-excursion
+     (save-restriction
+       (widen)
+       (org-save-outline-visibility t
+         (outline-show-all)
+         ,@body))))
+
+(defvar org-jira-proj-key-override nil
+  "String.  An override for the proj-key.  Set to nil to restore old behavior.")
+
+;; We want some hooking system to override default-jql + this.
+(defun org-jira--get-proj-key (issue-id)
+  "Get the proper proj-key.  Typically derived from ISSUE-ID."
+  (if org-jira-proj-key-override org-jira-proj-key-override
+    (replace-regexp-in-string "-.*" "" issue-id)))
+
+(defun org-jira--get-proj-key-from-issue (Issue)
+  "Get the proper proj-key from an ISSUE instance."
+  (oref Issue filename))
+
+;; TODO: Merge these 3 ensure macros (or, scrap all but ones that work on Issue)
+(defmacro ensure-on-issue-id (issue-id &rest body)
+  "Just do some work on ISSUE-ID, execute BODY."
+  (declare (debug t)
+           (indent 1))
+  (let ((issue-id-var (make-symbol "issue-id")))
+    `(let* ((,issue-id-var ,issue-id)
+            (proj-key (org-jira--get-proj-key ,issue-id-var))
+            (project-file (org-jira--get-project-file-name proj-key))
+            (project-buffer (or (find-buffer-visiting project-file)
+                                (find-file project-file))))
+       (with-current-buffer project-buffer
+         (org-jira-freeze-ui
+           (let ((p (org-find-entry-with-id ,issue-id-var)))
+             (unless p (error "Issue %s not found!" ,issue-id-var))
+             (goto-char p)
+             (org-narrow-to-subtree)
+             ,@body))))))
+
+(defmacro ensure-on-issue-id-with-filename (issue-id filename &rest body)
+  "Just do some work on ISSUE-ID, execute BODY."
+  (declare (debug t)
+           (indent 1))
+  (let ((issue-id-var (make-symbol "issue-id"))
+        (filename-var (make-symbol "filename")))
+    `(let* ((,issue-id-var ,issue-id)
+            (,filename-var ,filename)
+            (proj-key ,filename-var)
+            (project-file (org-jira--get-project-file-name proj-key))
+            (project-buffer (or (find-buffer-visiting project-file)
+                                (find-file project-file))))
+       (with-current-buffer project-buffer
+         (org-jira-freeze-ui
+           (let ((p (org-find-entry-with-id ,issue-id-var)))
+             (unless p (error "Issue %s not found!" ,issue-id-var))
+             (goto-char p)
+             (org-narrow-to-subtree)
+             ,@body))))))
+
+(defmacro ensure-on-issue-Issue (Issue &rest body)
+  "Just do some work on ISSUE, execute BODY."
+  (declare (debug t)
+           (indent 1))
+  (let ((Issue-var (make-symbol "Issue")))
+    `(let ((,Issue-var ,Issue))
+         (with-slots (issue-id) ,Issue-var
+           (let* ((proj-key (org-jira--get-proj-key-from-issue ,Issue-var))
+                  (project-file (org-jira--get-project-file-name proj-key))
+                  (project-buffer (or (find-buffer-visiting project-file)
+                                      (find-file project-file))))
+             (with-current-buffer project-buffer
+               (org-jira-freeze-ui
+                 (let ((p (org-find-entry-with-id issue-id)))
+                   (unless p (error "Issue %s not found!" issue-id))
+                   (goto-char p)
+                   (org-narrow-to-subtree)
+                   ,@body))))))))
+
+(defmacro ensure-on-todo (&rest body)
+  "Make sure we are on an todo heading, before executing BODY."
+  (declare (debug t)
+           (indent 0))
+  `(save-excursion
+     (save-restriction
+       (let ((continue t)
+             (on-todo nil))
+         (while continue
+           (when (org-get-todo-state)
+             (setq continue nil on-todo t))
+           (unless (and continue (org-up-heading-safe))
+             (setq continue nil)))
+         (if (not on-todo)
+             (error "TODO not found")
+           (org-narrow-to-subtree)
+           ,@body)))))
+
+(defmacro ensure-on-comment (&rest body)
+  "Make sure we are on a comment heading, before executing BODY."
+  (declare (debug t)
+           (indent 0))
+  `(save-excursion
+     (org-back-to-heading)
+     (forward-thing 'whitespace)
+     (unless (looking-at "Comment:")
+       (error "Not on a comment region!"))
+     (save-restriction
+       (org-narrow-to-subtree)
+       ,@body)))
+
+(defmacro ensure-on-worklog (&rest body)
+  "Make sure we are on a worklog heading, before executing BODY."
+  (declare (debug t)
+           (indent 0))
+  `(save-excursion
+     (org-back-to-heading)
+     (forward-thing 'whitespace)
+     (unless (looking-at "Worklog:")
+       (error "Not on a worklog region!"))
+     (save-restriction
+       (org-narrow-to-subtree)
+       ,@body)))
+
+(defun org-jira--ensure-working-dir ()
+  "Ensure that the org-jira-working-dir exists"
+  (unless (file-exists-p org-jira-working-dir)
+    (error (format "org-jira directory does not exist! Run (make-directory \"%s\")" org-jira-working-dir))
+    )
+  org-jira-working-dir
+  )
+
+(defvar org-jira-entry-mode-map
+  (let ((org-jira-map (make-sparse-keymap)))
+    (define-key org-jira-map (kbd "C-c pg") 'org-jira-get-projects)
+    (define-key org-jira-map (kbd "C-c bg") 'org-jira-get-boards)
+    (define-key org-jira-map (kbd "C-c iv") 'org-jira-get-issues-by-board)
+    (define-key org-jira-map (kbd "C-c ib") 'org-jira-browse-issue)
+    (define-key org-jira-map (kbd "C-c ig") 'org-jira-get-issues)
+    (define-key org-jira-map (kbd "C-c ij") 'org-jira-get-issues-from-custom-jql)
+    (define-key org-jira-map (kbd "C-c ih") 'org-jira-get-issues-headonly)
+    ;;(define-key org-jira-map (kbd "C-c if") 'org-jira-get-issues-from-filter-headonly)
+    ;;(define-key org-jira-map (kbd "C-c iF") 'org-jira-get-issues-from-filter)
+    (define-key org-jira-map (kbd "C-c il") 'org-jira-update-issue-labels)
+    (define-key org-jira-map (kbd "C-c iu") 'org-jira-update-issue)
+    (define-key org-jira-map (kbd "C-c iw") 'org-jira-progress-issue)
+    (define-key org-jira-map (kbd "C-c in") 'org-jira-progress-issue-next)
+    (define-key org-jira-map (kbd "C-c ia") 'org-jira-assign-issue)
+    ;(define-key org-jira-map (kbd "C-c isr") 'org-jira-set-issue-reporter)
+    (define-key org-jira-map (kbd "C-c ir") 'org-jira-refresh-issue)
+    (define-key org-jira-map (kbd "C-c iR") 'org-jira-refresh-issues-in-buffer)
+    (define-key org-jira-map (kbd "C-c ic") 'org-jira-create-issue)
+    (define-key org-jira-map (kbd "C-c ik") 'org-jira-copy-current-issue-key)
+    (define-key org-jira-map (kbd "C-c sc") 'org-jira-create-subtask)
+    (define-key org-jira-map (kbd "C-c sg") 'org-jira-get-subtasks)
+    (define-key org-jira-map (kbd "C-c cc") 'org-jira-add-comment)
+    (define-key org-jira-map (kbd "C-c cu") 'org-jira-update-comment)
+    (define-key org-jira-map (kbd "C-c wu") 'org-jira-update-worklogs-from-org-clocks)
+    (define-key org-jira-map (kbd "C-c tj") 'org-jira-todo-to-jira)
+    (define-key org-jira-map (kbd "C-c if") 'org-jira-get-issues-by-fixversion)
+    org-jira-map))
+
+;;;###autoload
+(define-minor-mode org-jira-mode
+  "Toggle org-jira mode.
+With no argument, the mode is toggled on/off.
+Non-nil argument turns mode on.
+Nil argument turns mode off.
+
+Commands:
+\\{org-jira-entry-mode-map}
+
+Entry to this mode calls the value of `org-jira-mode-hook'."
+
+  :init-value nil
+  :lighter " jira"
+  :group 'org-jira
+  :keymap org-jira-entry-mode-map
+
+  (if org-jira-mode
+      (progn
+        (set (make-local-variable 'org-element-use-cache) nil)
+        (run-mode-hooks 'org-jira-mode-hook))
+    (progn
+      (kill-local-variable 'org-element-use-cache))))
+
+(defun org-jira-maybe-activate-mode ()
+  "Having hooks can be an expensive operation, and they are invoked each
+time the mode is started - we should only ever re-activate the mode if
+it isn't already on."
+  (unless (bound-and-true-p org-jira-mode) (org-jira-mode t)))
+
+(defun org-jira-get-project-name (proj)
+  (org-jira-find-value proj 'key))
+
+(defun org-jira-find-value (l &rest keys)
+  (let* (key exists)
+    (while (and keys (listp l))
+      (setq key (car keys))
+      (setq exists nil)
+      (mapc (lambda (item)
+              (when (equal key (car item))
+                (setq exists t)))
+            (if (and (listp l)
+                     (listp (car l)))
+                l
+              nil))
+      (setq keys (cdr keys))
+      (if exists
+          (setq l (cdr (assoc key l)))
+        (setq l (or (cdr (assoc key l)) l))))
+    l))
+
+(defun org-jira--get-project-file-name (project-key)
+  "Translate PROJECT-KEY into filename."
+  (-if-let (translation (cdr (assoc project-key org-jira-project-filename-alist)))
+      (expand-file-name translation (org-jira--ensure-working-dir))
+    (expand-file-name (concat project-key ".org") (org-jira--ensure-working-dir))))
+
+(defun org-jira-get-project-lead (proj)
+  (org-jira-find-value proj 'lead 'name))
+
+;; This is mapped to accountId and not username, so we need nil not blank string.
+(defun org-jira-get-assignable-users (project-key)
+  "Get the list of assignable users for PROJECT-KEY, adding user set jira-users first."
+  (append
+   '(("Unassigned" . nil))
+   org-jira-users
+   (mapcar (lambda (user)
+             (cons (org-jira-decode (cdr (assoc 'displayName user)))
+                   (org-jira-decode (cdr (assoc 'accountId user)))))
+           (jiralib-get-users project-key))))
+
+(defun org-jira-get-reporter-candidates (project-key)
+  "Get the list of assignable users for PROJECT-KEY, adding user set jira-users first."
+  (append
+   org-jira-users
+   (mapcar (lambda (user)
+             (cons (org-jira-decode (cdr (assoc 'displayName user)))
+                   (org-jira-decode (cdr (assoc 'accountId user)))))
+           (jiralib-get-users project-key))))
+
+(defun org-jira-entry-put (pom property value)
+  "Similar to org-jira-entry-put, but with an optional alist of overrides.
+
+At point-or-marker POM, set PROPERTY to VALUE.
+
+Look at customizing org-jira-property-overrides if you want
+to change the property names this sets."
+  (unless (stringp property)
+    (setq property (symbol-name property)))
+  (let ((property (or (assoc-default property org-jira-property-overrides)
+                      property)))
+    (org-entry-put pom property (org-jira-decode value))))
+
+(defun org-jira-kill-line ()
+  "Kill the line, without 'kill-line' side-effects of altering kill ring."
+  (interactive)
+  (delete-region (point) (line-end-position)))
+
+;; Appropriated from org.el
+(defun org-jira-org-kill-line (&optional _arg)
+  "Kill line, to tags or end of line."
+  (cond
+   ((or (not org-special-ctrl-k)
+        (bolp)
+        (not (org-at-heading-p)))
+    (when (and (get-char-property (min (point-max) (point-at-eol)) 'invisible)
+               org-ctrl-k-protect-subtree
+               (or (eq org-ctrl-k-protect-subtree 'error)
+                   (not (y-or-n-p "Kill hidden subtree along with headline? "))))
+      (user-error "C-k aborted as it would kill a hidden subtree"))
+    (call-interactively
+     (if (bound-and-true-p visual-line-mode) 'kill-visual-line 'org-jira-kill-line)))
+   ((looking-at ".*?\\S-\\([ \t]+\\(:[[:alnum:]_@#%:]+:\\)\\)[ \t]*$")
+    (delete-region (point) (match-beginning 1))
+    (org-set-tags nil t))
+   (t (delete-region (point) (point-at-eol)))))
+
+;;;###autoload
+(defun org-jira-get-projects ()
+  "Get list of projects."
+  (interactive)
+  (let ((projects-file (expand-file-name "projects-list.org" (org-jira--ensure-working-dir))))
+    (or (find-buffer-visiting projects-file)
+        (find-file projects-file))
+    (org-jira-maybe-activate-mode)
+    (save-excursion
+      (let* ((oj-projs (jiralib-get-projects)))
+        (mapc (lambda (proj)
+                (let* ((proj-key (org-jira-find-value proj 'key))
+                       (proj-headline (format "Project: [[file:%s.org][%s]]" proj-key proj-key)))
+                  (save-restriction
+                    (widen)
+                    (goto-char (point-min))
+                    (outline-show-all)
+                    (setq p (org-find-exact-headline-in-buffer proj-headline))
+                    (if (and p (>= p (point-min))
+                             (<= p (point-max)))
+                        (progn
+                          (goto-char p)
+                          (org-narrow-to-subtree)
+                          (end-of-line))
+                      (goto-char (point-max))
+                      (unless (looking-at "^")
+                        (insert "\n"))
+                      (insert "* ")
+                      (org-jira-insert proj-headline)
+                      (org-narrow-to-subtree))
+                    (org-jira-entry-put (point) "name" (org-jira-get-project-name proj))
+                    (org-jira-entry-put (point) "key" (org-jira-find-value proj 'key))
+                    (org-jira-entry-put (point) "lead" (org-jira-get-project-lead proj))
+                    (org-jira-entry-put (point) "ID" (org-jira-find-value proj 'id))
+                    (org-jira-entry-put (point) "url" (format "%s/browse/%s" (replace-regexp-in-string "/*$" "" jiralib-url) (org-jira-find-value proj 'key))))))
+              oj-projs)))))
+
+(defun org-jira-get-issue-components (issue)
+  "Return the components the ISSUE belongs to."
+  (mapconcat
+   (lambda (comp)
+     (org-jira-find-value comp 'name))
+   (org-jira-find-value issue 'fields 'components) ", "))
+
+(defun org-jira-get-issue-labels (issue)
+  "Return the labels the ISSUE belongs to."
+  (org-jira-find-value issue 'fields 'labels))
+
+(defun org-jira-decode (data)
+  "Decode text DATA.
+
+It must receive a coercion to string, as not every time will it
+be populated."
+  (decode-coding-string
+   (cl-coerce data 'string) jiralib-coding-system))
+
+(defun org-jira-insert (&rest args)
+  "Set coding to text provide by `ARGS' when insert in buffer."
+  (insert (org-jira-decode (apply #'concat args))))
+
+(defun org-jira-transform-time-format (jira-time-str)
+  "Convert JIRA-TIME-STR to format \"%Y-%m-%d %T\".
+
+Example: \"2012-01-09T08:59:15.000Z\" becomes \"2012-01-09
+16:59:15\", with the current timezone being +0800."
+  (condition-case ()
+      (format-time-string
+       "%Y-%m-%d %T"
+       (apply
+        'encode-time
+        (parse-time-string (replace-regexp-in-string "T\\|\\.000" " " jira-time-str))))
+    (error jira-time-str)))
+
+(defun org-jira--fix-encode-time-args (arg)
+  "Fix ARG for 3 nil values at the head."
+  (cl-loop
+   for n from 0 to 2 by 1 do
+   (when (not (nth n arg))
+     (setcar (nthcdr n arg) 0)))
+  arg)
+
+(defun org-jira-time-format-to-jira (org-time-str)
+  "Convert ORG-TIME-STR back to jira time format."
+  (condition-case ()
+      (format-time-string
+       "%Y-%m-%dT%T.000Z"
+       (apply 'encode-time
+              (org-jira--fix-encode-time-args (parse-time-string org-time-str)))
+       t)
+    (error org-time-str)))
+
+(defun org-jira-get-comment-val (key comment)
+  "Return the value associated with KEY of COMMENT."
+  (org-jira-get-issue-val key comment))
+
+(defun org-jira-time-stamp-to-org-clock (time-stamp)
+  "Convert TIME-STAMP into org-clock format."
+  (format-time-string "%Y-%m-%d %a %H:%M" time-stamp))
+
+(defun org-jira-date-to-org-clock (date)
+  "Convert DATE into a time stamp and then into org-clock format.
+Expects a date in format such as: 2017-02-26T00:08:00.000-0500."
+  (org-jira-time-stamp-to-org-clock (date-to-time date)))
+
+(defun org-jira-worklogs-to-org-clocks (worklogs)
+  "Get a list of WORKLOGS and convert to org-clocks."
+  (mapcar
+   (lambda (worklog)
+     (let ((wl-start (cdr (assoc 'started worklog)))
+           (wl-time (cdr (assoc 'timeSpentSeconds worklog)))
+           (wl-end))
+       (setq wl-start (org-jira-date-to-org-clock wl-start))
+       (setq wl-end (org-jira-time-stamp-to-org-clock (time-add (date-to-time wl-start) wl-time)))
+       (list
+        wl-start
+        wl-end
+        (cdr (assoc 'comment worklog))
+        (cdr (assoc 'id worklog))
+        )
+       ))
+   worklogs)
+  )
+
+(defun org-jira-format-clock (clock-entry)
+  "Format a CLOCK-ENTRY given the (list start end).
+This format is typically generated from org-jira-worklogs-to-org-clocks call."
+  (format "CLOCK: [%s]--[%s]" (car clock-entry) (cadr clock-entry)))
+
+(defun org-jira-insert-clock (clock-entry)
+  "Insert a CLOCK-ENTRY given the (list start end).
+This format is typically generated from org-jira-worklogs-to-org-clocks call."
+  (insert (org-jira-format-clock clock-entry))
+  (org-beginning-of-line)
+  (org-ctrl-c-ctrl-c) ;; @TODO Maybe not call directly?  does it matter? - used to resync the clock estimate
+  (org-end-of-line)
+  (insert "\n")
+  (insert (format "  :id: %s\n" (cadddr clock-entry)))
+  (when (caddr clock-entry) (insert (replace-regexp-in-string "^\\*" "-" (format "  %s\n" (org-jira-decode (caddr clock-entry)))))) ;; No comment is nil, so don't print it
+  )
+
+(defun org-jira-logbook-reset (issue-id filename &optional clocks)
+  "Find logbook for ISSUE-ID in FILENAME, delete it.
+Re-create it with CLOCKS.  This is used for worklogs."
+  (interactive)
+  (let ((existing-logbook-p nil))
+    ;; See if the LOGBOOK already exists or not.
+    (ensure-on-issue-id-with-filename issue-id filename
+      (let ((drawer-name (or (org-clock-drawer-name) "LOGBOOK")))
+        (when (search-forward (format ":%s:" drawer-name) nil 1 1)
+          (setq existing-logbook-p t))))
+    (ensure-on-issue-id-with-filename issue-id filename
+      (let ((drawer-name (or (org-clock-drawer-name) "LOGBOOK")))
+        (if existing-logbook-p
+            (progn ;; If we had a logbook, drop it and re-create in a bit.
+              (search-forward (format ":%s:" drawer-name) nil 1 1)
+              (org-beginning-of-line)
+              (delete-region (point) (search-forward ":END:" nil 1 1))
+              )
+          (progn ;; Otherwise, create a new one at the end of properties list
+            (search-forward ":END:" nil 1 1)
+            (forward-line)))
+        (org-insert-drawer nil (format "%s" drawer-name)) ;; Doc says non-nil, but this requires nil
+        (mapc #'org-jira-insert-clock clocks)
+        ;; Clean up leftover newlines (we left 2 behind)
+        (dotimes (n 2)
+          (search-forward-regexp "^$" nil 1 1)
+          (delete-region (point) (min (point-max) (1+ (point)))))))))
+
+(defun org-jira-get-worklog-val (key WORKLOG)
+  "Return the value associated with KEY of WORKLOG."
+  (org-jira-get-comment-val key WORKLOG))
+
+(defun org-jira-get-issue-val (key issue)
+  "Return the value associated with key KEY of issue ISSUE."
+  (let ((tmp  (or (org-jira-find-value issue 'fields key 'key) ""))) ;; For project, we need a key, not the name...
+    (unless (stringp tmp)
+      (setq tmp (org-jira-find-value issue key)))
+    (unless (stringp tmp)
+      (setq tmp (org-jira-find-value issue 'fields key 'displayName)))
+    (unless (stringp tmp)
+      (setq tmp ""))
+    (cond ((eq key 'components)
+           (org-jira-get-issue-components issue))
+          ((eq key 'labels)
+           (org-jira-get-issue-labels issue))
+          ((member key '(created updated startDate))
+           (org-jira-transform-time-format tmp))
+          ((eq key 'status)
+           (if jiralib-use-restapi
+               (org-jira-find-value issue 'fields 'status 'name)
+             (org-jira-find-value (jiralib-get-statuses) tmp)))
+          ((eq key 'resolution)
+           (if jiralib-use-restapi
+               tmp
+             (if (string= tmp "")
+                 ""
+               (org-jira-find-value (jiralib-get-resolutions) tmp))))
+          ((eq key 'type)
+           (if jiralib-use-restapi
+               (org-jira-find-value issue 'fields 'issuetype 'name)
+             (org-jira-find-value (jiralib-get-issue-types) tmp)))
+          ((eq key 'priority)
+           (if jiralib-use-restapi
+               (org-jira-find-value issue 'fields 'priority 'name)
+             (org-jira-find-value (jiralib-get-priorities) tmp)))
+          ((eq key 'description)
+           (org-trim tmp))
+          (t
+           tmp))))
+
+(defvar org-jira-jql-history nil)
+
+;;;###autoload
+(defun org-jira-get-issue-list (&optional callback)
+  "Get list of issues, using jql (jira query language), invoke CALLBACK after.
+
+Default is unresolved issues assigned to current login user; with
+a prefix argument you are given the chance to enter your own
+jql."
+  (org-jira-log (format "I was called, was it with a callback? %s" (if callback "yes" "no")))
+  (let ((jql org-jira-default-jql))
+    (when current-prefix-arg
+      (setq jql (read-string "Jql: "
+                             (if org-jira-jql-history
+                                 (car org-jira-jql-history)
+                               "assignee = currentUser() and resolution = unresolved")
+                             'org-jira-jql-history
+                             "assignee = currentUser() and resolution = unresolved")))
+    (list (jiralib-do-jql-search jql nil callback))))
+
+(defun org-jira-get-issue-by-id (id)
+  "Get an issue by its ID."
+  (push id org-jira-issue-id-history)
+  (let ((jql (format "id = %s" id)))
+    (jiralib-do-jql-search jql)))
+
+(defun org-jira-get-issue-by-fixversion (fixversion-id)
+  "Get an issue by its FIXVERSION-ID."
+  (push fixversion-id org-jira-fixversion-id-history)
+  (let ((jql (format "fixVersion = \"%s\""  fixversion-id)))
+    (jiralib-do-jql-search jql)))
+
+;;;###autoload
+(defun org-jira-get-summary ()
+  "Get issue summary from point and place next to issue id from jira"
+  (interactive)
+  (let ((jira-id (thing-at-point 'symbol)))
+    (unless jira-id (error "ORG_JIRA_ERROR: JIRA-ID missing in org-jira-get-summary!"))
+    (forward-symbol 1)
+    (insert (format " - %s"
+                    (cdr (assoc 'summary (assoc 'fields (car (org-jira-get-issue-by-id jira-id)))))))))
+
+;;;###autoload
+(defun org-jira-get-summary-url ()
+  "Get issue summary from point and place next to issue id from jira, and make issue id a link"
+  (interactive)
+  (let ((jira-id (thing-at-point 'symbol)))
+    (insert (format "[[%s][%s]] - %s"
+                    (concatenate 'string jiralib-url "browse/" jira-id) jira-id
+                    (cdr (assoc 'summary (car (org-jira-get-issue-by-id jira-id))))))))
+
+;;;###autoload
+(defun org-jira-get-issues-headonly (issues)
+  "Get list of ISSUES, head only.
+
+The default behavior is to return issues assigned to you and unresolved.
+
+With a prefix argument, allow you to customize the jql.  See
+`org-jira-get-issue-list'."
+
+  (interactive
+   (org-jira-get-issue-list))
+
+  (let* ((issues-file (expand-file-name "issues-headonly.org" (org-jira--ensure-working-dir)))
+         (issues-headonly-buffer (or (find-buffer-visiting issues-file)
+                                     (find-file issues-file))))
+    (with-current-buffer issues-headonly-buffer
+      (widen)
+      (delete-region (point-min) (point-max))
+
+      (mapc (lambda (issue)
+              (let ((issue-id (org-jira-get-issue-key issue))
+                    (issue-summary (org-jira-get-issue-summary issue)))
+                (org-jira-insert (format "- [jira:%s] %s\n" issue-id issue-summary))))
+            issues))
+    (switch-to-buffer issues-headonly-buffer)))
+
+;;;###autoload
+(defun org-jira-get-issue (id)
+  "Get a JIRA issue, allowing you to enter the issue-id first."
+  (interactive (list (read-string "Issue ID: " "" 'org-jira-issue-id-history)))
+  (org-jira-get-issues (org-jira-get-issue-by-id id))
+  (let ((issue-pos (org-find-entry-with-id id)))
+    (when issue-pos
+      (goto-char issue-pos)
+      (recenter 0))))
+;;;###autoload
+(defun org-jira-get-issues-by-fixversion (fixversion)
+  "Get list of issues by FIXVERSION."
+  (interactive (list (read-string "Fixversion ID: " ""
+                                  'org-jira-fixversion-id-history)))
+  (org-jira-get-issues (org-jira-get-issue-by-fixversion fixversion)))
+
+;;;###autoload
+(defun org-jira-get-issue-project (issue)
+  (org-jira-find-value issue 'fields 'project 'key))
+
+(defun org-jira-get-issue-key (issue)
+  (org-jira-find-value issue 'key))
+
+(defun org-jira-get-issue-summary (issue)
+  (org-jira-find-value issue 'fields 'summary))
+
+(defvar org-jira-get-issue-list-callback
+  (cl-function
+   (lambda (&key data &allow-other-keys)
+     "Callback for async, DATA is the response from the request call.
+
+Will send a list of org-jira-sdk-issue objects to the list printer."
+     (org-jira-log "Received data for org-jira-get-issue-list-callback.")
+     (--> data
+          (org-jira-sdk-path it '(issues))
+          (append it nil)               ; convert the conses into a proper list.
+          org-jira-sdk-create-issues-from-data-list
+          org-jira-get-issues))))
+
+(defvar org-jira-get-sprint-list-callback
+  (cl-function
+   (lambda (&key data &allow-other-keys)
+     "Callback for async, DATA is the response from the request call.
+
+Will send a list of org-jira-sdk-issue objects to the list printer."
+     (org-jira-log "Received data for org-jira-get-sprint-list-callback.")
+     (--> data
+          (org-jira-sdk-path it '(sprint))
+          (append it nil)               ; convert the conses into a proper list.
+          org-jira-sdk-create-issues-from-data-list
+          org-jira-get-issues))))
+
+
+;;;###autoload
+(defun org-jira-get-issues (issues)
+  "Get list of ISSUES into an org buffer.
+
+Default is get unfinished issues assigned to you, but you can
+customize jql with a prefix argument.
+See`org-jira-get-issue-list'"
+  ;; If the user doesn't provide a default, async call to build an issue list
+  ;; from the JQL style query
+  (interactive
+   (org-jira-get-issue-list org-jira-get-issue-list-callback))
+  (org-jira-log "Fetching issues...")
+  (when (> (length issues) 0)
+    (org-jira--render-issues-from-issue-list issues)))
+
+(defvar org-jira-original-default-jql nil)
+
+(defun org-jira-get-issues-from-custom-jql-callback (filename list)
+  "Generate a function that we can iterate over FILENAME and LIST with when callback finishes."
+  (cl-function
+   (lambda (&key data &allow-other-keys)
+     "Callback for async, DATA is the response from the request call.
+
+Will send a list of org-jira-sdk-issue objects to the list printer."
+     (org-jira-log "Received data for org-jira-get-issues-from-custom-jql-callback.")
+     (--> data
+          (org-jira-sdk-path it '(issues))
+          (append it nil)     ; convert the conses into a proper list.
+          (org-jira-sdk-create-issues-from-data-list-with-filename filename it)
+          org-jira-get-issues)
+     (setq org-jira-proj-key-override nil)
+     (let ((next (rest list)))
+       (when next
+         (org-jira-get-issues-from-custom-jql next))))))
+
+;;;###autoload
+(defun org-jira-get-issues-from-custom-jql (&optional jql-list)
+  "Get JQL-LIST list of issues from a custom JQL and PROJ-KEY.
+
+The PROJ-KEY will act as the file name, while the JQL will be any
+valid JQL to populate a file to store PROJ-KEY results in.
+
+Please note that this is *not* concurrent or race condition
+proof.  If you try to run multiple calls to this function, it
+will mangle things badly, as they rely on globals DEFAULT-JQL and
+ORG-JIRA-PROJ-KEY-OVERRIDE being set before and after running."
+  (interactive)
+  (let* ((jl (or jql-list org-jira-custom-jqls))
+         (uno (car jl))
+         (filename (cl-getf uno :filename))
+         (limit (cl-getf uno :limit))
+         (jql (replace-regexp-in-string "[\n]" " " (cl-getf uno :jql))))
+    (setq org-jira-proj-key-override filename)
+    (jiralib-do-jql-search jql limit (org-jira-get-issues-from-custom-jql-callback filename jl))))
+
+(defun org-jira--get-project-buffer (Issue)
+  (let* ((proj-key (org-jira--get-proj-key-from-issue Issue))
+         (project-file (org-jira--get-project-file-name proj-key))
+         (project-buffer (find-file-noselect project-file)))
+    project-buffer))
+
+(defun org-jira--is-top-headline? (proj-key)
+  "For PROJ-KEY, check if it is a top headline or not."
+  (let ((elem (org-element-at-point)))
+    (and (eq 'headline (car elem))
+         (equal (format "%s-Tickets" proj-key)
+                (org-element-property :title elem))
+         (= 1 (org-element-property :level elem)))))
+
+(defun org-jira--maybe-render-top-heading (proj-key)
+  "Ensure that there is a headline for PROJ-KEY at the top of the file."
+  (goto-char (point-min))
+  (let ((top-heading (format ".*%s-Tickets" proj-key))
+        (th-found? nil))
+    (while (and (not (eobp))
+                (not th-found?))
+      (beginning-of-line)
+      (when (org-jira--is-top-headline? proj-key) (setq th-found? t))
+      (re-search-forward top-heading nil 1 1))
+    (beginning-of-line)
+    (unless (looking-at top-heading)
+      (insert (format "\n* %s-Tickets\n" proj-key)))))
+
+(defun org-jira--render-issue (Issue)
+  "Render single ISSUE."
+;;  (org-jira-log "Rendering issue from issue list")
+;;  (org-jira-log (org-jira-sdk-dump Issue))
+  (with-slots (filename proj-key issue-id summary status priority headline id) Issue
+    (let (p)
+      (with-current-buffer (org-jira--get-project-buffer Issue)
+        (org-jira-freeze-ui
+          (org-jira-maybe-activate-mode)
+          (org-jira--maybe-render-top-heading proj-key)
+          (setq p (org-find-entry-with-id issue-id))
+          (save-restriction
+            (if (and p (>= p (point-min))
+                     (<= p (point-max)))
+                (progn
+                  (goto-char p)
+                  (forward-thing 'whitespace)
+                  (org-jira-kill-line))
+              (goto-char (point-max))
+              (unless (looking-at "^")
+                (insert "\n"))
+              (insert "** "))
+            (org-jira-insert
+             (concat (org-jira-get-org-keyword-from-status status)
+                     " "
+                     (org-jira-get-org-priority-cookie-from-issue priority)
+                     headline))
+            (save-excursion
+              (unless (search-forward "\n" (point-max) 1)
+                (insert "\n")))
+            (org-narrow-to-subtree)
+            (save-excursion
+              (org-back-to-heading t)
+              (org-set-tags-to (replace-regexp-in-string "-" "_" issue-id)))
+            (org-jira-entry-put (point) "assignee" (or (slot-value Issue 'assignee) "Unassigned"))
+            (mapc (lambda (entry)
+                    (let ((val (slot-value Issue entry)))
+                      (when (and val (not (string= val "")))
+                        (org-jira-entry-put (point) (symbol-name entry) val))))
+                  '(filename reporter type type-id priority labels resolution status components created updated sprint))
+
+            (org-jira-entry-put (point) "ID" issue-id)
+            (org-jira-entry-put (point) "CUSTOM_ID" issue-id)
+
+            ;; Insert the duedate as a deadline if it exists
+            (when org-jira-deadline-duedate-sync-p
+              (let ((duedate (oref Issue duedate)))
+                (when (> (length duedate) 0)
+                  (org-deadline nil duedate))))
+
+            (mapc
+             (lambda (heading-entry)
+               (ensure-on-issue-id-with-filename issue-id filename
+                                                 (let* ((entry-heading
+                                                         (concat (symbol-name heading-entry)
+                                                                 (format ": [[%s][%s]]"
+                                                                         (concat jiralib-url "/browse/" issue-id) issue-id))))
+                                                   (setq p (org-find-exact-headline-in-buffer entry-heading))
+                                                   (if (and p (>= p (point-min))
+                                                            (<= p (point-max)))
+                                                       (progn
+                                                         (goto-char p)
+                                                         (org-narrow-to-subtree)
+                                                         (goto-char (point-min))
+                                                         (forward-line 1)
+                                                         (delete-region (point) (point-max)))
+                                                     (if (org-goto-first-child)
+                                                         (org-insert-heading)
+                                                       (goto-char (point-max))
+                                                       (open-line 1)
+                                                       (org-insert-subheading t))
+                                                     (org-jira-insert entry-heading "\n"))
+
+                                                   ;;  Insert 2 spaces of indentation so Jira markup won't cause org-markup
+                                                   (org-jira-insert
+                                                    (replace-regexp-in-string
+                                                     "^" "  "
+                                                     (format "%s" (slot-value Issue heading-entry)))))))
+             '(description))
+
+            (when org-jira-download-comments
+              (org-jira-update-comments-for-issue Issue)
+
+              ;; FIXME: Re-enable when attachments are not erroring.
+              ;;(org-jira-update-attachments-for-current-issue)
+              )
+
+            ;; only sync worklog clocks when the user sets it to be so.
+            (when org-jira-worklog-sync-p
+              (org-jira-update-worklogs-for-issue issue-id filename))))))))
+
+(defun org-jira--render-issues-from-issue-list (Issues)
+  "Add the issues from ISSUES list into the org file(s).
+
+ISSUES is a list of `org-jira-sdk-issue' records."
+  ;; FIXME: Some type of loading error - the first async callback does not know about
+  ;; the issues existing as a class, so we may need to instantiate here if we have none.
+  (when (eq 0 (->> Issues (cl-remove-if-not #'org-jira-sdk-isa-issue?) length))
+    (setq Issues (org-jira-sdk-create-issues-from-data-list Issues)))
+
+  ;; First off, we never ever want to run on non-issues, so check our types early.
+  (setq Issues (cl-remove-if-not #'org-jira-sdk-isa-issue? Issues))
+  (org-jira-log (format "About to render %d issues." (length Issues)))
+
+  ;; If we have any left, we map over them.
+  (mapc 'org-jira--render-issue Issues)
+
+  ;; Prior text: "Oh, are you the culprit?" - Not sure if this caused an issue at some point.
+  ;; We want to ensure we fix broken org narrowing though, by doing org-show-all and then org-cycle.
+  (switch-to-buffer (org-jira--get-project-buffer (-last-item Issues)))
+  (org-show-all)
+  (org-cycle))
+
+;;;###autoload
+(defun org-jira-update-comment ()
+  "Update a comment for the current issue."
+  (interactive)
+  (let* ((issue-id (org-jira-get-from-org 'issue 'key)) ; Really the key
+         (filename (org-jira-filename))
+         (comment-id (org-jira-get-from-org 'comment 'id))
+         (comment (replace-regexp-in-string "^  " "" (org-jira-get-comment-body comment-id))))
+    (lexical-let ((issue-id issue-id)
+                  (filename filename))
+      (let ((callback-edit
+             (cl-function
+              (lambda (&key _data &allow-other-keys)
+                (ensure-on-issue-id-with-filename
+                    issue-id filename
+                    (org-jira-update-comments-for-current-issue)))))
+            (callback-add
+             (cl-function
+              (lambda (&key _data &allow-other-keys)
+                (ensure-on-issue-id-with-filename
+                    issue-id filename
+                    ;; @TODO :optim: Has to be a better way to do this
+                    ;; than delete region (like update the unmarked
+                    ;; one)
+                    (org-jira-delete-current-comment)
+                    (org-jira-update-comments-for-current-issue))))))
+        (if comment-id
+            (jiralib-edit-comment issue-id comment-id comment callback-edit)
+          (jiralib-add-comment issue-id comment callback-add))))))
+
+(defun org-jira-add-comment (issue-id filename comment)
+  "For ISSUE-ID in FILENAME, add a new COMMENT string to the issue region."
+  (interactive
+   (let* ((issue-id (org-jira-get-from-org 'issue 'id))
+          (filename (org-jira-filename))
+          (comment (read-string (format  "Comment (%s): " issue-id))))
+     (list issue-id filename comment)))
+  (lexical-let ((issue-id issue-id)
+                (filename filename))
+    (ensure-on-issue-id-with-filename issue-id filename
+      (goto-char (point-max))
+      (jiralib-add-comment
+       issue-id comment
+       (cl-function
+        (lambda (&key _data &allow-other-keys)
+          (ensure-on-issue-id-with-filename issue-id filename
+            (org-jira-update-comments-for-current-issue))))))))
+
+(defun org-jira-org-clock-to-date (org-time)
+  "Convert ORG-TIME formatted date into a plain date string."
+  (format-time-string
+   "%Y-%m-%dT%H:%M:%S.000%z"
+   (date-to-time org-time)))
+
+(defun org-jira-worklog-time-from-org-time (org-time)
+  "Take in an ORG-TIME and convert it into the portions of a worklog time.
+Expects input in format such as: [2017-04-05 Wed 01:00]--[2017-04-05 Wed 01:46] =>  0:46"
+  (let ((start (replace-regexp-in-string "^\\[\\(.*?\\)\\].*" "\\1" org-time))
+        (end (replace-regexp-in-string ".*--\\[\\(.*?\\)\\].*" "\\1" org-time)))
+    `((started . ,(org-jira-org-clock-to-date start))
+      (time-spent-seconds . ,(time-to-seconds
+                              (time-subtract
+                               (date-to-time end)
+                               (date-to-time start)))))))
+
+(defun org-jira-org-clock-to-jira-worklog (org-time clock-content)
+  "Given ORG-TIME and CLOCK-CONTENT, format a jira worklog entry."
+  (let ((lines (split-string clock-content "\n"))
+        worklog-id)
+    ;; See if we look like we have an id
+    (when (string-match ":id:" (first lines))
+      (setq worklog-id
+            (replace-regexp-in-string "^.*:id: \\([0-9]*\\)$" "\\1" (first lines)))
+      (when (> (string-to-number worklog-id) 0) ;; pop off the first id line if we found it valid
+        (setq lines (cdr lines))))
+    (setq lines (reverse (cdr (reverse lines)))) ;; drop last line
+    (let ((comment (org-trim (mapconcat 'identity lines "\n")))
+          (worklog-time (org-jira-worklog-time-from-org-time org-time)))
+      `((worklog-id . ,worklog-id)
+        (comment . ,comment)
+        (started . ,(cdr (assoc 'started worklog-time)))
+        (time-spent-seconds . ,(cdr (assoc 'time-spent-seconds worklog-time)))
+        ))))
+
+(defun org-jira-worklog-to-hashtable (issue-id)
+  "Given ISSUE-ID, return a hashtable of worklog-id -> jira worklog."
+  (let ((worklog-hashtable (make-hash-table :test 'equal)))
+    (mapc
+     (lambda (worklog)
+       (let ((worklog-id (cdr (assoc 'id worklog))))
+         (puthash worklog-id worklog worklog-hashtable)))
+     (jiralib-worklog-import--filter-apply
+      (org-jira-find-value
+       (jiralib-get-worklogs
+        issue-id)
+       'worklogs)))
+    worklog-hashtable))
+
+;;;###autoload
+(defun org-jira-update-worklogs-from-org-clocks ()
+  "Update or add a worklog based on the org clocks."
+  (interactive)
+  (let* ((issue-id (org-jira-get-from-org 'issue 'key))
+         (filename (org-jira-filename))
+         ;; Fetch all workflogs for this issue
+         (jira-worklogs-ht (org-jira-worklog-to-hashtable issue-id)))
+    (org-jira-log (format "About to sync worklog for issue: %s in file: %s"
+                  issue-id filename))
+    (ensure-on-issue-id-with-filename issue-id filename
+      (search-forward (format ":%s:" (or (org-clock-drawer-name) "LOGBOOK"))  nil 1 1)
+      (org-beginning-of-line)
+      ;; (org-cycle 1)
+      (while (search-forward "CLOCK: " nil 1 1)
+        (let ((org-time (buffer-substring-no-properties (point) (point-at-eol))))
+          (forward-line)
+          ;; See where the stuff ends (what point)
+          (let (next-clock-point)
+            (save-excursion
+              (search-forward-regexp "\\(CLOCK\\|:END\\):" nil 1 1)
+              (setq next-clock-point (point)))
+            (let ((clock-content
+                   (buffer-substring-no-properties (point) next-clock-point)))
+              ;; Update via jiralib call
+              (let* ((worklog (org-jira-org-clock-to-jira-worklog org-time clock-content))
+                     (comment-text (cdr (assoc 'comment worklog)))
+                     (comment-text (if (string= (org-trim comment-text) "") nil comment-text)))
+                (if (cdr (assoc 'worklog-id worklog))
+                    ;; If there is a worklog in jira for this ID, check if the worklog has changed.
+                    ;; If it has changed, update the worklog.
+                    ;; If it has not changed, skip.
+                    (let ((jira-worklog (gethash (cdr (assoc 'worklog-id worklog)) jira-worklogs-ht)))
+                      (when (and jira-worklog
+                                 ;; Check if the entries are differing lengths.
+                                 (or (not (= (cdr (assoc 'timeSpentSeconds jira-worklog))
+                                         (cdr (assoc 'time-spent-seconds worklog))))
+                                 ;; Check if the entries start at different times.
+                                     (not (string= (cdr (assoc 'started jira-worklog))
+                                               (cdr (assoc 'started worklog))))))
+                        (jiralib-update-worklog
+                         issue-id
+                         (cdr (assoc 'worklog-id worklog))
+                         (cdr (assoc 'started worklog))
+                         (cdr (assoc 'time-spent-seconds worklog))
+                         comment-text
+                         nil))) ; no callback - synchronous
+                  ;; else
+                  (jiralib-add-worklog
+                   issue-id
+                   (cdr (assoc 'started worklog))
+                   (cdr (assoc 'time-spent-seconds worklog))
+                   comment-text
+                   nil) ; no callback - synchronous
+                  )
+                )))))
+      (org-jira-log (format "Updating worklog from org-jira-update-worklogs-from-org-clocks call"))
+      (org-jira-update-worklogs-for-issue issue-id filename)
+      )))
+
+(defun org-jira-update-worklog ()
+  "Update a worklog for the current issue."
+  (interactive)
+  (error "Deprecated, use org-jira-update-worklogs-from-org-clocks instead!")
+  (let* ((issue-id (org-jira-get-from-org 'issue 'key))
+         (worklog-id (org-jira-get-from-org 'worklog 'id))
+         (timeSpent (org-jira-get-from-org 'worklog 'timeSpent))
+         (timeSpent (if timeSpent
+                        timeSpent
+                      (read-string "Input the time you spent (such as 3w 1d 2h): ")))
+         (timeSpent (replace-regexp-in-string " \\(\\sw\\)\\sw*\\(,\\|$\\)" "\\1" timeSpent))
+         (startDate (org-jira-get-from-org 'worklog 'startDate))
+         (startDate (if startDate
+                        startDate
+                      (org-read-date nil nil nil "Input when did you start")))
+         (startDate (org-jira-time-format-to-jira startDate))
+         (comment (replace-regexp-in-string "^  " "" (org-jira-get-worklog-comment worklog-id)))
+         (worklog `((comment . ,comment)
+                    (timeSpent . ,timeSpent)
+                    (timeSpentInSeconds . 10)
+                    (startDate . ,startDate)))
+         (worklog (if worklog-id
+                      (cons `(id . ,(replace-regexp-in-string "^worklog-" "" worklog-id)) worklog)
+                    worklog)))
+    (if worklog-id
+        (jiralib-update-worklog worklog)
+      (jiralib-add-worklog-and-autoadjust-remaining-estimate issue-id startDate timeSpent comment))
+    (org-jira-delete-current-worklog)
+    (org-jira-update-worklogs-for-current-issue)))
+
+(defun org-jira-delete-current-comment ()
+  "Delete the current comment."
+  (ensure-on-comment
+   (delete-region (point-min) (point-max))))
+
+(defun org-jira-delete-current-worklog ()
+  "Delete the current worklog."
+  (ensure-on-worklog
+   (delete-region (point-min) (point-max))))
+
+;;;###autoload
+(defun org-jira-copy-current-issue-key ()
+  "Copy the current issue's key into clipboard."
+  (interactive)
+  (let ((issue-id (org-jira-get-from-org 'issue 'key)))
+    (with-temp-buffer
+      (insert issue-id)
+      (kill-region (point-min) (point-max)))))
+
+(defun org-jira-get-comment-id (comment)
+  (org-jira-find-value comment 'id))
+
+(defun org-jira-get-comment-author (comment)
+  (org-jira-find-value comment 'author 'displayName))
+
+(defun org-jira-isa-ignored-comment? (comment)
+  (member-ignore-case (oref comment author) org-jira-ignore-comment-user-list))
+
+(defun org-jira-maybe-reverse-comments (comments)
+  (if org-jira-reverse-comment-order (reverse comments) comments))
+
+(defun org-jira-extract-comments-from-data (data)
+  (->> (append data nil)
+       org-jira-sdk-create-comments-from-data-list
+       org-jira-maybe-reverse-comments
+       (cl-remove-if #'org-jira-isa-ignored-comment?)))
+
+(defun org-jira--render-comment (Issue Comment)
+  (with-slots (issue-id) Issue
+    (with-slots (comment-id author headline created updated body) Comment
+      (org-jira-log (format "Rendering a comment: %s" body))
+      (ensure-on-issue-Issue Issue
+        (setq p (org-find-entry-with-id comment-id))
+        (when (and p (>= p (point-min))
+                   (<= p (point-max)))
+          (goto-char p)
+          (org-narrow-to-subtree)
+          (delete-region (point-min) (point-max)))
+        (goto-char (point-max))
+        (unless (looking-at "^")
+          (insert "\n"))
+        (insert "*** ")
+        (org-jira-insert headline "\n")
+        (org-narrow-to-subtree)
+        (org-jira-entry-put (point) "ID" comment-id)
+        (org-jira-entry-put (point) "created" created)
+        (unless (string= created updated)
+          (org-jira-entry-put (point) "updated" updated))
+        (goto-char (point-max))
+        ;;  Insert 2 spaces of indentation so Jira markup won't cause org-markup
+        (org-jira-insert (replace-regexp-in-string "^" "  " (or body "")))))))
+
+(defun org-jira-update-comments-for-issue (Issue)
+  "Update the comments for the specified ISSUE issue."
+  (with-slots (issue-id) Issue
+    (jiralib-get-comments
+     issue-id
+     (org-jira-with-callback
+       (org-jira-log "In the callback for org-jira-update-comments-for-issue.")
+       (-->
+        (org-jira-find-value cb-data 'comments)
+        (org-jira-extract-comments-from-data it)
+        (mapc (lambda (Comment) (org-jira--render-comment Issue Comment)) it))))))
+
+(defun org-jira-update-comments-for-current-issue ()
+  "Update comments for the current issue."
+  (org-jira-log "About to update comments for current issue.")
+  (let ((Issue (make-instance 'org-jira-sdk-issue
+                              :issue-id (org-jira-get-from-org 'issue 'key)
+                              :filename (org-jira-filename))))
+    (-> Issue org-jira-update-comments-for-issue)))
+
+(defun org-jira-delete-subtree ()
+  "Derived from org-cut-subtree.
+
+Like that function, without mangling the user's clipboard for the
+purpose of wiping an old subtree."
+  (let (beg end folded (beg0 (point)))
+    (org-back-to-heading t) ; take what is really there
+    (setq beg (point))
+    (skip-chars-forward " \t\r\n")
+    (save-match-data
+      (save-excursion (outline-end-of-heading)
+                      (setq folded (org-invisible-p))
+                      (org-end-of-subtree t t)))
+    ;; Include the end of an inlinetask
+    (when (and (featurep 'org-inlinetask)
+               (looking-at-p (concat (org-inlinetask-outline-regexp)
+                                     "END[ \t]*$")))
+      (end-of-line))
+    (setq end (point))
+    (goto-char beg0)
+    (when (> end beg)
+      (setq org-subtree-clip-folded folded)
+      (org-save-markers-in-region beg end)
+      (delete-region beg end))))
+
+(defun org-jira-update-attachments-for-current-issue ()
+  "Update the attachments for the current issue."
+  (when jiralib-use-restapi
+    (lexical-let ((issue-id (org-jira-get-from-org 'issue 'key)))
+      ;; Run the call
+      (jiralib-get-attachments
+       issue-id
+       (save-excursion
+         (cl-function
+          (lambda (&key data &allow-other-keys)
+            ;; First, make sure we're in the proper buffer (logic copied from org-jira-get-issues.
+            (let* ((proj-key (replace-regexp-in-string "-.*" "" issue-id))
+                   (project-file (org-jira--get-project-file-name proj-key))
+                   (project-buffer (or (find-buffer-visiting project-file)
+                                       (find-file project-file))))
+              (with-current-buffer project-buffer
+                ;; delete old attachment node
+                (ensure-on-issue
+                  (if (org-goto-first-child)
+                      (while (org-goto-sibling)
+                        (forward-thing 'whitespace)
+                        (when (looking-at "Attachments:")
+                          (org-jira-delete-subtree)))))
+                (let ((attachments (org-jira-find-value data 'fields 'attachment)))
+                  (when (not (zerop (length attachments)))
+                    (ensure-on-issue
+                      (if (org-goto-first-child)
+                          (progn
+                            (while (org-goto-sibling))
+                            (org-insert-heading-after-current))
+                        (org-insert-subheading nil))
+
+                      (insert "Attachments:")
+                      (mapc
+                       (lambda (attachment)
+                         (let ((attachment-id (org-jira-get-comment-id attachment))
+                               (author (org-jira-get-comment-author attachment))
+                               (created (org-jira-transform-time-format
+                                         (org-jira-find-value attachment 'created)))
+                               (size (org-jira-find-value attachment 'size))
+                               (mimeType (org-jira-find-value attachment 'mimeType))
+                               (content (org-jira-find-value attachment 'content))
+                               (filename (org-jira-find-value attachment 'filename)))
+                           (if (looking-back "Attachments:")
+                               (org-insert-subheading nil)
+                             (org-insert-heading-respect-content))
+                           (insert "[[" content "][" filename "]]")
+                           (org-narrow-to-subtree)
+                           (org-jira-entry-put (point) "ID" attachment-id)
+                           (org-jira-entry-put (point) "Author" author)
+                           (org-jira-entry-put (point) "Name" filename)
+                           (org-jira-entry-put (point) "Created" created)
+                           (org-jira-entry-put (point) "Size" (ls-lisp-format-file-size size t))
+                           (org-jira-entry-put (point) "Content" content)
+                           (widen)))
+                       attachments)))))))))))))
+
+(defun org-jira-sort-org-clocks (clocks)
+  "Given a CLOCKS list, sort it by start date descending."
+  ;; Expects data such as this:
+
+  ;; ((\"2017-02-26 Sun 00:08\" \"2017-02-26 Sun 01:08\" \"Hi\" \"10101\")
+  ;;  (\"2017-03-16 Thu 22:25\" \"2017-03-16 Thu 22:57\" \"Test\" \"10200\"))
+  (sort clocks
+        (lambda (a b)
+          (> (time-to-seconds (date-to-time (car a)))
+             (time-to-seconds (date-to-time (car b)))))))
+
+(defun org-jira-update-worklogs-for-current-issue ()
+  "Update the worklogs for the current issue."
+  (let ((issue-id (org-jira-get-from-org 'issue 'key))
+        (filename (org-jira-filename)))
+    (org-jira-update-worklogs-for-issue issue-id filename)))
+
+(defun org-jira-update-worklogs-for-issue (issue-id filename)
+  "Update the worklogs for the current ISSUE-ID located in FILENAME."
+  (org-jira-log (format "org-jira-update-worklogs-for-issue id: %s filename: %s"
+                issue-id filename))
+  ;; Run the call
+  (jiralib-get-worklogs
+   issue-id
+   (org-jira-with-callback
+     (ensure-on-issue-id-with-filename issue-id filename
+       (let ((worklogs (org-jira-find-value cb-data 'worklogs)))
+         (org-jira-log (format "org-jira-update-worklogs-for-issue cb id: %s fn: %s"
+                       issue-id filename))
+         (org-jira-logbook-reset issue-id filename
+          (org-jira-sort-org-clocks (org-jira-worklogs-to-org-clocks
+                                     (jiralib-worklog-import--filter-apply worklogs)))))))))
+
+;;;###autoload
+(defun org-jira-unassign-issue ()
+  "Update an issue to be unassigned."
+  (interactive)
+  (let ((issue-id (org-jira-parse-issue-id))
+        (filename (org-jira-parse-issue-filename)))
+    (org-jira-update-issue-details issue-id filename :assignee nil)))
+
+;;;###autoload
+(defun org-jira-set-issue-reporter ()
+  "Update an issue's reporter interactively."
+  (interactive)
+  (let ((issue-id (org-jira-parse-issue-id))
+        (filename (org-jira-parse-issue-filename)))
+    (if issue-id
+        (let* ((project (replace-regexp-in-string "-[0-9]+" "" issue-id))
+               (jira-users (org-jira-get-reporter-candidates project)) ;; TODO, probably a better option than org-jira-get-assignable-users here
+               (user (completing-read
+                      "Reporter: "
+                      (append (mapcar 'car jira-users)
+                              (mapcar 'cdr jira-users))))
+               (reporter (or
+                          (cdr (assoc user jira-users))
+                          (cdr (rassoc user jira-users)))))
+          (when (null reporter)
+            (error "No reporter found, this should probably never happen."))
+          (org-jira-update-issue-details issue-id filename :reporter (jiralib-get-user-account-id project reporter)))
+      (error "Not on an issue"))))
+
+;;;###autoload
+(defun org-jira-assign-issue ()
+  "Update an issue with interactive re-assignment."
+  (interactive)
+  (let ((issue-id (org-jira-parse-issue-id))
+        (filename (org-jira-parse-issue-filename)))
+    (if issue-id
+        (let* ((project (replace-regexp-in-string "-[0-9]+" "" issue-id))
+               (jira-users (org-jira-get-assignable-users project))
+               (user (completing-read
+                      "Assignee: "
+                      (append (mapcar 'car jira-users)
+                              (mapcar 'cdr jira-users))))
+               (assignee (or
+                          (cdr (assoc user jira-users))
+                          (cdr (rassoc user jira-users)))))
+          (when (null assignee)
+            (error "No assignee found, use org-jira-unassign-issue to make the issue unassigned"))
+          (org-jira-update-issue-details issue-id filename :assignee (jiralib-get-user-account-id project assignee)))
+      (error "Not on an issue"))))
+
+;;;###autoload
+(defun org-jira-update-issue ()
+  "Update an issue."
+  (interactive)
+  (let ((issue-id (org-jira-parse-issue-id))
+        (filename (org-jira-parse-issue-filename)))
+    (if issue-id
+        (org-jira-update-issue-details issue-id filename)
+      (error "Not on an issue"))))
+
+;;;###autoload
+(defun org-jira-todo-to-jira ()
+  "Convert an ordinary todo item to a jira ticket."
+  (interactive)
+  (ensure-on-todo
+   (when (org-jira-parse-issue-id)
+     (error "Already on jira ticket"))
+   (save-excursion (org-jira-create-issue
+                    (org-jira-read-project)
+                    (org-jira-read-issue-type)
+                    (org-get-heading t t)
+                    (org-get-entry)))
+   (delete-region (point-min) (point-max))))
+
+;;;###autoload
+(defun org-jira-get-subtasks ()
+  "Get subtasks for the current issue."
+  (interactive)
+  (ensure-on-issue
+    (org-jira-get-issues-headonly (jiralib-do-jql-search (format "parent = %s" (org-jira-parse-issue-id))))))
+
+;;;###autoload
+(defun org-jira-update-issue-labels ()
+  "Update jira issue labels."
+  (interactive)
+  (let* ((labels (org-jira-parse-issue-labels))
+         (updated-labels (org-jira-read-labels (format "%s, " labels)))
+         (updated-labels-string (mapconcat 'identity updated-labels ", ")))
+    (org-set-property "labels" updated-labels-string)
+    (org-jira-update-issue)))
+
+(defvar org-jira-project-read-history nil)
+(defvar org-jira-boards-read-history nil)
+(defvar org-jira-sprints-read-history nil)
+(defvar org-jira-components-read-history nil)
+(defvar org-jira-priority-read-history nil)
+(defvar org-jira-type-read-history nil)
+
+(defun org-jira-read-project ()
+  "Read project name."
+  (completing-read
+   "Project: "
+   (jiralib-make-list (jiralib-get-projects) 'key)
+   nil
+   t
+   nil
+   'org-jira-project-read-history
+   (car org-jira-project-read-history)))
+
+(defun org-jira-read-board ()
+  "Read board name. Returns cons pair (name . integer-id)"
+  (let* ((boards-alist
+          (jiralib-make-assoc-list (jiralib-get-boards) 'name 'id))
+         (board-name
+          (completing-read "Boards: "  boards-alist
+                           nil  t  nil
+                           'org-jira-boards-read-history
+                           (car org-jira-boards-read-history))))
+    (assoc board-name boards-alist)))
+
+(defun org-jira-read-sprint (board)
+  "Read sprint name. Returns cons pair (name . integer-id)"
+  (let* ((sprints-alist
+         (jiralib-make-assoc-list (append (alist-get 'values (jiralib-get-board-sprints board)) nil) 'name 'id))
+         (sprint-name
+          (completing-read "Sprints: " sprints-alist
+                           nil t nil
+                           'org-jira-sprints-read-history
+                           (car org-jira-sprints-read-history))))
+       (assoc sprint-name sprints-alist)))
+
+(defun org-jira-read-component (project)
+  "Read the components options for PROJECT such as EX."
+  (completing-read
+   "Components (choose Done to stop): "
+   (append '("Done") (mapcar 'cdr (jiralib-get-components project)))
+   nil
+   t
+   nil
+   'org-jira-components-read-history
+   "Done"))
+
+;; TODO: Finish this feature - integrate into org-jira-create-issue
+(defun org-jira-read-components (project)
+  "Types: string PROJECT : string (csv of components).
+
+Get all the components for the PROJECT such as EX,
+that should be bound to an issue."
+  (let (components component)
+    (while (not (equal "Done" component))
+      (setq component (org-jira-read-component project))
+      (unless (equal "Done" component)
+        (push component components)))
+    components))
+
+(defun org-jira-read-priority ()
+  "Read priority name."
+  (completing-read
+   "Priority: "
+   (mapcar 'cdr (jiralib-get-priorities))
+   nil
+   t
+   nil
+   'org-jira-priority-read-history
+   (car org-jira-priority-read-history)))
+
+(defun org-jira-read-issue-type (&optional project)
+  "Read issue type name.  PROJECT is the optional project key."
+  (let* ((issue-types
+          (mapcar 'cdr (if project
+                           (jiralib-get-issue-types-by-project project)
+                         (jiralib-get-issue-types))))
+         (initial-input (when (member (car org-jira-type-read-history) issue-types)
+                          org-jira-type-read-history)))
+
+    ;; TODO: The completing-read calls as such are all over the place, and always tend
+    ;; to follow this exact same call structure - we should abstract to a single fn
+    ;; that will allow calling with fewer or keyword args
+    (completing-read
+     "Type: "                           ; PROMPT
+     issue-types                        ; COLLECTION
+     nil                                ; PREDICATE
+     t                                  ; REQUIRE-MATCH
+     nil                                ; INITIAL-INPUT
+     'initial-input                     ; HIST
+     (car initial-input))))             ; DEF
+
+(defun org-jira-read-subtask-type ()
+  "Read issue type name."
+  (completing-read
+   "Type: "
+   (mapcar 'cdr (jiralib-get-subtask-types))
+   nil
+   t
+   nil
+   'org-jira-type-read-history
+   (car org-jira-type-read-history)))
+
+(defun org-jira-get-issue-struct (project type summary description &optional parent-id)
+  "Create an issue struct for PROJECT, of TYPE, with SUMMARY and DESCRIPTION."
+  (if (or (equal project "")
+          (equal type "")
+          (equal summary ""))
+      (error "Must provide all information!"))
+  (let* ((project-components (jiralib-get-components project))
+         (jira-users (org-jira-get-assignable-users project))
+         (user (completing-read "Assignee: " (mapcar 'car jira-users)))
+         (priority (car (rassoc (org-jira-read-priority) (jiralib-get-priorities))))
+         (labels (org-jira-read-labels))
+         (ticket-struct
+          `((fields
+             (project (key . ,project))
+             (parent (key . ,parent-id))
+             (issuetype (id . ,(car (rassoc type (if (and (boundp 'parent-id) parent-id)
+                                                     (jiralib-get-subtask-types)
+                                                   (jiralib-get-issue-types-by-project project))))))
+             (summary . ,(format "%s%s" summary
+                                 (if (and (boundp 'parent-id) parent-id)
+                                     (format " (subtask of [jira:%s])" parent-id)
+                                   "")))
+             (description . ,description)
+             (priority (id . ,priority))
+             (labels . ,labels)
+             ;; accountId should be nil if Unassigned, not the key slot.
+             (assignee (accountId . ,(or (cdr (assoc user jira-users)) nil)))))))
+    ticket-struct))
+
+;;;###autoload
+(defun org-jira-create-issue (project type summary description)
+  "Create an issue in PROJECT, of type TYPE, with given SUMMARY and DESCRIPTION."
+  (interactive
+   (let* ((project (org-jira-read-project))
+          (type (org-jira-read-issue-type project))
+          (summary (read-string "Summary: "))
+          (description (read-string "Description: ")))
+     (list project type summary description)))
+  (if (or (equal project "")
+          (equal type "")
+          (equal summary ""))
+      (error "Must provide all information!"))
+  (let* ((parent-id nil)
+         (ticket-struct (org-jira-get-issue-struct project type summary description)))
+    (org-jira-get-issues (list (jiralib-create-issue ticket-struct)))))
+
+;;;###autoload
+(defun org-jira-create-subtask (project type summary description)
+  "Create a subtask issue for PROJECT, of TYPE, with SUMMARY and DESCRIPTION."
+  (interactive (ensure-on-issue (list (org-jira-read-project)
+                                      (org-jira-read-subtask-type)
+                                      (read-string "Summary: ")
+                                      (read-string "Description: "))))
+  (if (or (equal project "")
+          (equal type "")
+          (equal summary ""))
+      (error "Must provide all information!"))
+  (let* ((parent-id (org-jira-parse-issue-id))
+         (ticket-struct (org-jira-get-issue-struct project type summary description parent-id)))
+    (org-jira-get-issues (list (jiralib-create-subtask ticket-struct)))))
+
+(defun org-jira-get-issue-val-from-org (key)
+  "Return the requested value by KEY from the current issue."
+  ;; There is some odd issue when not using any let-scoping, where myself
+  ;; and an array of users are hitting a snag circa 2023-03-01 time frame
+  ;; in which the setq portion of a when clause is being hit even when it
+  ;; evaluates to false - the bug only manifests on a first launch of Emacs - it
+  ;; doesn't occur when re-evaluating this function.  However, wrapping it "fixes"
+  ;; the issue.
+  ;;
+  ;; The first link has the most troubleshooting/diagnosis around the particulars of
+  ;; this bug.
+  ;;
+  ;; See: https://github.com/ahungry/org-jira/issues/319
+  ;; See: https://github.com/ahungry/org-jira/issues/296
+  ;; See: https://github.com/ahungry/org-jira/issues/316
+  (lexical-let ((my-key key))
+    (ensure-on-issue
+      (cond ((eq my-key 'description)
+             (org-goto-first-child)
+             (forward-thing 'whitespace)
+             (if (looking-at "description: ")
+                 (org-trim (org-get-entry))
+               (error "Can not find description field for this issue")))
+
+            ((eq my-key 'summary)
+             (ensure-on-issue
+               (org-get-heading t t)))
+
+            ;; org returns a time tuple, we need to convert it
+            ((eq my-key 'deadline)
+             (let ((encoded-time (org-get-deadline-time (point))))
+               (when encoded-time
+                 (cl-reduce (lambda (carry segment)
+                              (format "%s-%s" carry segment))
+                            (reverse (cl-subseq (decode-time encoded-time) 3 6))))))
+
+            ;; default case, just grab the value in the properties block
+            (t
+             (when (symbolp my-key)
+               (setq my-key (symbol-name my-key)))
+
+             (setq my-key (or (assoc-default my-key org-jira-property-overrides)
+                              my-key))
+
+             ;; This is the "impossible" to hit setq that somehow gets hit without the let
+             ;; wrapper around the function input args.
+             (when (string= my-key "key")
+               (setq my-key "ID"))
+
+             ;; The variable `org-special-properties' will mess this up
+             ;; if our search, such as 'priority' is within there, so
+             ;; don't bother with it for this (since we only ever care
+             ;; about the local properties, not any hierarchal or special
+             ;; ones).
+             (let ((org-special-properties nil))
+               (or (org-entry-get (point) my-key t)
+                   "")))))))
+
+(defun org-jira-read-action (actions)
+  "Read issue workflow progress ACTIONS."
+  (let ((action (completing-read
+                 "Action: "
+                 (mapcar 'cdr actions)
+                 nil
+                 t
+                 nil)))
+    (or
+     (car (rassoc action actions))
+     (user-error "You specified an empty action, the valid actions are: %s" (mapcar 'cdr actions)))))
+
+(defun org-jira-read-labels (&optional current-labels)
+  "Pick multiple labels which will be added or updating existing
+CURRENT-LABELS and save with the jira issue."
+  (unless current-labels (setq current-labels nil))
+  (if jiralib-labels-cache
+      (completing-read-multiple "Labels: " jiralib-labels-cache nil nil current-labels)
+    (jiralib-get-labels)
+    (completing-read-multiple "Labels: " jiralib-labels-cache nil nil current-labels)))
+
+(defvar org-jira-fields-history nil)
+(defun org-jira-read-field (fields)
+  "Read (custom) FIELDS for workflow progress."
+  (let ((field-desc (completing-read
+                     "More fields to set: "
+                     (cons "Thanks, no more fields are *required*." (mapcar 'org-jira-decode (mapcar 'cdr fields)))
+                     nil
+                     t
+                     nil
+                     'org-jira-fields-history))
+        field-name)
+    (setq field-name (car (rassoc field-desc fields)))
+    (if field-name
+        (intern field-name)
+      field-name)))
+
+
+(defvar org-jira-rest-fields nil
+  "Extra fields are held here for usage between two endpoints.
+Used in org-jira-read-resolution and org-jira-progress-issue calls.")
+
+(defvar org-jira-resolution-history nil)
+(defun org-jira-read-resolution ()
+  "Read issue workflow progress resolution."
+  (if (not jiralib-use-restapi)
+      (let ((resolution (completing-read
+                         "Resolution: "
+                         (mapcar 'cdr (jiralib-get-resolutions))
+                         nil
+                         t
+                         nil
+                         'org-jira-resolution-history
+                         (car org-jira-resolution-history))))
+        (car (rassoc resolution (jiralib-get-resolutions))))
+    (let* ((resolutions (org-jira-find-value org-jira-rest-fields 'resolution 'allowedValues))
+           (resolution-name (completing-read
+                             "Resolution: "
+                             (mapcar (lambda (resolution)
+                                       (org-jira-find-value resolution 'name))
+                                     resolutions))))
+      (cons 'name resolution-name))))
+
+(defun org-jira-refresh-issues-in-buffer-loose ()
+  "Iterates over all level 1-2 headings in current buffer, refreshing on issue :ID:.
+It differs with `org-jira-refresh-issues-in-buffer' in that it accepts the current buffer
+and its corresponding filename, regardless of whether it has been previously registered
+as an org-jira project file or not."
+  (interactive)
+  (save-excursion
+    (save-restriction
+      (widen)
+      (let* ((org-ids (org-map-entries 'org-id-get "LEVEL=1|LEVEL=2"))
+             (org-ids (delq nil org-ids))
+             (file-name (file-name-sans-extension buffer-file-name)))
+        (mapcar (lambda (org-id) (org-jira--refresh-issue org-id file-name))
+                org-ids)))))
+
+(defun org-jira-refresh-issues-in-buffer ()
+  "Iterate across all level 1-2 headings in current buffer, refreshing on issue :ID:.
+Where issue-id will be something such as \"EX-22\"."
+  (interactive)
+  (save-excursion
+    (save-restriction
+      (widen)
+      (let* ((org-ids (org-map-entries 'org-id-get "LEVEL=1|LEVEL=2"))
+             (org-ids (delq nil org-ids)))
+        ;; It's possible we could be on a non-org-jira headline, but
+        ;; that should be an exceptional case and not necessitating a
+        ;; fix atm.
+        (mapcar 'org-jira--refresh-issue-by-id org-ids)))))
+
+;;;###autoload
+(defun org-jira-refresh-issue ()
+  "Refresh current issue from jira to org."
+  (interactive)
+  (ensure-on-issue
+    (org-jira--refresh-issue (org-jira-id) (org-jira-filename))))
+
+(defun org-jira--refresh-issue (issue-id &optional filename)
+  "Refresh issue from jira to org using ISSUE-ID."
+  (unless filename (setq filename (replace-regexp-in-string "-[0-9]+" "" issue-id)))
+  (jiralib-get-issue
+   issue-id
+   (org-jira-with-callback
+     (org-jira-log (format "Received refresh issue data for id: %s in file: %s" issue-id filename))
+     (--> cb-data
+          list
+          (org-jira-sdk-create-issues-from-data-list-with-filename filename it)
+          org-jira--render-issues-from-issue-list))))
+
+(defun org-jira--refresh-issue-by-id (issue-id)
+  "Refresh issue from jira to org using ISSUE-ID."
+  (ensure-on-issue-id issue-id
+    (org-jira--refresh-issue issue-id)))
+
+(defvar org-jira-fields-values-history nil)
+;;;###autoload
+(defun org-jira-progress-issue ()
+  "Progress issue workflow."
+  (interactive)
+  (ensure-on-issue
+    (let* ((issue-id (org-jira-id))
+           (actions (jiralib-get-available-actions
+                     issue-id
+                     (org-jira-get-issue-val-from-org 'status)))
+           (action (org-jira-read-action actions))
+           (fields (jiralib-get-fields-for-action issue-id action))
+           (org-jira-rest-fields fields)
+           (field-key)
+           (custom-fields-collector nil)
+           (custom-fields
+            (progn
+              ;; delete those elements in fields, which have
+              ;; already been set in custom-fields-collector
+              (while fields
+                (setq fields
+                      (cl-remove-if
+                       (lambda (strstr)
+                         (cl-member-if (lambda (symstr)
+                                         (string= (car strstr)  (symbol-name (car symstr))))
+                                       custom-fields-collector))
+                       fields))
+                (setq field-key (org-jira-read-field fields))
+                (if (not field-key)
+                    (setq fields nil)
+                  (setq custom-fields-collector
+                        (cons
+                         (funcall (if jiralib-use-restapi
+                                      #'list
+                                    #'cons)
+                                  field-key
+                                  (if (eq field-key 'resolution)
+                                      (org-jira-read-resolution)
+                                    (let ((field-value (completing-read
+                                                        (format "Please enter %s's value: "
+                                                                (cdr (assoc (symbol-name field-key) fields)))
+                                                        org-jira-fields-values-history
+                                                        nil
+                                                        nil
+                                                        nil
+                                                        'org-jira-fields-values-history)))
+                                      (if jiralib-use-restapi
+                                          (cons 'name field-value)
+                                        field-value))))
+                         custom-fields-collector))))
+              custom-fields-collector)))
+      (jiralib-progress-workflow-action
+       issue-id
+       action
+       custom-fields
+       (cl-function
+        (lambda (&key data &allow-other-keys)
+          (org-jira-refresh-issue)))))))
+
+(defun org-jira-progress-next-action (actions current-status)
+  "Grab the user defined 'next' action from ACTIONS, given CURRENT-STATUS."
+  (let* ((next-action-name (cdr (assoc current-status org-jira-progress-issue-flow)))
+         (next-action-id (caar (cl-remove-if-not
+                                (lambda (action)
+                                  (equal action next-action-name)) actions :key #'cdr))))
+    next-action-id))
+
+;;;###autoload
+(defun org-jira-progress-issue-next ()
+  "Progress issue workflow."
+  (interactive)
+  (ensure-on-issue
+    (let* ((issue-id (org-jira-id))
+           (filename (org-jira-filename))
+           (actions (jiralib-get-available-actions
+                     issue-id
+                     (org-jira-get-issue-val-from-org 'status)))
+           (action (org-jira-progress-next-action actions (org-jira-get-issue-val-from-org 'status)))
+           (fields (jiralib-get-fields-for-action issue-id action))
+           (org-jira-rest-fields fields)
+           (field-key)
+           (custom-fields-collector nil)
+           (custom-fields
+            (progn
+              ;; delete those elements in fields, which have
+              ;; already been set in custom-fields-collector
+              (while fields
+                (setq fields
+                      (cl-remove-if
+                       (lambda (strstr)
+                         (cl-member-if (lambda (symstr)
+                                         (string= (car strstr)  (symbol-name (car symstr))))
+                                       custom-fields-collector))
+                       fields))
+                (setq field-key (org-jira-read-field fields))
+                (if (not field-key)
+                    (setq fields nil)
+                  (setq custom-fields-collector
+                        (cons
+                         (funcall (if jiralib-use-restapi
+                                      #'list
+                                    #'cons)
+                                  field-key
+                                  (if (eq field-key 'resolution)
+                                      (org-jira-read-resolution)
+                                    (let ((field-value (completing-read
+                                                        (format "Please enter %s's value: "
+                                                                (cdr (assoc (symbol-name field-key) fields)))
+                                                        org-jira-fields-values-history
+                                                        nil
+                                                        nil
+                                                        nil
+                                                        'org-jira-fields-values-history)))
+                                      (if jiralib-use-restapi
+                                          (cons 'name field-value)
+                                        field-value))))
+                         custom-fields-collector))))
+              custom-fields-collector)))
+      (if action
+          (jiralib-progress-workflow-action
+           issue-id
+           action
+           custom-fields
+           (org-jira-with-callback
+             (ensure-on-issue-id-with-filename issue-id filename
+               (org-jira-refresh-issue))))
+        (error "No action defined for that step!")))))
+
+(defun org-jira-get-id-name-alist (name ids-to-names)
+  "Find the id corresponding to NAME in IDS-TO-NAMES and return an alist with id and name as keys."
+  (let ((id (car (rassoc name ids-to-names))))
+    `((id . ,id)
+      (name . ,name))))
+
+(defun org-jira-build-components-list (project-components org-issue-components)
+  "Given PROJECT-COMPONENTS, attempt to build a list.
+
+If the PROJECT-COMPONENTS are nil, this should return:
+
+  (list components []), which will translate into the JSON:
+
+  {\"components\": []}
+
+otherwise it should return:
+
+  (list components (list (cons id comp-id) (cons name item-name))),
+
+  which will translate into the JSON:
+
+{\"components\": [{\"id\": \"comp-id\", \"name\": \"item\"}]}"
+  (if (not project-components) (vector) ;; Return a blank array for JSON
+    (apply 'list
+           (cl-mapcan
+            (lambda (item)
+              (let ((comp-id (car (rassoc item project-components))))
+                (if comp-id
+                    `(((id . ,comp-id)
+                       (name . ,item)))
+                  nil)))
+            (split-string org-issue-components ",\\s *")))))
+
+(defun org-jira-strip-priority-tags (s)
+  "Given string S, remove any priority tags in the brackets."
+  (->> s (replace-regexp-in-string "\\[#.*?\\]" "") org-trim))
+
+(defun org-jira-update-issue-details (issue-id filename &rest rest)
+  "Update the details of issue ISSUE-ID in FILENAME.  REST will contain optional input."
+  (ensure-on-issue-id-with-filename issue-id filename
+    ;; Set up a bunch of values from the org content
+    (let* ((org-issue-components (org-jira-get-issue-val-from-org 'components))
+           (org-issue-labels (org-jira-get-issue-val-from-org 'labels))
+           (org-issue-description (org-trim (org-jira-get-issue-val-from-org 'description)))
+           (org-issue-priority (org-jira-get-issue-val-from-org 'priority))
+           (org-issue-type (org-jira-get-issue-val-from-org 'type))
+           (org-issue-type-id (org-jira-get-issue-val-from-org 'type-id))
+           (org-issue-assignee (cl-getf rest :assignee (org-jira-get-issue-val-from-org 'assignee)))
+           (org-issue-reporter (cl-getf rest :reporter (org-jira-get-issue-val-from-org 'reporter)))
+           (project (replace-regexp-in-string "-[0-9]+" "" issue-id))
+           (project-components (jiralib-get-components project)))
+
+      ;; Lets fire off a worklog update async with the main issue
+      ;; update, why not?  This is better to fire first, because it
+      ;; doesn't auto-refresh any areas, while the end of the main
+      ;; update does a callback that reloads the worklog entries (so,
+      ;; we hope that won't occur until after this successfully syncs
+      ;; up).  Only do this sync if the user defcustom defines it as such.
+      (when org-jira-worklog-sync-p
+        (org-jira-update-worklogs-from-org-clocks))
+
+      ;; Send the update to jira
+      (let ((update-fields
+             (list (cons
+                    'components
+                    (or (org-jira-build-components-list
+                         project-components
+                         org-issue-components) []))
+                   (cons 'labels (split-string org-issue-labels ",\\s *"))
+                   (cons 'priority (org-jira-get-id-name-alist org-issue-priority
+                                                       (jiralib-get-priorities)))
+                   (cons 'description org-issue-description)
+                   (cons 'assignee (list (cons 'id (jiralib-get-user-account-id project org-issue-assignee))))
+                   (cons 'summary (org-jira-strip-priority-tags (org-jira-get-issue-val-from-org 'summary)))
+                   (cons 'issuetype `((id . ,org-issue-type-id)
+      (name . ,org-issue-type))))))
+
+        (if org-jira-update-issue-details-include-reporter
+            (setq update-fields
+                  (append update-fields
+                          (list (cons 'reporter (list (cons 'id (jiralib-get-user-account-id project org-issue-reporter))))))))
+
+        ;; If we enable duedate sync and we have a deadline present
+        (when (and org-jira-deadline-duedate-sync-p
+                   (org-jira-get-issue-val-from-org 'deadline))
+          (setq update-fields
+                (append update-fields
+                        (list (cons 'duedate (org-jira-get-issue-val-from-org 'deadline))))))
+
+        ;; TODO: We need some way to handle things like assignee setting
+        ;; and refreshing the proper issue in the proper buffer/filename.
+        (jiralib-update-issue
+         issue-id
+         update-fields
+         ;; This callback occurs on success
+         (org-jira-with-callback
+           (message (format "Issue '%s' updated!" issue-id))
+           (jiralib-get-issue
+            issue-id
+            (org-jira-with-callback
+              (org-jira-log "Update get issue for refresh callback hit.")
+              (-> cb-data list org-jira-get-issues))))
+         ))
+      )))
+
+
+
+(defun org-jira-parse-issue-id ()
+  "Get issue id from org text."
+  (save-excursion
+    (let ((continue t)
+          issue-id)
+      (while continue
+        (when (string-match (jiralib-get-issue-regexp)
+                            (or (setq issue-id (org-entry-get (point) "ID"))
+                                ""))
+          (setq continue nil))
+        (unless (and continue (org-up-heading-safe))
+          (setq continue nil)))
+      issue-id)))
+
+(defun org-jira-parse-issue-filename ()
+  "Get filename from org text."
+  (save-excursion
+    (let ((continue t)
+          filename)
+      (while continue
+        (when (setq filename (org-entry-get (point) "filename"))
+          (setq continue nil))
+        (unless (and continue (org-up-heading-safe))
+          (setq continue nil)))
+      filename)))
+
+(defun org-jira-parse-issue-labels ()
+  "Get issue labels from org text."
+  (save-excursion
+    (let ((continue t)
+          labels)
+      (while continue
+        (when (setq labels (org-entry-get (point) "labels"))
+          (setq continue nil))
+        (unless (and continue (org-up-heading-safe))
+          (setq continue nil)))
+      labels)))
+
+(defun org-jira-get-from-org (type entry)
+  "Get an org property from the current item.
+
+TYPE is the type to of the current item, and can be 'issue, or 'comment.
+
+ENTRY will vary, and is the name of the property to return.  If
+it is a symbol, it will be converted to string."
+  (when (symbolp entry)
+    (setq entry (symbol-name entry)))
+  (cond
+   ((eq type 'issue)
+    (org-jira-get-issue-val-from-org entry))
+   ((eq type 'comment)
+    (org-jira-get-comment-val-from-org entry))
+   ((eq type 'worklog)
+    (org-jira-get-worklog-val-from-org entry))
+   (t (error "Unknown type %s" type))))
+
+(defun org-jira-get-comment-val-from-org (entry)
+  "Get the JIRA issue field value ENTRY of the current comment item."
+  (ensure-on-comment
+   (when (symbolp entry)
+     (setq entry (symbol-name entry)))
+   (when (string= entry "id")
+     (setq entry "ID"))
+   (org-entry-get (point) entry)))
+
+(defun org-jira-get-worklog-val-from-org (entry)
+  "Get the JIRA issue field value ENTRY of the current worklog item."
+  (ensure-on-worklog
+   (when (symbolp entry)
+     (setq entry (symbol-name entry)))
+   (when (string= entry "id")
+     (setq entry "ID"))
+   (org-entry-get (point) entry)))
+
+(defun org-jira-get-comment-body (&optional comment-id)
+  "Get the comment body of the comment with id COMMENT-ID."
+  (ensure-on-comment
+   (goto-char (point-min))
+   ;; so that search for :END: won't fail
+   (org-jira-entry-put (point) "ID" comment-id)
+   (search-forward ":END:" nil 1 1)
+   (forward-line)
+   (org-trim (buffer-substring-no-properties (point) (point-max)))))
+
+(defun org-jira-get-worklog-comment (&optional worklog-id)
+  "Get the worklog comment of the worklog with id WORKLOG-ID."
+  (ensure-on-worklog
+   (goto-char (point-min))
+   ;; so that search for :END: won't fail
+   (org-jira-entry-put (point) "ID" worklog-id)
+   (search-forward ":END:" nil 1 1)
+   (forward-line)
+   (org-trim (buffer-substring-no-properties (point) (point-max)))))
+
+(defun org-jira-id ()
+  "Get the ID entry for the current heading."
+  (org-entry-get (point) "ID"))
+
+(defun org-jira-filename ()
+  "Get the ID entry for the current heading."
+  (org-jira-get-from-org 'issue 'filename))
+
+;;;###autoload
+(defun org-jira-browse-issue ()
+  "Open the current issue in external browser."
+  (interactive)
+  (ensure-on-issue
+    (browse-url (concat (replace-regexp-in-string "/*$" "" jiralib-url) "/browse/" (org-jira-id)))))
+
+(defun org-jira-url-copy-file (url newname)
+  "Similar to url-copy-file but async."
+  (lexical-let ((newname newname))
+    (url-retrieve
+     url
+     (lambda (status)
+       (let ((buffer (current-buffer))
+             (handle nil)
+             (filename (if (and (file-exists-p newname)
+                                org-jira-download-ask-override)
+                           (read-string "File already exists, select new name or press ENTER to override: " newname)
+                         newname)))
+         (if (not buffer)
+             (error "Opening input file: No such file or directory, %s" url))
+         (with-current-buffer buffer
+           (setq handle (mm-dissect-buffer t)))
+         (mm-save-part-to-file handle filename)
+         (kill-buffer buffer)
+         (mm-destroy-parts handle))))))
+
+;;;###autoload
+(defun org-jira-download-attachment ()
+  "Download the attachment under cursor."
+  (interactive)
+  (when jiralib-use-restapi
+    (save-excursion
+      (org-up-heading-safe)
+      (org-back-to-heading)
+      (forward-thing 'whitespace)
+      (unless (looking-at "Attachments:")
+        (error "Not on a attachment region!")))
+    (let ((filename (org-entry-get (point) "Name"))
+          (url (org-entry-get (point) "Content"))
+          (url-request-extra-headers `(,jiralib-token)))
+      (org-jira-url-copy-file
+       url
+       (concat (file-name-as-directory org-jira-download-dir) filename)))))
+
+;;;###autoload
+(defun org-jira-get-issues-from-filter (filter)
+  "Get issues from the server-side stored filter named FILTER.
+
+Provide this command in case some users are not able to use
+client side jql (maybe because of JIRA server version?)."
+  (interactive
+   (list (completing-read "Filter: " (mapcar 'cdr (jiralib-get-saved-filters)))))
+  (org-jira-get-issues (jiralib-get-issues-from-filter (car (rassoc filter (jiralib-get-saved-filters))))))
+
+;;;###autoload
+(defun org-jira-get-issues-from-filter-headonly (filter)
+  "Get issues *head only* from saved filter named FILTER.
+See `org-jira-get-issues-from-filter'."
+  (interactive
+   (list (completing-read "Filter: " (mapcar 'cdr (jiralib-get-saved-filters)))))
+  (org-jira-get-issues-headonly (jiralib-get-issues-from-filter (car (rassoc filter (jiralib-get-saved-filters))))))
+
+(org-add-link-type "jira" 'org-jira-open)
+
+;; This was only added in org 9.0, not sure all org users will have
+;; that version, so keep the deprecated one from above for now.
+
+;;(org-link-set-parameters "jira" ((:follow . 'org-jira-open)))
+
+(defun org-jira-open (path)
+  "Open a Jira Link from PATH."
+  (org-jira-get-issue path))
+
+;;;###autoload
+(defun org-jira-get-issues-by-board ()
+  "Get list of ISSUES from agile board."
+  (interactive)
+  (let* ((board (org-jira-read-board))
+         (board-id (cdr board)))
+    (jiralib-get-board-issues board-id
+                              :callback org-jira-get-issue-list-callback
+                              :limit (org-jira-get-board-limit board-id)
+                              :query-params (org-jira--make-jql-queryparams board-id))))
+
+;;;###autoload
+(defun org-jira-get-issues-by-sprint ()
+  "Get list of ISSUES from sprint."
+  (interactive)
+  (let* ((board (org-jira-read-board))
+        (board-id (cdr board))
+        (sprint (org-jira-read-sprint board-id))
+        (sprint-id (cdr sprint)))
+    (jiralib-get-sprint-issues sprint-id
+                              :callback org-jira-get-issue-list-callback
+                              :limit (org-jira-get-board-limit board-id)
+                              :query-params (org-jira--make-jql-queryparams board-id))))
+
+(defun org-jira-get-board-limit (id)
+  "Get limit for number of retrieved issues for a board
+id - integer board id"
+  (let ((board (org-jira--get-board-from-buffer id)))
+    (if (and board (slot-boundp board 'limit))
+        (oref board limit)
+      org-jira-boards-default-limit)))
+
+(defun org-jira--make-jql-queryparams (board-id)
+  "make GET query parameters for jql, returns nil if JQL query is not set"
+  (let* ((board (org-jira--get-board-from-buffer board-id))
+         (jql (if (and board (slot-boundp board 'jql))
+                  (oref board jql))))
+    (if (and jql (not (string-blank-p jql))) `((jql ,jql)))))
+
+;;;###autoload
+(defun org-jira-get-issues-by-board-headonly ()
+  "Get list of ISSUES from agile board, head only."
+  (interactive)
+  (let* ((board (org-jira-read-board))
+         (board-id (cdr board)))
+    (org-jira-get-issues-headonly
+     (jiralib-get-board-issues board-id
+                               :limit (org-jira-get-board-limit board-id)
+                               :query-params (org-jira--make-jql-queryparams board-id)))))
+
+
+(defun org-jira--render-boards-from-list (boards)
+  "Add the boards from list into the org file.
+
+boards -  list of `org-jira-sdk-board' records."
+  (mapc 'org-jira--render-board  boards))
+
+
+(defun org-jira--render-board (board)
+  "Render single board"
+  ;;(org-jira-sdk-dump board)
+  (with-slots (id name url board-type jql limit) board
+    (with-current-buffer (org-jira--get-boards-buffer)
+      (org-jira-maybe-activate-mode)
+      (org-jira-freeze-ui
+        (org-save-outline-visibility t
+          (save-restriction
+            (outline-show-all)
+            (widen)
+            (goto-char (point-min))
+            (let* ((board-headline
+                    (format "Board: [[%s][%s]]" url name))
+                   (headline-pos
+                    (org-find-exact-headline-in-buffer board-headline (current-buffer) t))
+                   (entry-exists (and headline-pos (>= headline-pos (point-min)) (<= headline-pos (point-max))))
+                   (limit-value  (if (slot-boundp board 'limit) (int-to-string  limit) nil))
+                   (jql-value    (if (slot-boundp board 'jql) jql nil)))
+              (if entry-exists
+                  (progn
+                    (goto-char headline-pos)
+                    (org-narrow-to-subtree)
+                    (end-of-line))
+                (goto-char (point-max))
+                (unless (looking-at "^")
+                  (insert "\n"))
+                (insert "* ")
+                (org-jira-insert board-headline)
+                (org-narrow-to-subtree))
+              (org-jira-entry-put (point) "name" name)
+              (org-jira-entry-put (point) "type" board-type)
+              (org-jira-entry-put (point) "url"  url)
+              ;; do not overwrite existing user properties with empty values
+              (if (or (not entry-exists) limit-value)
+                  (org-jira-entry-put (point) "limit" limit-value))
+              (if (or (not entry-exists) jql-value)
+                  (org-jira-entry-put (point) "JQL" jql-value ))
+              (org-jira-entry-put (point) "ID"   id))))))))
+
+(defun org-jira--get-boards-file ()
+  (expand-file-name "boards-list.org" (org-jira--ensure-working-dir)))
+
+(defun org-jira--get-boards-buffer ()
+  "Return buffer for list of agile boards. Create one if it does not exist."
+  (let* ((boards-file  (org-jira--get-boards-file))
+         (existing-buffer (find-buffer-visiting boards-file)))
+    (if existing-buffer
+        existing-buffer
+      (find-file-noselect boards-file))))
+
+;;;###autoload
+(defun org-jira-get-boards ()
+  "Get list of boards and their properties."
+  (interactive)
+  (let* ((datalist (jiralib-get-boards))
+         (boards (org-jira-sdk-create-boards-from-data-list datalist)))
+    (org-jira--render-boards-from-list boards))
+  (switch-to-buffer (org-jira--get-boards-buffer)))
+
+(defun org-jira--get-board-from-buffer (id)
+  "Parse board record from org file."
+  (with-current-buffer (org-jira--get-boards-buffer)
+    (org-jira-freeze-ui
+      (let ((pos (org-find-property "ID" (int-to-string  id))))
+        (if pos
+            (progn
+              (goto-char pos)
+              (apply 'org-jira-sdk-board
+                     (cl-reduce
+                      #'(lambda (acc entry)
+                          (let* ((pname   (car entry))
+                                 (pval (cdr entry))
+                                 (pair (and pval
+                                            (not (string-empty-p pval))
+                                            (cond
+                                             ((equal pname "ID")
+                                              (list :id pval))
+                                             ((equal pname "URL")
+                                              (list :url pval))
+                                             ((equal pname "TYPE")
+                                              (list :board-type pval))
+                                             ((equal pname "NAME")
+                                              (list :name pval))
+                                             ((equal pname "LIMIT")
+                                              (list :limit (string-to-number pval)))
+                                             ((equal pname "JQL")
+                                              (list :jql pval))
+                                             (t nil)))))
+                            (if pair  (append pair acc)  acc)))
+                      (org-entry-properties) :initial-value  ()))))))))
+
+(defun org-jira-get-org-keyword-from-status (status)
+  "Gets an 'org-mode' keyword corresponding to a given jira STATUS."
+  (if org-jira-use-status-as-todo
+      (upcase (replace-regexp-in-string " " "-" status))
+    (let ((known-keyword (assoc status org-jira-jira-status-to-org-keyword-alist)))
+      (cond (known-keyword (cdr known-keyword))
+            ((member (org-jira-decode status) org-jira-done-states) "DONE")
+            ("TODO")))))
+
+(defun org-jira-get-org-priority-string (character)
+  "Return an org-priority-string based on CHARACTER and user settings."
+  (cond ((not character) "")
+        ((and org-jira-priority-to-org-priority-omit-default-priority
+              (eq character org-default-priority)) "")
+        (t (format "[#%c] " character))))
+
+(defun org-jira-get-org-priority-cookie-from-issue (priority)
+  "Get the `org-mode' [#X] PRIORITY cookie."
+  (let ((character (cdr (assoc priority org-jira-priority-to-org-priority-alist))))
+    (org-jira-get-org-priority-string character)))
+
+(provide 'org-jira)
+;;; org-jira.el ends here