-
Notifications
You must be signed in to change notification settings - Fork 0
/
fogbugz-api.rkt
156 lines (126 loc) · 5.05 KB
/
fogbugz-api.rkt
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
#lang racket
(require "structs.rkt"
(prefix-in time: "time.rkt")
net/url
srfi/19
;(planet bzlib/xml:1:1)
(only-in "compatibility.rkt" read-sxml)
(planet lizorkin/sxml:2:1/sxml))
;;; Customization Parameters
;;; ========================
(provide base-url)
(define base-url (make-parameter "http://fogbugz.phosphor.co.nz/api.asp"))
;;; API Calls
;;; =========
;;;
;;; all normal calls to the api should go through fb-command
;;; which uses the requires an auth-token.
;;;
;;; fb-command* is a little more raw, and isn't logon aware at all
(define (fb-command* params)
(if (not (base-url))
(error "base-url not set")
(let* ([params* (remove (lambda (p)
(eq? (cdr p) #f))
params)]
[response (call/input-url (struct-copy url (string->url (base-url))
[query params*])
get-pure-port
read-sxml)])
(cond [(error-response? response) => (lambda (err)
(raise err #t))]
[else response]))))
(define (fb-command token name [params '()])
(fb-command* (list* `[cmd . ,name]
`[token . ,token]
params)))
(define (error-response? response)
(let ([err ((sxpath "/response/error") response)])
(and (not (null? err))
(make-exn:fogbugz-error (first ((sxpath "text()") err))
(current-continuation-marks)
(string->number (first ((sxpath "@code/text()") err)))))))
;;; Logging On and Off
;;; ==================
(provide logon logoff)
(define (logon email pw)
(let* ([response (fb-command* `([cmd . "logon"]
[email . ,email]
[password . ,pw]))]
[tokens ((sxpath "/response/token/text()") response)])
(and (not (null? tokens))
(first tokens))))
(define (logoff token)
(fb-command token "logoff"))
;;; Searching and Listing Cases
;;; ===========================
(provide list-cases search list-filters set-current-filter)
(define default-columns '("ixBug" "sTitle" "hrsCurrEst" "sProject" "ixStatus"))
(define (list-cases token)
(set-current-filter token my-cases)
(map case-xml->dict
((sxpath "/response/cases/case")
(fb-command token "search" `([cols . ,(string-join default-columns ",")])))))
(define (search token text
#:max [max #f]
#:columns [columns default-columns])
(let ([response (fb-command token "search"
`([q . ,text]
[max . ,max]
[cols . ,(and columns (string-join columns ","))]))])
(map case-xml->dict
((sxpath "/response/cases/case") response))))
(define (list-filters token)
(map (lambda (n)
(cons (first ((sxpath "@sfilter/text()") n))
((sxpath "text()") n)))
((sxpath "/response/filters/filter")
(fb-command token "listFilters"))))
(define (set-current-filter token id)
;; id *must* be an sFilter attribute returned by list-filters
(fb-command token
"setCurrentFilter"
`([sFilter . ,id])))
(define my-cases "ez")
;;; Time Tracking
;;; =============
(provide start-work stop-work working-on list-intervals new-interval set-estimate resolve-bug close-bug quick-interval)
(define (start-work token case)
(fb-command token "startWork" `([ixBug . ,case])))
(define (stop-work token)
(fb-command token "stopWork"))
(define (list-intervals token
#:person [person #f]
#:bug [bug #f]
#:start [start #f]
#:end [end #f])
(fb-command token "listIntervals"
`([ixPerson . ,person]
[ixBug . ,bug]
[dtStart . ,(and start (date->string start))]
[dtEnd . ,(and end (date->string end))]
[cols . ,(string-join default-columns ",")])))
(define (new-interval token bug start stop)
(fb-command token "newInterval"
`([ixBug . ,bug]
[dtStart . ,(time:time-stamp start)]
[dtEnd . ,(time:time-stamp stop)])))
(define (set-estimate token bug n)
(fb-command token "edit"
`([ixBug . ,bug]
[hrsCurrEst . ,(number->string n)])))
(define (close-bug token bug)
(fb-command token "close" `([ixBug . ,bug])))
(define (resolve-bug token bug)
(fb-command token "resolve"
`([ixBug . ,bug]
[ixStatus . "2"])))
(define (quick-interval token bug minutes)
(define-values (start end)
(time:quick-interval minutes))
(new-interval token bug start end))
(define (working-on token)
(let ([current ((sxpath "//interval [dtend = '']")
(list-intervals token))])
(and (not (null? current))
(first (search token (case-id (case-xml->dict current)))))))