-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjira-issues.el
222 lines (195 loc) · 8.75 KB
/
jira-issues.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
;;; jira-issues.el --- Jira Issues -*- lexical-binding: t -*-
;; Copyright (C) 2025 Pablo González Carrizo
;; Author: Pablo González Carrizo <[email protected]>
;; Created: 2025-02-16
;; 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, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; List and manage Jira issues
;;; Code:
(require 'tablist)
(require 'transient)
(require 'jira-api)
(require 'jira-table)
(require 'jira-fmt)
(require 'jira-utils)
(require 'jira-tempo)
(require 'jira-actions)
(require 'jira-detail)
;; TODO: It would be nice to make it a defcustom but, currently, we
;; need it to always show the key at the beginning and the summary at
;; the end of the list, so we can extract it when we create a worklog
(defvar jira-issues-table-fields
'(:key :issue-type-name :status-name :assignee-name
:progress-percent :work-ratio :remaining-time :summary))
(defcustom jira-issues-max-results 50
"Maximum number of Jira issues to retrieve."
:group 'jira :type 'integer)
(defun jira-issues--api-get-issues (jql callback)
"Retrieve issues from the given JQL filter and call CALLBACK function."
(let* ((parent (lambda (fd) (jira-table-field-parent jira-issues-fields fd)))
(fields (mapcar parent jira-issues-table-fields)))
(when jira-debug
(message (concat "Get issues with jql "jql)))
(jira-api-call
"GET" "search"
:params `(("jql" . ,jql)
("maxResults" . ,jira-issues-max-results)
("startAt" . 0)
("fields" . ,(mapconcat (lambda (x) (format "%s" x))
(cl-remove nil fields) ",")))
:callback callback)))
(defun jira-issues--api-filters-and (filters)
"Concat all FILTERS with AND."
(mapconcat (lambda (filter) filter) (remove nil filters) " AND "))
(defun jira-issues--data-format-cell (issue field)
"Format the given FIELD from the given ISSUE."
(let* ((extracted (jira-table-extract-field jira-issues-fields field issue))
(value (format "%s" (or extracted "")))
(formatter (jira-table-field-formatter jira-issues-fields field)))
(if formatter (funcall formatter value) value)))
(defun jira-issues--data-format-issue (issue)
"Format the given ISSUE."
(let ((key (jira-table-extract-field jira-issues-fields :key issue))
(formatter (lambda (fd) (jira-issues--data-format-cell issue fd))))
(list key (vconcat (mapcar formatter jira-issues-table-fields)))))
(defun jira-issues--refresh-table (data _response)
"Refresh the table with the given RESPONSE DATA."
(let* ((data-alist (json-read-from-string (json-encode data)))
(issues (alist-get 'issues data-alist))
(entries (mapcar #'jira-issues--data-format-issue issues)))
(setq tabulated-list-entries entries)
(tabulated-list-print t)))
(defun jira-issues--refresh ()
"Refresh the table."
(let* ((args (transient-args 'jira-issues-menu))
(myself (transient-arg-value "--myself" args))
(current-sprint (transient-arg-value "--current-sprint" args))
(jql (transient-arg-value "--jql=" args))
(status (transient-arg-value "--status=" args))
(project (transient-arg-value "--project=" args))
(resolution (transient-arg-value "--resolution=" args))
(version (transient-arg-value "--version=" args)))
(jira-issues--api-get-issues
(or jql
(jira-issues--api-filters-and
(list (when myself "assignee = currentUser()")
(when current-sprint "sprint in openSprints()")
(when status (concat "status = \"" status "\""))
(when project (concat "project = \"" project "\""))
(when resolution (concat "resolution = \"" resolution "\""))
(when version (concat "fixversion = \"" version "\"")))))
#'jira-issues--refresh-table)))
(defun jira-issues--filter-invalid-if-jql ()
"Return t if JQL filter is set."
(if transient-current-command
(transient-arg-value "--jql=" (transient-args transient-current-command))))
(transient-define-prefix jira-issues-menu ()
"Show menu for listing Jira Issues."
:refresh-suffixes t
:value '("--myself" "--current-sprint")
[["Arguments"
("m" "Just from myself" "--myself"
:transient t
:inapt-if jira-issues--filter-invalid-if-jql)
("c" "Just from current sprint" "--current-sprint"
:transient t
:inapt-if jira-issues--filter-invalid-if-jql)
("s" "Status" "--status="
:transient t
:inapt-if jira-issues--filter-invalid-if-jql
:choices
(lambda () (mapcar (lambda (st) (car st)) jira-statuses)))
("p" "Project" "--project="
:transient t
:inapt-if jira-issues--filter-invalid-if-jql
:choices
(lambda () (mapcar (lambda (prj) (car prj)) jira-projects)))
("r" "Resolution" "--resolution="
:transient t
:inapt-if jira-issues--filter-invalid-if-jql
:choices
(lambda () (mapcar (lambda (res) (car res)) jira-resolutions)))
("j""JQL filter" "--jql="
:transient transient--do-call)
("v" "Fix Versions" "--version="
:transient t
:inapt-if jira-issues--filter-invalid-if-jql
:choices
(lambda () (apply #'append (mapcar #'cdr jira-projects-versions))))]
["Arguments Help"
("C-x" :info (concat (propertize "Check " 'face 'italic)
"additional options"))
("C-x s" :info (concat (propertize "Set" 'face 'italic)
" current arguments for Emacs session"))
("C-x C-s" :info (concat (propertize "Persist" 'face 'italic)
" current arguments for all Emacs sessions"))
("C-x C-k" :info (concat (propertize "Reset" 'face 'italic)
" arguments to default ones"))]]
["Actions"
("l" "List Jira Issues" tablist-revert)])
(transient-define-prefix jira-issues-actions-menu ()
"Show menu for actions on Jira Issues."
[[:description "Jira Issues List"
("?" "Show this menu" jira-issues-actions-menu)
("g" "Refresh list" tablist-revert)
("l" "List Jira Issues menu" jira-issues-menu)]]
[[:description
(lambda () (jira-utils-transient-description "Actions on issue"))
:inapt-if-not jira-utils-marked-item
("C" "Change issue" jira-actions-change-issue-menu)
("I" "Show issue information"
(lambda () (interactive) (jira-detail-show-issue (jira-utils-marked-item))))
("O" "Open issue in browser"
(lambda () (interactive) (jira-actions-open-issue (jira-utils-marked-item))))
("W" "Add worklog to issue" jira-actions-add-worklog-menu)]])
(defvar jira-issues-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "?" 'jira-issues-actions-menu)
(define-key map "l" 'jira-issues-menu)
(define-key map "C" 'jira-actions-change-issue-menu)
(define-key map "I" (lambda () (interactive)
(jira-detail-show-issue (jira-utils-marked-item))))
(define-key map "O" (lambda () (interactive)
(jira-actions-open-issue (jira-utils-marked-item))))
(define-key map "W" 'jira-actions-add-worklog-menu)
map)
"Keymap for `jira-issues-mode'.")
;;;###autoload (autoload 'jira-issues "jira-issues" nil t)
(defun jira-issues ()
"List Jira issues."
(interactive)
(pop-to-buffer "*Jira Issues*")
(delete-other-windows)
(jira-issues-mode)
(jira-api-get-basic-data)
(tablist-revert))
(define-derived-mode jira-issues-mode tabulated-list-mode "Jira Issues"
"Major mode for listing Jira issues."
:interactive nil
(let* ((name (lambda (fd) (jira-table-field-name jira-issues-fields fd)))
(columns (lambda (fd) (jira-table-field-columns jira-issues-fields fd)))
(col-info (lambda (fd) (list (funcall name fd) (funcall columns fd) t))))
(setq tabulated-list-format
(vconcat (mapcar col-info jira-issues-table-fields))))
(setq tabulated-list-sort-key (cons "Status" nil))
(setq tabulated-list-padding 2)
(add-hook 'tabulated-list-revert-hook #'jira-issues--refresh nil t)
(add-hook 'jira-issues-changed-hook #'tablist-revert)
(tabulated-list-init-header)
(tablist-minor-mode))
(provide 'jira-issues)
;;; jira-issues.el ends here