Merge lp:~gmb/launchpad/bugzilla-3.4-init-db-bug-416514 into lp:launchpad
- bugzilla-3.4-init-db-bug-416514
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~gmb/launchpad/bugzilla-3.4-init-db-bug-416514 |
Merge into: | lp:launchpad |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~gmb/launchpad/bugzilla-3.4-init-db-bug-416514 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abel Deuring (community) | code | Approve | |
Review via email: mp+10516@code.launchpad.net |
Commit message
Description of the change
Graham Binns (gmb) wrote : | # |
Abel Deuring (adeuring) wrote : | # |
Hi Graham,
nice branch. No complaints, but I found a few lines with trailing spaces.
Could you remove them?
r=me
Abel
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -111,3 +111,111 @@
> >>> bugzilla.
> CALLED Bugzilla.time()
> datetime.
> +
> +
> +Initializing the bug database
> +------
> +
> +BugzillaAPI implements IExternalBugTra
> +which takes a list of bug IDs to fetch from the remote server and stores
> +those bugs locally for future use.
> +
> + >>> bugzilla.
> + CALLED Bug.get({'ids': [1, 2], 'permissive': True})
> +
> +The bug data is stored as a list of dicts:
> +
> + >>> def print_bugs(bugs):
> + ... for bug in sorted(bugs):
> + ... print "Bug %s:" % bug
> + ... for key in sorted(
> + ... print " %s: %s" % (key, bugzilla.
> + ... print "\n"
> +
> + >>> print_bugs(
> + Bug 1:
> + alias:
trailing space.
> + assigned_to: <email address hidden>
> + component: GPPSystems
> + creation_time: 20080610T16:19:53
> + id: 1
> + internals:...
> + is_open: True
> + last_change_time: 20080610T16:19:53
> + priority: P1
> + product: Marvin
> + resolution: FIXED
> + severity: normal
> + status: RESOLVED
> + summary: That bloody robot still exists.
> + <BLANKLINE>
> + Bug 2:
> + alias: bug-two
> + assigned_to: <email address hidden>
> + component: Crew
> + creation_time: 20080611T09:23:12
> + id: 2
> + internals:...
> + is_open: True
> + last_change_time: 20080611T09:24:29
> + priority: P1
> + product: HeartOfGold
> + resolution:
trailing space.
> + severity: high
> + status: NEW
> + summary: Collect unknown persons in docking bay 2.
> + <BLANKLINE>
> + <BLANKLINE>
> +
> +
> +Storing bugs
> +------------
> +
> +initializeRemo
> +_storeBugs() will only store a bug once, even if it is requested both by
> +alias and ID. We'll reset the test BugzillaAPI's _bugs and _bug_aliases
> +dicts to demonstrate this.
> +
> + >>> bugzilla._bugs = {}
> + >>> bugzilla.
> + >>> bugzilla.
> + CALLED Bug.get({'ids': [2, 'bug-two'], 'permissive': True})
> +
> + >>> for bug in sorted(
> + ... print "Bug %r:" % bug
> + ... for key in sorted(
> + ... print " %s: %s" % (key, bugzilla.
> + ... print "\n"
> + Bug 2:
> + alias: bug-two
> + assigned_to: <email address hidden>
> + component: Crew
> + creation_time: 20080611T09:23:12
> + id: 2
> +...
Preview Diff
1 | === modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt' | |||
2 | --- lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt 2009-08-19 13:12:30 +0000 | |||
3 | +++ lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt 2009-08-21 09:32:57 +0000 | |||
4 | @@ -75,3 +75,147 @@ | |||
5 | 75 | ... | 75 | ... |
6 | 76 | BugTrackerAuthenticationError: http://thiswillfail.example.com: | 76 | BugTrackerAuthenticationError: http://thiswillfail.example.com: |
7 | 77 | Fault 300: The username or password you entered is not valid. | 77 | Fault 300: The username or password you entered is not valid. |
8 | 78 | |||
9 | 79 | |||
10 | 80 | Getting the server time | ||
11 | 81 | ----------------------- | ||
12 | 82 | |||
13 | 83 | To be able to accurately sync with a bug tracker, we need to be able to | ||
14 | 84 | check the time on the remote server. We use BugzillaAPI.getCurrentDBTime() | ||
15 | 85 | to get the current time on the remote server. | ||
16 | 86 | |||
17 | 87 | # There's no way to create a UTC timestamp without monkey-patching | ||
18 | 88 | # the TZ environment variable. Rather than do that, we create our | ||
19 | 89 | # own datetime and work with that. | ||
20 | 90 | >>> from datetime import datetime | ||
21 | 91 | >>> remote_time = datetime(2009, 8, 19, 17, 2, 2) | ||
22 | 92 | |||
23 | 93 | >>> test_transport.local_datetime = remote_time | ||
24 | 94 | >>> bugzilla.getCurrentDBTime() | ||
25 | 95 | CALLED Bugzilla.time() | ||
26 | 96 | datetime.datetime(2009, 8, 19, 17, 2, 2, tzinfo=<UTC>) | ||
27 | 97 | |||
28 | 98 | If the remote system is in a different timezone, getCurrentDBTime() will | ||
29 | 99 | convert its time to UTC before returning it. | ||
30 | 100 | |||
31 | 101 | >>> test_transport.utc_offset = 60**2 | ||
32 | 102 | >>> test_transport.timezone = 'CET' | ||
33 | 103 | >>> bugzilla.getCurrentDBTime() | ||
34 | 104 | CALLED Bugzilla.time() | ||
35 | 105 | datetime.datetime(2009, 8, 19, 16, 2, 2, tzinfo=<UTC>) | ||
36 | 106 | |||
37 | 107 | This works whether the UTC offset is positive or negative. | ||
38 | 108 | |||
39 | 109 | >>> test_transport.utc_offset = -5 * 60**2 | ||
40 | 110 | >>> test_transport.timezone = 'US/Eastern' | ||
41 | 111 | >>> bugzilla.getCurrentDBTime() | ||
42 | 112 | CALLED Bugzilla.time() | ||
43 | 113 | datetime.datetime(2009, 8, 19, 22, 2, 2, tzinfo=<UTC>) | ||
44 | 114 | |||
45 | 115 | |||
46 | 116 | Initializing the bug database | ||
47 | 117 | ----------------------------- | ||
48 | 118 | |||
49 | 119 | BugzillaAPI implements IExternalBugTracker.initializeRemoteBugDB(), | ||
50 | 120 | which takes a list of bug IDs to fetch from the remote server and stores | ||
51 | 121 | those bugs locally for future use. | ||
52 | 122 | |||
53 | 123 | >>> bugzilla.initializeRemoteBugDB([1, 2]) | ||
54 | 124 | CALLED Bug.get({'ids': [1, 2], 'permissive': True}) | ||
55 | 125 | |||
56 | 126 | The bug data is stored as a list of dicts: | ||
57 | 127 | |||
58 | 128 | >>> def print_bugs(bugs): | ||
59 | 129 | ... for bug in sorted(bugs): | ||
60 | 130 | ... print "Bug %s:" % bug | ||
61 | 131 | ... for key in sorted(bugzilla._bugs[bug]): | ||
62 | 132 | ... print " %s: %s" % (key, bugzilla._bugs[bug][key]) | ||
63 | 133 | ... print "\n" | ||
64 | 134 | |||
65 | 135 | >>> print_bugs(bugzilla._bugs) | ||
66 | 136 | Bug 1: | ||
67 | 137 | alias: | ||
68 | 138 | assigned_to: test@canonical.com | ||
69 | 139 | component: GPPSystems | ||
70 | 140 | creation_time: 20080610T16:19:53 | ||
71 | 141 | id: 1 | ||
72 | 142 | internals:... | ||
73 | 143 | is_open: True | ||
74 | 144 | last_change_time: 20080610T16:19:53 | ||
75 | 145 | priority: P1 | ||
76 | 146 | product: Marvin | ||
77 | 147 | resolution: FIXED | ||
78 | 148 | severity: normal | ||
79 | 149 | status: RESOLVED | ||
80 | 150 | summary: That bloody robot still exists. | ||
81 | 151 | <BLANKLINE> | ||
82 | 152 | Bug 2: | ||
83 | 153 | alias: bug-two | ||
84 | 154 | assigned_to: marvin@heartofgold.ship | ||
85 | 155 | component: Crew | ||
86 | 156 | creation_time: 20080611T09:23:12 | ||
87 | 157 | id: 2 | ||
88 | 158 | internals:... | ||
89 | 159 | is_open: True | ||
90 | 160 | last_change_time: 20080611T09:24:29 | ||
91 | 161 | priority: P1 | ||
92 | 162 | product: HeartOfGold | ||
93 | 163 | resolution: | ||
94 | 164 | severity: high | ||
95 | 165 | status: NEW | ||
96 | 166 | summary: Collect unknown persons in docking bay 2. | ||
97 | 167 | <BLANKLINE> | ||
98 | 168 | <BLANKLINE> | ||
99 | 169 | |||
100 | 170 | |||
101 | 171 | Storing bugs | ||
102 | 172 | ------------ | ||
103 | 173 | |||
104 | 174 | initializeRemoteBugDB() uses the _storeBugs() method to store bug data. | ||
105 | 175 | _storeBugs() will only store a bug once, even if it is requested both by | ||
106 | 176 | alias and ID. We'll reset the test BugzillaAPI's _bugs and _bug_aliases | ||
107 | 177 | dicts to demonstrate this. | ||
108 | 178 | |||
109 | 179 | >>> bugzilla._bugs = {} | ||
110 | 180 | >>> bugzilla._bug_aliases = {} | ||
111 | 181 | >>> bugzilla.initializeRemoteBugDB([2, 'bug-two']) | ||
112 | 182 | CALLED Bug.get({'ids': [2, 'bug-two'], 'permissive': True}) | ||
113 | 183 | |||
114 | 184 | >>> for bug in sorted(bugzilla._bugs): | ||
115 | 185 | ... print "Bug %r:" % bug | ||
116 | 186 | ... for key in sorted(bugzilla._bugs[bug]): | ||
117 | 187 | ... print " %s: %s" % (key, bugzilla._bugs[bug][key]) | ||
118 | 188 | ... print "\n" | ||
119 | 189 | Bug 2: | ||
120 | 190 | alias: bug-two | ||
121 | 191 | assigned_to: marvin@heartofgold.ship | ||
122 | 192 | component: Crew | ||
123 | 193 | creation_time: 20080611T09:23:12 | ||
124 | 194 | id: 2 | ||
125 | 195 | internals:... | ||
126 | 196 | is_open: True | ||
127 | 197 | last_change_time: 20080611T09:24:29 | ||
128 | 198 | priority: P1 | ||
129 | 199 | product: HeartOfGold | ||
130 | 200 | resolution: | ||
131 | 201 | severity: high | ||
132 | 202 | status: NEW | ||
133 | 203 | summary: Collect unknown persons in docking bay 2. | ||
134 | 204 | <BLANKLINE> | ||
135 | 205 | <BLANKLINE> | ||
136 | 206 | |||
137 | 207 | Aliases are stored in a separate dict, which contains a mapping between | ||
138 | 208 | the alias and the bug's actual ID. | ||
139 | 209 | |||
140 | 210 | >>> for alias, bug_id in bugzilla._bug_aliases.items(): | ||
141 | 211 | ... print "%s: %s" % (alias, bug_id) | ||
142 | 212 | bug-two: 2 | ||
143 | 213 | |||
144 | 214 | The method _getActualBugId() returns the correct bug ID for a passed bug | ||
145 | 215 | ID or alias. | ||
146 | 216 | |||
147 | 217 | >>> bugzilla._getActualBugId('bug-two') | ||
148 | 218 | 2 | ||
149 | 219 | |||
150 | 220 | >>> bugzilla._getActualBugId(2) | ||
151 | 221 | 2 | ||
152 | 78 | 222 | ||
153 | === modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla-lp-plugin.txt' | |||
154 | --- lib/lp/bugs/doc/externalbugtracker-bugzilla-lp-plugin.txt 2009-08-19 09:22:08 +0000 | |||
155 | +++ lib/lp/bugs/doc/externalbugtracker-bugzilla-lp-plugin.txt 2009-08-21 09:32:57 +0000 | |||
156 | @@ -1,4 +1,5 @@ | |||
158 | 1 | = Bugzilla bugtrackers with the Launchpad plugin = | 1 | Bugzilla bugtrackers with the Launchpad plugin |
159 | 2 | ============================================== | ||
160 | 2 | 3 | ||
161 | 3 | These tests cover the BugzillaLPPlugin ExternalBugTracker, which handles | 4 | These tests cover the BugzillaLPPlugin ExternalBugTracker, which handles |
162 | 4 | Bugzilla instances that have the Launchpad plugin installed. | 5 | Bugzilla instances that have the Launchpad plugin installed. |
163 | @@ -25,7 +26,8 @@ | |||
164 | 25 | True | 26 | True |
165 | 26 | 27 | ||
166 | 27 | 28 | ||
168 | 28 | == Authentication == | 29 | Authentication |
169 | 30 | -------------- | ||
170 | 29 | 31 | ||
171 | 30 | XML-RPC methods that modify data on the remote server require | 32 | XML-RPC methods that modify data on the remote server require |
172 | 31 | authentication. To authenticate, we create a LoginToken of type | 33 | authentication. To authenticate, we create a LoginToken of type |
173 | @@ -154,7 +156,8 @@ | |||
174 | 154 | Protocol error: 500 "Internal server error" | 156 | Protocol error: 500 "Internal server error" |
175 | 155 | 157 | ||
176 | 156 | 158 | ||
178 | 157 | == Getting the current time == | 159 | Getting the current time |
179 | 160 | ------------------------ | ||
180 | 158 | 161 | ||
181 | 159 | The BugzillaLPPlugin ExternalBugTracker, like all other | 162 | The BugzillaLPPlugin ExternalBugTracker, like all other |
182 | 160 | ExternalBugTrackers, has a getCurrentDBTime() method, which returns the | 163 | ExternalBugTrackers, has a getCurrentDBTime() method, which returns the |
183 | @@ -173,7 +176,8 @@ | |||
184 | 173 | datetime.datetime(2008, 5, 16, 15, 53, 20, tzinfo=<UTC>) | 176 | datetime.datetime(2008, 5, 16, 15, 53, 20, tzinfo=<UTC>) |
185 | 174 | 177 | ||
186 | 175 | 178 | ||
188 | 176 | == Initializing the remote bug database == | 179 | Initializing the remote bug database |
189 | 180 | ------------------------------------ | ||
190 | 177 | 181 | ||
191 | 178 | The BugzillaLPPlugin implements the standard initializeRemoteBugDB() | 182 | The BugzillaLPPlugin implements the standard initializeRemoteBugDB() |
192 | 179 | method, taking a list of the bug ids that need to be updated. It uses | 183 | method, taking a list of the bug ids that need to be updated. It uses |
193 | @@ -228,56 +232,13 @@ | |||
194 | 228 | <BLANKLINE> | 232 | <BLANKLINE> |
195 | 229 | <BLANKLINE> | 233 | <BLANKLINE> |
196 | 230 | 234 | ||
247 | 231 | initializeRemoteBugDB() will only store a bug once, even if it is | 235 | BugzillaLPPlugin.initializeRemoteBugDB() uses its _storeBugs() method to |
248 | 232 | requested both by alias and ID. We'll reset the test BugzillaLPPlugin's | 236 | store bugs. See externalbugtracker-bugzilla-api.txt for details of |
249 | 233 | _bugs and _bug_aliases dicts to demonstrate this. | 237 | _storeBugs(). |
250 | 234 | 238 | ||
251 | 235 | >>> bugzilla._bugs = {} | 239 | |
252 | 236 | >>> bugzilla._bug_aliases = {} | 240 | Getting a list of changed bugs |
253 | 237 | >>> bugzilla.initializeRemoteBugDB([2, 'bug-two']) | 241 | ------------------------------ |
204 | 238 | CALLED Launchpad.get_bugs({'ids': [2, 'bug-two'], 'permissive': True}) | ||
205 | 239 | |||
206 | 240 | >>> for bug in sorted(bugzilla._bugs): | ||
207 | 241 | ... print "Bug %r:" % bug | ||
208 | 242 | ... for key in sorted(bugzilla._bugs[bug]): | ||
209 | 243 | ... print " %s: %s" % (key, bugzilla._bugs[bug][key]) | ||
210 | 244 | ... print "\n" | ||
211 | 245 | Bug 2: | ||
212 | 246 | alias: bug-two | ||
213 | 247 | assigned_to: marvin@heartofgold.ship | ||
214 | 248 | component: Crew | ||
215 | 249 | creation_time: 20080611T09:23:12 | ||
216 | 250 | id: 2 | ||
217 | 251 | internals:... | ||
218 | 252 | is_open: True | ||
219 | 253 | last_change_time: 20080611T09:24:29 | ||
220 | 254 | priority: P1 | ||
221 | 255 | product: HeartOfGold | ||
222 | 256 | resolution: | ||
223 | 257 | severity: high | ||
224 | 258 | status: NEW | ||
225 | 259 | summary: Collect unknown persons in docking bay 2. | ||
226 | 260 | <BLANKLINE> | ||
227 | 261 | <BLANKLINE> | ||
228 | 262 | |||
229 | 263 | Aliases are stored in a separate dict, which contains a mapping between | ||
230 | 264 | the alias and the bug's actual ID. | ||
231 | 265 | |||
232 | 266 | >>> for alias, bug_id in bugzilla._bug_aliases.items(): | ||
233 | 267 | ... print "%s: %s" % (alias, bug_id) | ||
234 | 268 | bug-two: 2 | ||
235 | 269 | |||
236 | 270 | The method _getActualBugId() returns the correct bug ID for a passed bug | ||
237 | 271 | ID or alias. | ||
238 | 272 | |||
239 | 273 | >>> bugzilla._getActualBugId('bug-two') | ||
240 | 274 | 2 | ||
241 | 275 | |||
242 | 276 | >>> bugzilla._getActualBugId(2) | ||
243 | 277 | 2 | ||
244 | 278 | |||
245 | 279 | |||
246 | 280 | == Getting a list of changed bugs == | ||
254 | 281 | 242 | ||
255 | 282 | IExternalBugTracker defines a method, getModifiedRemoteBugs(), which | 243 | IExternalBugTracker defines a method, getModifiedRemoteBugs(), which |
256 | 283 | accepts a list of bug IDs and a datetime as a parameter and returns the | 244 | accepts a list of bug IDs and a datetime as a parameter and returns the |
257 | @@ -337,7 +298,8 @@ | |||
258 | 337 | CALLED Launchpad.get_bugs({'ids': [3], 'permissive': True}) | 298 | CALLED Launchpad.get_bugs({'ids': [3], 'permissive': True}) |
259 | 338 | 299 | ||
260 | 339 | 300 | ||
262 | 340 | == Getting remote statuses == | 301 | Getting remote statuses |
263 | 302 | ----------------------- | ||
264 | 341 | 303 | ||
265 | 342 | BugzillaLPPlugin.getRemoteStatus() will return the remote status of a | 304 | BugzillaLPPlugin.getRemoteStatus() will return the remote status of a |
266 | 343 | given bug as a string. If the bug has a resolution, that will be | 305 | given bug as a string. If the bug has a resolution, that will be |
267 | @@ -369,7 +331,8 @@ | |||
268 | 369 | >>> del bugzilla._bugs[999] | 331 | >>> del bugzilla._bugs[999] |
269 | 370 | 332 | ||
270 | 371 | 333 | ||
272 | 372 | == Getting the remote product == | 334 | Getting the remote product |
273 | 335 | -------------------------- | ||
274 | 373 | 336 | ||
275 | 374 | getRemoteProduct() returns the product a remote bug is associated with | 337 | getRemoteProduct() returns the product a remote bug is associated with |
276 | 375 | in Bugzilla. | 338 | in Bugzilla. |
277 | @@ -393,7 +356,8 @@ | |||
278 | 393 | BugNotFound: 12345 | 356 | BugNotFound: 12345 |
279 | 394 | 357 | ||
280 | 395 | 358 | ||
282 | 396 | == Retrieving remote comments == | 359 | Retrieving remote comments |
283 | 360 | -------------------------- | ||
284 | 397 | 361 | ||
285 | 398 | BugzillaLPPlugin implments the ISupportsCommentImport interface, which | 362 | BugzillaLPPlugin implments the ISupportsCommentImport interface, which |
286 | 399 | means that we can use it to import comments from the remote Bugzilla | 363 | means that we can use it to import comments from the remote Bugzilla |
287 | @@ -435,7 +399,8 @@ | |||
288 | 435 | >>> LaunchpadZopelessLayer.switchDbUser(config.checkwatches.dbuser) | 399 | >>> LaunchpadZopelessLayer.switchDbUser(config.checkwatches.dbuser) |
289 | 436 | 400 | ||
290 | 437 | 401 | ||
292 | 438 | === getCommentIds() === | 402 | getCommentIds() |
293 | 403 | --------------- | ||
294 | 439 | 404 | ||
295 | 440 | ISupportsCommentImport.getCommentIds() is the method used to get all the | 405 | ISupportsCommentImport.getCommentIds() is the method used to get all the |
296 | 441 | comment IDs for a given bug on a remote bugtracker. | 406 | comment IDs for a given bug on a remote bugtracker. |
297 | @@ -456,7 +421,8 @@ | |||
298 | 456 | BugNotFound: 42 | 421 | BugNotFound: 42 |
299 | 457 | 422 | ||
300 | 458 | 423 | ||
302 | 459 | === fetchComments() === | 424 | fetchComments() |
303 | 425 | --------------- | ||
304 | 460 | 426 | ||
305 | 461 | ISupportsCommentImport.fetchComments() is the method used to fetch a | 427 | ISupportsCommentImport.fetchComments() is the method used to fetch a |
306 | 462 | given set of comments from the remote bugtracker. It takes a bug watch | 428 | given set of comments from the remote bugtracker. It takes a bug watch |
307 | @@ -488,7 +454,8 @@ | |||
308 | 488 | time: 20080616T13:22:29 | 454 | time: 20080616T13:22:29 |
309 | 489 | 455 | ||
310 | 490 | 456 | ||
312 | 491 | === getPosterForComment() === | 457 | getPosterForComment() |
313 | 458 | --------------------- | ||
314 | 492 | 459 | ||
315 | 493 | ISupportsCommentImport.getPosterForComment() returns a tuple of | 460 | ISupportsCommentImport.getPosterForComment() returns a tuple of |
316 | 494 | (displayname, email) for the author of a remote comment. | 461 | (displayname, email) for the author of a remote comment. |
317 | @@ -507,7 +474,8 @@ | |||
318 | 507 | None trillian | 474 | None trillian |
319 | 508 | 475 | ||
320 | 509 | 476 | ||
322 | 510 | === getMessageForComment() === | 477 | getMessageForComment() |
323 | 478 | ---------------------- | ||
324 | 511 | 479 | ||
325 | 512 | ISupportsCommentImport.getMessageForComment() returns a Launchpad | 480 | ISupportsCommentImport.getMessageForComment() returns a Launchpad |
326 | 513 | IMessage instance for a given comment. It takes a bug watch, a comment | 481 | IMessage instance for a given comment. It takes a bug watch, a comment |
327 | @@ -533,7 +501,8 @@ | |||
328 | 533 | 2008-06-16 13:08:08+00:00 | 501 | 2008-06-16 13:08:08+00:00 |
329 | 534 | 502 | ||
330 | 535 | 503 | ||
332 | 536 | == Pushing comments to remote systems == | 504 | Pushing comments to remote systems |
333 | 505 | ---------------------------------- | ||
334 | 537 | 506 | ||
335 | 538 | BugzillaLPPlugin implements the ISupportsCommentPushing interface, which | 507 | BugzillaLPPlugin implements the ISupportsCommentPushing interface, which |
336 | 539 | defines the necessary methods for pushing comments to remote servers. | 508 | defines the necessary methods for pushing comments to remote servers. |
337 | @@ -583,7 +552,8 @@ | |||
338 | 583 | <BLANKLINE> | 552 | <BLANKLINE> |
339 | 584 | 553 | ||
340 | 585 | 554 | ||
342 | 586 | == Linking remote bugs to Launchpad bugs == | 555 | Linking remote bugs to Launchpad bugs |
343 | 556 | ------------------------------------- | ||
344 | 587 | 557 | ||
345 | 588 | BugzillaLPPlugin implements the ISupportsBackLinking interface, which | 558 | BugzillaLPPlugin implements the ISupportsBackLinking interface, which |
346 | 589 | provides methods to set and retrieve the Launchpad bug that links to a | 559 | provides methods to set and retrieve the Launchpad bug that links to a |
347 | @@ -644,7 +614,8 @@ | |||
348 | 644 | 10 | 614 | 10 |
349 | 645 | 615 | ||
350 | 646 | 616 | ||
352 | 647 | == Working with a specified set of Bugzilla products == | 617 | Working with a specified set of Bugzilla products |
353 | 618 | ------------------------------------------------- | ||
354 | 648 | 619 | ||
355 | 649 | BugzillaLPPlugin can be instructed to only get the data for a set of | 620 | BugzillaLPPlugin can be instructed to only get the data for a set of |
356 | 650 | bug IDs if those bugs belong to one of a given set of products. | 621 | bug IDs if those bugs belong to one of a given set of products. |
357 | @@ -691,7 +662,8 @@ | |||
358 | 691 | 0 | 662 | 0 |
359 | 692 | 663 | ||
360 | 693 | 664 | ||
362 | 694 | == Getting the products for a set of remote bugs == | 665 | Getting the products for a set of remote bugs |
363 | 666 | --------------------------------------------- | ||
364 | 695 | 667 | ||
365 | 696 | BugzillaLPPlugin provides a helper method, getProductsForRemoteBugs(), | 668 | BugzillaLPPlugin provides a helper method, getProductsForRemoteBugs(), |
366 | 697 | which takes a list of bug IDs or aliases and returns the products to | 669 | which takes a list of bug IDs or aliases and returns the products to |
367 | 698 | 670 | ||
368 | === modified file 'lib/lp/bugs/externalbugtracker/bugzilla.py' | |||
369 | --- lib/lp/bugs/externalbugtracker/bugzilla.py 2009-08-19 13:19:22 +0000 | |||
370 | +++ lib/lp/bugs/externalbugtracker/bugzilla.py 2009-08-21 09:32:57 +0000 | |||
371 | @@ -430,6 +430,98 @@ | |||
372 | 430 | self.baseurl, | 430 | self.baseurl, |
373 | 431 | "Fault %s: %s" % (fault.faultCode, fault.faultString)) | 431 | "Fault %s: %s" % (fault.faultCode, fault.faultString)) |
374 | 432 | 432 | ||
375 | 433 | def _storeBugs(self, remote_bugs): | ||
376 | 434 | """Store remote bugs in the local `bugs` dict.""" | ||
377 | 435 | for remote_bug in remote_bugs: | ||
378 | 436 | self._bugs[remote_bug['id']] = remote_bug | ||
379 | 437 | |||
380 | 438 | # The bug_aliases dict is a mapping between aliases and bug | ||
381 | 439 | # IDs. We use the aliases dict to look up the correct ID for | ||
382 | 440 | # a bug. This allows us to reference a bug by either ID or | ||
383 | 441 | # alias. | ||
384 | 442 | if remote_bug['alias'] != '': | ||
385 | 443 | self._bug_aliases[remote_bug['alias']] = remote_bug['id'] | ||
386 | 444 | |||
387 | 445 | def getCurrentDBTime(self): | ||
388 | 446 | """See `IExternalBugTracker`.""" | ||
389 | 447 | time_dict = self.xmlrpc_proxy.Bugzilla.time() | ||
390 | 448 | |||
391 | 449 | # Convert the XML-RPC DateTime we get back into a regular Python | ||
392 | 450 | # datetime. | ||
393 | 451 | server_db_timetuple = time.strptime( | ||
394 | 452 | str(time_dict['db_time']), '%Y%m%dT%H:%M:%S') | ||
395 | 453 | server_db_datetime = datetime(*server_db_timetuple[:6]) | ||
396 | 454 | |||
397 | 455 | # The server's DB time is the one that we want to use. However, | ||
398 | 456 | # this may not be in UTC, so we need to convert it. Since we | ||
399 | 457 | # can't guarantee that the timezone data returned by the server | ||
400 | 458 | # is sane, we work out the server's offset from UTC by looking | ||
401 | 459 | # at the difference between the web_time and the web_time_utc | ||
402 | 460 | # values. | ||
403 | 461 | server_web_time = time.strptime( | ||
404 | 462 | str(time_dict['web_time']), '%Y%m%dT%H:%M:%S') | ||
405 | 463 | server_web_datetime = datetime(*server_web_time[:6]) | ||
406 | 464 | server_web_time_utc = time.strptime( | ||
407 | 465 | str(time_dict['web_time_utc']), '%Y%m%dT%H:%M:%S') | ||
408 | 466 | server_web_datetime_utc = datetime(*server_web_time_utc[:6]) | ||
409 | 467 | |||
410 | 468 | server_utc_offset = server_web_datetime - server_web_datetime_utc | ||
411 | 469 | server_utc_datetime = server_db_datetime - server_utc_offset | ||
412 | 470 | |||
413 | 471 | return server_utc_datetime.replace(tzinfo=pytz.timezone('UTC')) | ||
414 | 472 | |||
415 | 473 | def _getActualBugId(self, bug_id): | ||
416 | 474 | """Return the actual bug id for an alias or id.""" | ||
417 | 475 | # See if bug_id is actually an alias. | ||
418 | 476 | actual_bug_id = self._bug_aliases.get(bug_id) | ||
419 | 477 | |||
420 | 478 | # bug_id isn't an alias, so try turning it into an int and | ||
421 | 479 | # looking the bug up by ID. | ||
422 | 480 | if actual_bug_id is not None: | ||
423 | 481 | return actual_bug_id | ||
424 | 482 | else: | ||
425 | 483 | try: | ||
426 | 484 | actual_bug_id = int(bug_id) | ||
427 | 485 | except ValueError: | ||
428 | 486 | # If bug_id can't be int()'d then it's likely an alias | ||
429 | 487 | # that doesn't exist, so raise BugNotFound. | ||
430 | 488 | raise BugNotFound(bug_id) | ||
431 | 489 | |||
432 | 490 | # Check that the bug does actually exist. That way we're | ||
433 | 491 | # treating integer bug IDs and aliases in the same way. | ||
434 | 492 | if actual_bug_id not in self._bugs: | ||
435 | 493 | raise BugNotFound(bug_id) | ||
436 | 494 | |||
437 | 495 | return actual_bug_id | ||
438 | 496 | |||
439 | 497 | def _getBugIdsToRetrieve(self, bug_ids): | ||
440 | 498 | """For a set of bug IDs, return those for which we have no data.""" | ||
441 | 499 | bug_ids_to_retrieve = [] | ||
442 | 500 | for bug_id in bug_ids: | ||
443 | 501 | try: | ||
444 | 502 | actual_bug_id = self._getActualBugId(bug_id) | ||
445 | 503 | except BugNotFound: | ||
446 | 504 | bug_ids_to_retrieve.append(bug_id) | ||
447 | 505 | |||
448 | 506 | return bug_ids_to_retrieve | ||
449 | 507 | |||
450 | 508 | def initializeRemoteBugDB(self, bug_ids): | ||
451 | 509 | """See `IExternalBugTracker`.""" | ||
452 | 510 | # First, discard all those bug IDs about which we already have | ||
453 | 511 | # data. | ||
454 | 512 | bug_ids_to_retrieve = self._getBugIdsToRetrieve(bug_ids) | ||
455 | 513 | |||
456 | 514 | # Pull the bug data from the remote server. permissive=True here | ||
457 | 515 | # prevents Bugzilla from erroring if we ask for a bug it doesn't | ||
458 | 516 | # have. | ||
459 | 517 | response_dict = self.xmlrpc_proxy.Bug.get({ | ||
460 | 518 | 'ids': bug_ids_to_retrieve, | ||
461 | 519 | 'permissive': True, | ||
462 | 520 | }) | ||
463 | 521 | remote_bugs = response_dict['bugs'] | ||
464 | 522 | |||
465 | 523 | self._storeBugs(remote_bugs) | ||
466 | 524 | |||
467 | 433 | 525 | ||
468 | 434 | class BugzillaLPPlugin(BugzillaAPI): | 526 | class BugzillaLPPlugin(BugzillaAPI): |
469 | 435 | """An `ExternalBugTracker` to handle Bugzillas using the LP Plugin.""" | 527 | """An `ExternalBugTracker` to handle Bugzillas using the LP Plugin.""" |
470 | @@ -472,18 +564,6 @@ | |||
471 | 472 | raise BugTrackerAuthenticationError( | 564 | raise BugTrackerAuthenticationError( |
472 | 473 | self.baseurl, message) | 565 | self.baseurl, message) |
473 | 474 | 566 | ||
474 | 475 | def _storeBugs(self, remote_bugs): | ||
475 | 476 | """Store remote bugs in the local `bugs` dict.""" | ||
476 | 477 | for remote_bug in remote_bugs: | ||
477 | 478 | self._bugs[remote_bug['id']] = remote_bug | ||
478 | 479 | |||
479 | 480 | # The bug_aliases dict is a mapping between aliases and bug | ||
480 | 481 | # IDs. We use the aliases dict to look up the correct ID for | ||
481 | 482 | # a bug. This allows us to reference a bug by either ID or | ||
482 | 483 | # alias. | ||
483 | 484 | if remote_bug['alias'] != '': | ||
484 | 485 | self._bug_aliases[remote_bug['alias']] = remote_bug['id'] | ||
485 | 486 | |||
486 | 487 | def getModifiedRemoteBugs(self, bug_ids, last_checked): | 567 | def getModifiedRemoteBugs(self, bug_ids, last_checked): |
487 | 488 | """See `IExternalBugTracker`.""" | 568 | """See `IExternalBugTracker`.""" |
488 | 489 | # We marshal last_checked into an xmlrpclib.DateTime since | 569 | # We marshal last_checked into an xmlrpclib.DateTime since |
489 | @@ -538,12 +618,7 @@ | |||
490 | 538 | """See `IExternalBugTracker`.""" | 618 | """See `IExternalBugTracker`.""" |
491 | 539 | # First, discard all those bug IDs about which we already have | 619 | # First, discard all those bug IDs about which we already have |
492 | 540 | # data. | 620 | # data. |
499 | 541 | bug_ids_to_retrieve = [] | 621 | bug_ids_to_retrieve = self._getBugIdsToRetrieve(bug_ids) |
494 | 542 | for bug_id in bug_ids: | ||
495 | 543 | try: | ||
496 | 544 | actual_bug_id = self._getActualBugId(bug_id) | ||
497 | 545 | except BugNotFound: | ||
498 | 546 | bug_ids_to_retrieve.append(bug_id) | ||
500 | 547 | 622 | ||
501 | 548 | # Next, grab the bugs we still need from the remote server. | 623 | # Next, grab the bugs we still need from the remote server. |
502 | 549 | # We pass permissive=True to ensure that Bugzilla won't error if | 624 | # We pass permissive=True to ensure that Bugzilla won't error if |
503 | @@ -573,30 +648,6 @@ | |||
504 | 573 | server_utc_time = datetime(*server_timetuple[:6]) | 648 | server_utc_time = datetime(*server_timetuple[:6]) |
505 | 574 | return server_utc_time.replace(tzinfo=pytz.timezone('UTC')) | 649 | return server_utc_time.replace(tzinfo=pytz.timezone('UTC')) |
506 | 575 | 650 | ||
507 | 576 | def _getActualBugId(self, bug_id): | ||
508 | 577 | """Return the actual bug id for an alias or id.""" | ||
509 | 578 | # See if bug_id is actually an alias. | ||
510 | 579 | actual_bug_id = self._bug_aliases.get(bug_id) | ||
511 | 580 | |||
512 | 581 | # bug_id isn't an alias, so try turning it into an int and | ||
513 | 582 | # looking the bug up by ID. | ||
514 | 583 | if actual_bug_id is not None: | ||
515 | 584 | return actual_bug_id | ||
516 | 585 | else: | ||
517 | 586 | try: | ||
518 | 587 | actual_bug_id = int(bug_id) | ||
519 | 588 | except ValueError: | ||
520 | 589 | # If bug_id can't be int()'d then it's likely an alias | ||
521 | 590 | # that doesn't exist, so raise BugNotFound. | ||
522 | 591 | raise BugNotFound(bug_id) | ||
523 | 592 | |||
524 | 593 | # Check that the bug does actually exist. That way we're | ||
525 | 594 | # treating integer bug IDs and aliases in the same way. | ||
526 | 595 | if actual_bug_id not in self._bugs: | ||
527 | 596 | raise BugNotFound(bug_id) | ||
528 | 597 | |||
529 | 598 | return actual_bug_id | ||
530 | 599 | |||
531 | 600 | def getRemoteStatus(self, bug_id): | 651 | def getRemoteStatus(self, bug_id): |
532 | 601 | """See `IExternalBugTracker`.""" | 652 | """See `IExternalBugTracker`.""" |
533 | 602 | actual_bug_id = self._getActualBugId(bug_id) | 653 | actual_bug_id = self._getActualBugId(bug_id) |
534 | 603 | 654 | ||
535 | === modified file 'lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt' | |||
536 | --- lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt 2009-08-18 16:39:17 +0000 | |||
537 | +++ lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt 2009-08-21 09:32:57 +0000 | |||
538 | @@ -576,3 +576,68 @@ | |||
539 | 576 | Traceback (most recent call last): | 576 | Traceback (most recent call last): |
540 | 577 | ... | 577 | ... |
541 | 578 | Fault: <Fault 300: 'The username or password you entered is not valid.'> | 578 | Fault: <Fault 300: 'The username or password you entered is not valid.'> |
542 | 579 | |||
543 | 580 | |||
544 | 581 | Getting the current time | ||
545 | 582 | ------------------------ | ||
546 | 583 | |||
547 | 584 | The Bugzilla.time() method allows us to retrieve a dict of the time on | ||
548 | 585 | the remote server. | ||
549 | 586 | |||
550 | 587 | >>> time_dict = server.Bugzilla.time() | ||
551 | 588 | >>> for key in sorted(time_dict): | ||
552 | 589 | ... print "%s: %s" % (key, time_dict[key]) | ||
553 | 590 | db_time: 20080501T01:01:01 | ||
554 | 591 | tz_name: UTC | ||
555 | 592 | tz_offset: +0000 | ||
556 | 593 | tz_short_name: UTC | ||
557 | 594 | web_time: 20080501T01:01:01 | ||
558 | 595 | web_time_utc: 20080501T01:01:01 | ||
559 | 596 | |||
560 | 597 | If the remote server is in a different timezone, the db_time and | ||
561 | 598 | web_time items will be in the server's local timezone whilst | ||
562 | 599 | web_time_utc will be in UTC. | ||
563 | 600 | |||
564 | 601 | >>> bugzilla_transport.utc_offset = 60**2 | ||
565 | 602 | >>> bugzilla_transport.timezone = 'CET' | ||
566 | 603 | >>> time_dict = server.Bugzilla.time() | ||
567 | 604 | >>> for key in sorted(time_dict): | ||
568 | 605 | ... print "%s: %s" % (key, time_dict[key]) | ||
569 | 606 | db_time: 20080501T01:01:01 | ||
570 | 607 | tz_name: CET | ||
571 | 608 | tz_offset: +0100 | ||
572 | 609 | tz_short_name: CET | ||
573 | 610 | web_time: 20080501T01:01:01 | ||
574 | 611 | web_time_utc: 20080501T00:01:01 | ||
575 | 612 | |||
576 | 613 | |||
577 | 614 | Getting bugs from the server | ||
578 | 615 | ---------------------------- | ||
579 | 616 | |||
580 | 617 | The Bugzilla API method Bug.get() allows us to get one or more bugs from | ||
581 | 618 | the remote server. It takes a list of bug IDs to return and returns a | ||
582 | 619 | list of dicts containing those bugs' data. | ||
583 | 620 | |||
584 | 621 | >>> return_value = server.Bug.get( | ||
585 | 622 | ... {'ids': [1], 'permissive': True}) | ||
586 | 623 | >>> [bug_dict] = return_value['bugs'] | ||
587 | 624 | >>> for key in sorted(bug_dict): | ||
588 | 625 | ... print "%s: %s" % (key, bug_dict[key]) | ||
589 | 626 | alias: | ||
590 | 627 | assigned_to: test@canonical.com | ||
591 | 628 | component: GPPSystems | ||
592 | 629 | creation_time: 20080610T16:19:53 | ||
593 | 630 | id: 1 | ||
594 | 631 | internals:... | ||
595 | 632 | is_open: True | ||
596 | 633 | last_change_time: 20080610T16:19:53 | ||
597 | 634 | priority: P1 | ||
598 | 635 | product: Marvin | ||
599 | 636 | resolution: FIXED | ||
600 | 637 | severity: normal | ||
601 | 638 | status: RESOLVED | ||
602 | 639 | summary: That bloody robot still exists. | ||
603 | 640 | |||
604 | 641 | Note that further tests for this functionality can be found in the | ||
605 | 642 | "Launchpad.get_bugs()" section, above. This is because these two methods | ||
606 | 643 | are synonymous. | ||
607 | 579 | 644 | ||
608 | === modified file 'lib/lp/bugs/tests/externalbugtracker.py' | |||
609 | --- lib/lp/bugs/tests/externalbugtracker.py 2009-08-18 16:41:18 +0000 | |||
610 | +++ lib/lp/bugs/tests/externalbugtracker.py 2009-08-21 09:32:57 +0000 | |||
611 | @@ -522,7 +522,9 @@ | |||
612 | 522 | # what BugZilla will return. | 522 | # what BugZilla will return. |
613 | 523 | local_time = xmlrpclib.DateTime(local_datetime.timetuple()) | 523 | local_time = xmlrpclib.DateTime(local_datetime.timetuple()) |
614 | 524 | 524 | ||
616 | 525 | utc_date_time = local_datetime - timedelta(seconds=self.utc_offset) | 525 | utc_offset_delta = timedelta(seconds=self.utc_offset) |
617 | 526 | utc_date_time = local_datetime - utc_offset_delta | ||
618 | 527 | |||
619 | 526 | utc_time = xmlrpclib.DateTime(utc_date_time.timetuple()) | 528 | utc_time = xmlrpclib.DateTime(utc_date_time.timetuple()) |
620 | 527 | return { | 529 | return { |
621 | 528 | 'local_time': local_time, | 530 | 'local_time': local_time, |
622 | @@ -757,7 +759,11 @@ | |||
623 | 757 | 759 | ||
624 | 758 | # Map namespaces onto method names. | 760 | # Map namespaces onto method names. |
625 | 759 | methods = { | 761 | methods = { |
627 | 760 | 'Bugzilla': ['version'], | 762 | 'Bug': ['get'], |
628 | 763 | 'Bugzilla': [ | ||
629 | 764 | 'time', | ||
630 | 765 | 'version', | ||
631 | 766 | ], | ||
632 | 761 | 'Test': ['login_required'], | 767 | 'Test': ['login_required'], |
633 | 762 | 'User': ['login'], | 768 | 'User': ['login'], |
634 | 763 | } | 769 | } |
635 | @@ -794,6 +800,29 @@ | |||
636 | 794 | 300, | 800 | 300, |
637 | 795 | "The username or password you entered is not valid.") | 801 | "The username or password you entered is not valid.") |
638 | 796 | 802 | ||
639 | 803 | def time(self): | ||
640 | 804 | """Return a dict of the local time and associated data.""" | ||
641 | 805 | # We cheat slightly by calling the superclass to get the time | ||
642 | 806 | # data. We do this the old fashioned way because XML-RPC | ||
643 | 807 | # Transports don't support new-style classes. | ||
644 | 808 | time_dict = TestBugzillaXMLRPCTransport.time(self) | ||
645 | 809 | offset_hours = (self.utc_offset / 60) / 60 | ||
646 | 810 | offset_string = '+%02d00' % offset_hours | ||
647 | 811 | |||
648 | 812 | return { | ||
649 | 813 | 'db_time': time_dict['local_time'], | ||
650 | 814 | 'tz_name': time_dict['tz_name'], | ||
651 | 815 | 'tz_offset': offset_string, | ||
652 | 816 | 'tz_short_name': time_dict['tz_name'], | ||
653 | 817 | 'web_time': time_dict['local_time'], | ||
654 | 818 | 'web_time_utc': time_dict['utc_time'], | ||
655 | 819 | } | ||
656 | 820 | |||
657 | 821 | def get(self, arguments): | ||
658 | 822 | """Return a list of bug dicts for a given set of bug ids.""" | ||
659 | 823 | # This method is actually just a synonym for get_bugs(). | ||
660 | 824 | return self.get_bugs(arguments) | ||
661 | 825 | |||
662 | 797 | 826 | ||
663 | 798 | class TestMantis(Mantis): | 827 | class TestMantis(Mantis): |
664 | 799 | """Mantis ExternalSystem for use in tests. | 828 | """Mantis ExternalSystem for use in tests. |
This branch adds an initializeRemot eBugDB( ) method to the BugzillaAPI cker. I've
ExternalBugTracker, in accordance with IExternalBugTra
refactored a couple of methods out of BugzillaLPPlugin, which descends
from BugzillaAPI, and into the BugzillaAPI class because they are useful
utilities.
I've also refactored the associated tests into the bugzilla-api doctest.
Note that this branch is based on a branch that hasn't yet landed (but
which is r=cprov). Please use the diff below rather than the one
generated by the merge proposal.
Launchpad lint
--------------
Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.
Linting changed files: bugs/doc/ externalbugtrac ker-bugzilla- api.txt bugs/doc/ externalbugtrac ker-bugzilla- lp-plugin. txt bugs/externalbu gtracker/ bugzilla. py bugs/tests/ bugzilla- xmlrpc- transport. txt bugs/tests/ externalbugtrac ker.py
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
Diff of changes
---------------
=== modified file 'lib/lp/ bugs/doc/ externalbugtrac ker-bugzilla- api.txt' bugs/doc/ externalbugtrac ker-bugzilla- api.txt 2009-08-20 15:43:37 +0000 bugs/doc/ externalbugtrac ker-bugzilla- api.txt 2009-08-21 09:51:57 +0000 getCurrentDBTim e() datetime( 2009, 8, 19, 22, 2, 2, tzinfo=<UTC>) ------- ------- ------- -- cker.initialize RemoteBugDB( ), initializeRemot eBugDB( [1, 2]) bugzilla. _bugs[bug] ): _bugs[bug] [key]) bugzilla. _bugs) teBugDB( ) uses the _storeBugs() method to store bug data.
--- lib/lp/
+++ lib/lp/
@@ -111,3 +111,111 @@
>>> bugzilla.
CALLED Bugzilla.time()
datetime.
+
+
+Initializing the bug database
+------
+
+BugzillaAPI implements IExternalBugTra
+which takes a list of bug IDs to fetch from the remote server and stores
+those bugs locally for future use.
+
+ >>> bugzilla.
+ CALLED Bug.get({'ids': [1, 2], 'permissive': True})
+
+The bug data is stored as a list of dicts:
+
+ >>> def print_bugs(bugs):
+ ... for bug in sorted(bugs):
+ ... print "Bug %s:" % bug
+ ... for key in sorted(
+ ... print " %s: %s" % (key, bugzilla.
+ ... print "\n"
+
+ >>> print_bugs(
+ Bug 1:
+ alias:
+ assigned_to: <email address hidden>
+ component: GPPSystems
+ creation_time: 20080610T16:19:53
+ id: 1
+ internals:...
+ is_open: True
+ last_change_time: 20080610T16:19:53
+ priority: P1
+ product: Marvin
+ resolution: FIXED
+ severity: normal
+ status: RESOLVED
+ summary: That bloody robot still exists.
+ <BLANKLINE>
+ Bug 2:
+ alias: bug-two
+ assigned_to: <email address hidden>
+ component: Crew
+ creation_time: 20080611T09:23:12
+ id: 2
+ internals:...
+ is_open: True
+ last_change_time: 20080611T09:24:29
+ priority: P1
+ product: HeartOfGold
+ resolution:
+ severity: high
+ status: NEW
+ summary: Collect unknown persons in docking bay 2.
+ <BLANKLINE>
+ <BLANKLINE>
+
+
+Storing bugs
+------------
+
+initializeRemo
+_storeBugs() will only store a bug once, even if it is requested both by
+alias and ID...