Browse code
misc: changes
Ed Langley authored on 31/07/2019 18:39:08
Showing 6 changed files
Showing 6 changed files
... | ... |
@@ -3,19 +3,51 @@ |
3 | 3 |
; curl -u user:password https://atomampd.atlassian.net/rest/api/2/issue/ATOMOS-212 | jq . |
4 | 4 |
(defparameter *hostname* "https://atomampd.atlassian.net") |
5 | 5 |
(defparameter *endpoint* (princ-to-string (puri:merge-uris *hostname* "/rest/api/2/"))) |
6 |
+(defparameter *agile-endpoint* (princ-to-string (puri:merge-uris *hostname* "/rest/agile/1.0/"))) |
|
6 | 7 |
|
7 |
-(defun update-hostname (new-subdomain) |
|
8 |
- (setf *hostname* (format nil "https://~a.atlassian.net" new-subdomain)) |
|
9 |
- (setf *endpoint* (princ-to-string (puri:merge-uris "/rest/api/2/" *hostname*)))) |
|
8 |
+(defun update-hostname () |
|
9 |
+ (setf *hostname* "https://jira.cnvrmedia.net") |
|
10 |
+ (setf *endpoint* (princ-to-string (puri:merge-uris "/rest/api/2/" *hostname*))) |
|
11 |
+ (setf *agile-endpoint* (princ-to-string (puri:merge-uris *hostname* "/rest/agile/1.0/")))) |
|
12 |
+ |
|
13 |
+(define-condition jira-error () |
|
14 |
+ ()) |
|
15 |
+ |
|
16 |
+(define-condition auth-call-unauthorized (jira-error) |
|
17 |
+ ()) |
|
18 |
+ |
|
19 |
+(define-condition server-error () |
|
20 |
+ ()) |
|
10 | 21 |
|
11 | 22 |
(defun api-get-call (auth method &rest parameters) |
12 | 23 |
"Connect to a GET REST endpoint specified by method and return a stream from |
13 | 24 |
which the response can be read." |
14 | 25 |
(let ((drakma:*text-content-types* (acons "application" "json" drakma:*text-content-types*))) |
15 |
- (drakma:http-request (puri:merge-uris method *endpoint*) |
|
16 |
- :parameters (alexandria:plist-alist parameters) |
|
17 |
- :basic-authorization auth |
|
18 |
- :want-stream t))) |
|
26 |
+ (format t "~&~a ~s~%" (puri:merge-uris method *endpoint*) (alexandria:plist-alist parameters)) |
|
27 |
+ (multiple-value-bind (stream retcode) |
|
28 |
+ (drakma:http-request (puri:merge-uris method *endpoint*) |
|
29 |
+ :parameters (alexandria:plist-alist parameters) |
|
30 |
+ :basic-authorization auth |
|
31 |
+ :want-stream t) |
|
32 |
+ (case retcode |
|
33 |
+ (401 (error 'auth-call-unauthorized)) |
|
34 |
+ (500 (error 'server-error)) |
|
35 |
+ (t stream))))) |
|
36 |
+ |
|
37 |
+(defun agile-get-call (auth method &rest parameters) |
|
38 |
+ "Connect to a GET REST endpoint specified by method and return a stream from |
|
39 |
+ which the response can be read." |
|
40 |
+ (let ((drakma:*text-content-types* (acons "application" "json" drakma:*text-content-types*))) |
|
41 |
+ (format t "~&~a ~s~%" (puri:merge-uris method *agile-endpoint*) (alexandria:plist-alist parameters)) |
|
42 |
+ (multiple-value-bind (stream retcode) |
|
43 |
+ (drakma:http-request (puri:merge-uris method *agile-endpoint*) |
|
44 |
+ :parameters (alexandria:plist-alist parameters) |
|
45 |
+ :basic-authorization auth |
|
46 |
+ :want-stream t) |
|
47 |
+ (case retcode |
|
48 |
+ (401 (error 'auth-call-unauthorized)) |
|
49 |
+ (500 (error 'server-error)) |
|
50 |
+ (t stream))))) |
|
19 | 51 |
|
20 | 52 |
(defun api-post-call (auth method post-data) |
21 | 53 |
"Connect to a GET REST endpoint specified by method and return a stream from |
... | ... |
@@ -1,13 +1,31 @@ |
1 | 1 |
;;;; jira-api.lisp |
2 | 2 |
(in-package #:jira-api) |
3 | 3 |
|
4 |
-(defun get-issues (auth) |
|
5 |
- (api-get-call auth "search")) |
|
4 |
+(defun get-filter (auth filter &rest r &key (expand "subscriptions[:-5]")) |
|
5 |
+ (declare (ignore expand)) |
|
6 |
+ (apply 'api-get-call auth (format nil "filter/~d" filter) r)) |
|
7 |
+ |
|
8 |
+(defun get-issues (auth &key jql) |
|
9 |
+ (if jql |
|
10 |
+ (api-get-call auth "search" "jql" jql) |
|
11 |
+ (api-get-call auth "search"))) |
|
12 |
+ |
|
13 |
+(defmacro jql (auth &body jql) |
|
14 |
+ `(api-get-call ,auth "search" "jql" |
|
15 |
+ ,(apply #'serapeum:concat jql))) |
|
16 |
+ |
|
17 |
+(defun run-filter (auth filter) |
|
18 |
+ (let ((jql (gethash "jql" (yason:parse (get-filter auth filter))))) |
|
19 |
+ (get-issues auth :jql jql))) |
|
6 | 20 |
|
7 | 21 |
(defun get-issue (auth key &rest params &key expand) |
8 | 22 |
(declare (ignore expand)) |
9 | 23 |
(apply 'api-get-call auth (format nil "issue/~a" key) params)) |
10 | 24 |
|
25 |
+(defun get-board-issues (auth key &rest params &key expand) |
|
26 |
+ (declare (ignore expand)) |
|
27 |
+ (apply 'api-get-call auth (format nil "board/~a/issue" key) params)) |
|
28 |
+ |
|
11 | 29 |
(defun get-projects (auth) |
12 | 30 |
(api-get-call auth "issue/createmeta")) |
13 | 31 |
|
... | ... |
@@ -71,20 +89,41 @@ |
71 | 89 |
finally (push issue (gethash thing classes))) |
72 | 90 |
finally (return classes)))) |
73 | 91 |
|
74 |
-(defun show-issue-short (issue) |
|
92 |
+(defun status-to-num (status) |
|
93 |
+ (string-case (string-downcase status) |
|
94 |
+ ("open" 0) |
|
95 |
+ ("in progress" 1) |
|
96 |
+ ("needs qr" 2) |
|
97 |
+ ("needs demo" 3) |
|
98 |
+ ("closed" 4) |
|
99 |
+ (t 5))) |
|
100 |
+ |
|
101 |
+(defun sort-issues-by-status (sheeple-issues) |
|
102 |
+ (prog1 sheeple-issues |
|
103 |
+ (sheeple:with-properties (issues) sheeple-issues |
|
104 |
+ (setf issues |
|
105 |
+ (sort issues #'< :key (op (status-to-num (name (status (fields _)))))))))) |
|
106 |
+ |
|
107 |
+(defun show-issue-short (issue &optional (stream t)) |
|
75 | 108 |
(sheeple:with-properties (key self) issue |
76 | 109 |
(let* ((fields (fields issue)) |
77 | 110 |
(status (name (status fields))) |
78 | 111 |
(summary (summary fields))) |
79 | 112 |
|
80 |
- (format t "~&~a (~a) <~a>~%" |
|
113 |
+ (format stream "~&~a (~a) <~a>~%" |
|
81 | 114 |
key |
82 | 115 |
status |
83 | 116 |
(puri:merge-uris (format nil "/browse/~a" (key issue)) |
84 | 117 |
*hostname*)) |
85 | 118 |
|
86 |
- (show-summary summary) |
|
87 |
- (fresh-line)))) |
|
119 |
+ (show-summary summary stream) |
|
120 |
+ (fresh-line stream)))) |
|
121 |
+ |
|
122 |
+(defun map-issues (fun sheeple-issues) |
|
123 |
+ (sheeple:with-properties (issues) sheeple-issues |
|
124 |
+ (loop for issue across issues |
|
125 |
+ do (ensure-parent issue =issue=) |
|
126 |
+ collect (funcall fun issue)))) |
|
88 | 127 |
|
89 | 128 |
(defun show-issues (sheeple-issues) |
90 | 129 |
(sheeple:with-properties (issues) sheeple-issues |
... | ... |
@@ -100,3 +139,17 @@ |
100 | 139 |
(loop for project across projects |
101 | 140 |
do (ensure-parent project =project=) |
102 | 141 |
do (print-on-own-line (show project))))) |
142 |
+ |
|
143 |
+(defun get-points-for-issues (count start) |
|
144 |
+ (loop with end = (+ count start) |
|
145 |
+ for batch-start from start to end by 50 |
|
146 |
+ for batch-count = (min (- end batch-start) |
|
147 |
+ 50) |
|
148 |
+ append (map 'list #'points |
|
149 |
+ (issues |
|
150 |
+ (json2sheeple |
|
151 |
+ (get-issues *auth* :jql |
|
152 |
+ (format nil "issueKey in (~{CJPM-~a~^, ~})" |
|
153 |
+ (alexandria:iota batch-count |
|
154 |
+ :start batch-start))) |
|
155 |
+ =issues=))))) |
... | ... |
@@ -1,13 +1,19 @@ |
1 | 1 |
;;;; package.lisp |
2 | 2 |
|
3 | 3 |
(defpackage #:jira-api.cli |
4 |
- (:use :cl :anaphora :alexandria :serapeum :fw.lu) |
|
5 |
- (:export #:prompt-for-lines |
|
4 |
+ (:use :cl #:anaphora #:alexandria #:serapeum #:fw.lu) |
|
5 |
+ (:export #:optional-prompt |
|
6 |
+ #:prompt |
|
6 | 7 |
#:prompt-for-line |
7 |
- #:optional-prompt |
|
8 |
- #:prompt)) |
|
8 |
+ #:prompt-for-lines)) |
|
9 | 9 |
|
10 | 10 |
(defpackage #:jira-api |
11 | 11 |
(:shadow #:comment) |
12 |
- (:use #:cl #:serapeum #:alexandria #:fw.lu #:jira-api.cli)) |
|
12 |
+ (:use #:cl #:jira-api.cli #:alexandria #:fw.lu #:serapeum) |
|
13 |
+ (:export #:auth-call-unauthorized |
|
14 |
+ #:classify-issues |
|
15 |
+ #:get-issues |
|
16 |
+ #:jql |
|
17 |
+ #:jira-error |
|
18 |
+ #:server-error)) |
|
13 | 19 |
|
... | ... |
@@ -6,7 +6,7 @@ |
6 | 6 |
(apply #'format stream prompt args) |
7 | 7 |
(force-output stream) |
8 | 8 |
(read-line stream nil :done)) |
9 |
-;(trace prompt-for-line) |
|
9 |
+;;(trace prompt-for-line) |
|
10 | 10 |
|
11 | 11 |
(defun prompt-for-lines (stream initial-prompt continuation-prompt &rest args) |
12 | 12 |
"Not finished ... see :description below" |
... | ... |
@@ -13,6 +13,7 @@ |
13 | 13 |
(sheeple:defproto =person= () (displayname emailaddress)) |
14 | 14 |
(sheeple:defproto =issue= () (fields key id self)) |
15 | 15 |
(sheeple:defproto =fields= () (summary description reporter creator assignee status comment)) |
16 |
+(sheeple:defproto =issues= () (issues)) |
|
16 | 17 |
(sheeple:defmessage show (object &rest args)) |
17 | 18 |
(sheeple:defmessage fields-labels (fields)) |
18 | 19 |
(sheeple:defreply fields-labels ((fields =fields=)) |
... | ... |
@@ -21,6 +22,15 @@ |
21 | 22 |
|
22 | 23 |
(sheeple:defproto =comment= () (self id author body)) |
23 | 24 |
|
25 |
+(sheeple:defmessage points (issue)) |
|
26 |
+(sheeple:defreply points ((issue =issue=)) |
|
27 |
+ (sheeple:property-value (fields issue) |
|
28 |
+ 'customfield_10002)) |
|
29 |
+ |
|
30 |
+(sheeple:defreply sheeple:shared-init ((issues =issues=) &key) |
|
31 |
+ (map nil (op (ensure-parent _ =issue=)) |
|
32 |
+ (issues issues))) |
|
33 |
+ |
|
24 | 34 |
(sheeple:defreply sheeple:shared-init :after ((comment =comment=) &key) |
25 | 35 |
(with-accessors ((author author)) comment |
26 | 36 |
(ensure-parent author =person= :err-if-nil nil))) |
... | ... |
@@ -30,13 +40,14 @@ |
30 | 40 |
(when fields |
31 | 41 |
(ensure-parent fields =fields=) |
32 | 42 |
(ensure-parent (status fields) =status=) |
33 |
- (ensure-parent (reporter fields) =person=) |
|
34 |
- (ensure-parent (creator fields) =person=) |
|
43 |
+ (ensure-parent (reporter fields) =person= :err-if-nil nil) |
|
44 |
+ (ensure-parent (creator fields) =person= :err-if-nil nil) |
|
35 | 45 |
(ensure-parent (assignee fields) =person= :err-if-nil nil) |
36 | 46 |
(sheeple:with-properties (comment) fields |
37 |
- (sheeple:with-properties (comments) comment |
|
38 |
- (map 'nil (lambda (comment) (ensure-parent comment =comment=)) |
|
39 |
- comments)))))) |
|
47 |
+ (when comment |
|
48 |
+ (sheeple:with-properties (comments) comment |
|
49 |
+ (map 'nil (lambda (comment) (ensure-parent comment =comment=)) |
|
50 |
+ comments))))))) |
|
40 | 51 |
|
41 | 52 |
(sheeple:defreply show ((person =person=) &rest args) |
42 | 53 |
(declare (ignore args)) |
... | ... |
@@ -63,16 +74,16 @@ |
63 | 74 |
(pprint-newline :fill *standard-output*))) |
64 | 75 |
(pprint-newline :mandatory *standard-output*))))) |
65 | 76 |
|
66 |
-(defun show-summary (summary) |
|
67 |
- (pprint-logical-block (*standard-output* (funcall (compose 'tokens 'trim-whitespace) summary)) |
|
77 |
+(defun show-summary (summary &optional (stream *standard-output*)) |
|
78 |
+ (pprint-logical-block (stream (funcall (compose 'tokens 'trim-whitespace) summary)) |
|
68 | 79 |
(pprint-indent :block 8 *standard-output*) |
69 | 80 |
(pprint-exit-if-list-exhausted) |
70 |
- (format *standard-output* "~4tSummary: ") |
|
81 |
+ (format stream "~4tSummary: ") |
|
71 | 82 |
(loop |
72 |
- (princ (pprint-pop)) |
|
83 |
+ (princ (pprint-pop) stream) |
|
73 | 84 |
(pprint-exit-if-list-exhausted) |
74 |
- (pprint-newline :fill *standard-output*) |
|
75 |
- (princ #\space)))) |
|
85 |
+ (pprint-newline :fill stream) |
|
86 |
+ (princ #\space stream)))) |
|
76 | 87 |
|
77 | 88 |
(sheeple:defreply show ((issue =issue=) &rest args) |
78 | 89 |
(declare (ignorable args)) |
... | ... |
@@ -149,20 +160,20 @@ |
149 | 160 |
|
150 | 161 |
(defun json2sheeple (json &optional parent) |
151 | 162 |
(labels |
152 |
- ((handle-parsed (parsed-json) |
|
153 |
- (typecase parsed-json |
|
154 |
- (vector-of-objects (map 'vector #'handle-parsed parsed-json)) |
|
155 |
- (hash-table |
|
156 |
- (let ((result (sheeple:object))) |
|
157 |
- (loop for json-prop being the hash-keys of parsed-json using (hash-value json-value) |
|
158 |
- do (setf (sheeple:property-value result |
|
159 |
- (intern (string-upcase json-prop) :jira-api)) |
|
160 |
- (typecase json-value |
|
161 |
- (hash-table (handle-parsed json-value)) |
|
162 |
- (vector-of-objects (map 'vector #'handle-parsed json-value)) |
|
163 |
- (t json-value))) |
|
164 |
- finally (return result)))) |
|
165 |
- (t parsed-json)))) |
|
163 |
+ ((handle-parsed (parsed-json) |
|
164 |
+ (typecase parsed-json |
|
165 |
+ (vector-of-objects (map 'vector #'handle-parsed parsed-json)) |
|
166 |
+ (hash-table |
|
167 |
+ (let ((result (sheeple:object))) |
|
168 |
+ (loop for json-prop being the hash-keys of parsed-json using (hash-value json-value) |
|
169 |
+ do (setf (sheeple:property-value result |
|
170 |
+ (intern (string-upcase json-prop) :jira-api)) |
|
171 |
+ (typecase json-value |
|
172 |
+ (hash-table (handle-parsed json-value)) |
|
173 |
+ (vector-of-objects (map 'vector #'handle-parsed json-value)) |
|
174 |
+ (t json-value))) |
|
175 |
+ finally (return result)))) |
|
176 |
+ (t parsed-json)))) |
|
166 | 177 |
(let* ((yason:*parse-json-arrays-as-vectors* t) |
167 | 178 |
(result (handle-parsed (yason:parse json)))) |
168 | 179 |
(when parent |