-
Notifications
You must be signed in to change notification settings - Fork 6
/
api.clj
296 lines (253 loc) · 10.7 KB
/
api.clj
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
(ns rally.api
(:require [cheshire.core :as json]
[clj-http.client :as client]
[clj-http.conn-mgr :as conn-mgr]
[clj-http.cookies :as cookies]
[clojure.pprint :refer [pprint]]
[clojure.string :refer [lower-case]]
[environ.core :as env]
[rally.api.data :as data]
[rally.api.request :as request]
[slingshot.slingshot :refer [throw+]])
(:refer-clojure :exclude [find]))
(def ^:dynamic *current-user*)
(def ^:dynamic *debug* false)
(defn- debug? [request]
(or *debug*
(= (lower-case (str (:debug request))) "true")
(= (lower-case (str (env/env :debug-rally-rest))) "true")
false))
(defn- not-found? [error]
(= error "Cannot find object to read"))
(defn- valid-rest-api? [rest-api]
(not (nil? (get-in rest-api [:request :cookie-store]))))
(defn- check-for-rally-errors [api response]
(let [errors (:errors response)]
(cond
(empty? errors) response
(:disable-throw-on-error? api) {:object response}
(some not-found? errors) nil
:else (throw+ response "rally-errors: %s" errors))))
(defn parse-response-body [response debug]
(try
(update response :body #(if (nil? %) % (json/parse-string % true)))
(catch Exception x
(debug (.printStackTrace x))
(merge response {:body {}
:parse-exception x}))))
(defn do-request [{:keys [request] :as api}]
(let [debug (debug? request)
request (assoc request :debug debug)]
(when debug (pprint {:api (dissoc api :request)
:request request}))
(let [response (client/request request)
_ (when debug (pprint {:response response}))
response (parse-response-body response debug)
_ (when debug (pprint {:json (:body response)}))]
(if (string? (:body response))
(check-for-rally-errors api response)
(->> response
:body
data/->clojure-map
vals
first
(check-for-rally-errors api))))))
(defn create!
([type] (create! *current-user* type {}))
([api-or-type type-or-data]
(if (keyword? api-or-type)
(create! *current-user* api-or-type type-or-data)
(create! api-or-type type-or-data {})))
([api-or-type type-or-data data-or-query-params]
(if (keyword? api-or-type)
(create! *current-user* api-or-type type-or-data data-or-query-params)
(create! api-or-type type-or-data data-or-query-params {})))
([rest-api type data query-params]
{:pre [(valid-rest-api? rest-api)]}
(let [default-data-fn (request/get-default-data-fn rest-api)]
(-> rest-api
(request/set-method :put)
(request/set-uri type "create")
(request/set-body-as-map type (default-data-fn type data))
(request/merge-query-params query-params)
do-request
:object))))
(defn copy!
([ref-or-object]
(copy! *current-user* ref-or-object))
([rest-api ref-or-object]
{:pre [(valid-rest-api? rest-api)]}
(let [ref (data/->ref ref-or-object)
type (or (:metadata/type ref-or-object) (data/rally-ref->clojure-type ref))]
(-> rest-api
(request/set-method :post)
(request/set-uri ref "copy")
(request/set-body-as-map type {})
do-request
:object))))
(defn update!
([ref-or-object updated-data]
(update! *current-user* ref-or-object updated-data))
([api-or-ref-or-object ref-or-object-or-updated-data updated-data-or-query-params]
(if (valid-rest-api? api-or-ref-or-object)
(update! api-or-ref-or-object ref-or-object-or-updated-data updated-data-or-query-params {})
(update! *current-user* api-or-ref-or-object ref-or-object-or-updated-data updated-data-or-query-params)))
([rest-api ref-or-object updated-data query-params]
{:pre [(valid-rest-api? rest-api)]}
(let [ref (data/->ref ref-or-object)
type (or (:metadata/type ref-or-object) (data/rally-ref->clojure-type ref))]
(-> rest-api
(request/set-method :post)
(request/set-url ref)
(request/set-body-as-map type updated-data)
(request/merge-query-params query-params)
do-request
:object))))
(defn update-collection!
([collection-ref-or-object action items]
(update-collection! *current-user* collection-ref-or-object action items))
([rest-api collection-ref-or-object action items]
{:pre [(valid-rest-api? rest-api)]}
(let [ref (str (data/->ref collection-ref-or-object) "/" (name action))
items (map #(hash-map :metadata/ref (data/->ref %)) items)]
(-> rest-api
(request/set-method :post)
(request/set-url ref)
(request/set-body-as-map :collection-items items)
do-request))))
(defn delete!
([ref-or-object]
(delete! *current-user* ref-or-object))
([rest-api ref-or-object]
{:pre [(valid-rest-api? rest-api)]}
(-> rest-api
(request/set-method :delete)
(request/set-url ref-or-object)
do-request)))
(defn- query-for-page [rest-api uri start pagesize query-spec]
(-> rest-api
(request/set-uri uri)
(request/merge-query-params query-spec)
(request/set-query-param :start start)
(request/set-query-param :pagesize pagesize)
do-request))
(defn query
([uri]
(query *current-user* uri {}))
([api-or-uri uri-or-spec]
(if (valid-rest-api? api-or-uri)
(query api-or-uri uri-or-spec {})
(query *current-user* api-or-uri uri-or-spec)))
([rest-api uri query-spec]
{:pre [(valid-rest-api? rest-api)]}
(let [query-spec (if (vector? query-spec) {:query query-spec} query-spec)
start (or (:start query-spec) 1)
pagesize (or (:pagesize query-spec) 200)
next-start (+ start pagesize)
page (query-for-page rest-api uri start pagesize query-spec)
total-result-count (:total-result-count page)]
(concat (:results page)
(when (<= next-start total-result-count)
(lazy-seq (query rest-api uri (assoc query-spec :start next-start))))))))
(defn find
([ref-or-object]
(find *current-user* ref-or-object))
([api-ref-or-object ref-or-object-or-query-spec]
(if (valid-rest-api? api-ref-or-object)
(-> api-ref-or-object
(request/set-url ref-or-object-or-query-spec)
do-request)
(find *current-user* api-ref-or-object ref-or-object-or-query-spec)))
([rest-api uri query-spec]
{:pre [(valid-rest-api? rest-api)]}
(-> (query rest-api uri query-spec)
first)))
(defn find-by-formatted-id
([rally-type formatted-id]
(find-by-formatted-id *current-user* rally-type formatted-id))
([rest-api rally-type formatted-id]
{:pre [(valid-rest-api? rest-api)]}
(let [query-spec {:query [:= :formatted-id formatted-id]
:fetch true}]
;; We use a query and then search because if you look for formatted-id
;; in the artifact endpoint it will return all artifacts with the number
;; part of the formatted it. So searching for US11 will also return DE11,T11, and so on.
(->> (query rest-api rally-type query-spec)
(some #(when (= formatted-id (:formatted-id %)) %))))))
(defn find-by-id
([type id]
(find-by-id *current-user* type id))
([rest-api type id]
{:pre [(valid-rest-api? rest-api)]}
(-> rest-api
(request/set-uri type id)
do-request)))
(defn current-workspace
([] (current-workspace *current-user*))
([rest-api]
{:pre [(valid-rest-api? rest-api)]}
(let [current-project (find rest-api (request/get-current-project rest-api))]
(find rest-api (:workspace current-project)))))
(defn current-project
([] (current-project *current-user*))
([rest-api]
{:pre [(valid-rest-api? rest-api)]}
(find rest-api :project {:fetch true})))
(defn current-user
([] (current-user *current-user*))
([rest-api]
{:pre [(valid-rest-api? rest-api)]}
(-> rest-api
(request/set-uri (keyword "user:current"))
do-request)))
(defn- security-token [rest-api {:keys [username password]}]
(-> rest-api
(request/set-basic-auth username password)
(request/set-uri :security :authorize)
do-request
:security-token))
(defn create-basic-rest-api
"Create a rest-api, but do not make any calls to the server."
[{:keys [api-key] :as credentials} rally-host conn-props]
(let [connection-manager (conn-mgr/make-reusable-conn-manager conn-props)
rest-api {:request {:connection-manager connection-manager
; avoid apache.http cookies warnings by using a standards compliant policy
:cookie-policy :standard ; RFC 6265 (interoprability profile)
:cookie-store (cookies/cookie-store)
:headers {"X-RallyIntegrationOS" (env/env "os.name")
"X-RallyIntegrationPlatform" (env/env "java.version")
"X-RallyIntegrationLibrary" "RallyRestAPIForClojure"}
:method :get}
:rally {:host rally-host
:version :v2.0}}]
(if api-key
(request/add-headers rest-api {:zsessionid api-key})
rest-api)))
(defn init-rest-api
"Sets the current project, and the the security token (if applicable)."
[rest-api {:keys [api-key] :as credentials}]
(let [rest-api (if api-key
rest-api
(request/set-security-token rest-api (security-token rest-api credentials)))
current-project (find rest-api :project {})]
(request/set-current-project rest-api current-project)))
(defn create-rest-api
"Create and initialize a rest-api."
([]
(let [username (env/env :username)
password (env/env :password)
api-key (env/env :api-key)]
(create-rest-api {:username username
:password password
:api-key api-key})))
([credentials]
(let [rally-host (or (env/env :rally-host) "https://rally1.rallydev.com")]
(create-rest-api credentials rally-host)))
([credentials rally-host]
(create-rest-api credentials rally-host {}))
([{:keys [api-key] :as credentials} rally-host conn-props]
(let [rest-api (create-basic-rest-api credentials rally-host conn-props)]
(init-rest-api rest-api credentials))))
(defn shutdown-rest-api [rest-api]
(conn-mgr/shutdown-manager (get-in rest-api [:request :connection-manager]))
(assoc-in rest-api [:request :connection-manager] nil))