bugpipe 3.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bugpipe/__init__.py +42 -0
- bugpipe/api/AUDIT.md +331 -0
- bugpipe/api/README.md +728 -0
- bugpipe/api/__init__.py +3 -0
- bugpipe/api/client.py +328 -0
- bugpipe/api/models.py +742 -0
- bugpipe/api/parser.py +720 -0
- bugpipe/cli/__init__.py +69 -0
- bugpipe/cli/cmd.py +304 -0
- bugpipe/cli/term.py +234 -0
- bugpipe/cli/update_checker.py +207 -0
- bugpipe-3.0.1.dist-info/METADATA +52 -0
- bugpipe-3.0.1.dist-info/RECORD +15 -0
- bugpipe-3.0.1.dist-info/WHEEL +4 -0
- bugpipe-3.0.1.dist-info/entry_points.txt +3 -0
bugpipe/api/README.md
ADDED
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
# Google Issue Tracker API reference
|
|
2
|
+
|
|
3
|
+
Bugpipe reads public Google Issue Tracker issues through the JSON API at
|
|
4
|
+
`https://issuetracker.google.com/action`. It sends requests without cookies
|
|
5
|
+
or authentication tokens.
|
|
6
|
+
|
|
7
|
+
This reference records the request formats and response fields found through
|
|
8
|
+
browser traffic and API tests. {doc}`AUDIT.md <audit>` contains further
|
|
9
|
+
observations and marks which were tested.
|
|
10
|
+
|
|
11
|
+
## Requests and headers
|
|
12
|
+
|
|
13
|
+
The base URL is `https://issuetracker.google.com/action`. Endpoint paths below
|
|
14
|
+
include `/action`.
|
|
15
|
+
|
|
16
|
+
Search, issue fetches, comments, and updates use POST with JSON arrays.
|
|
17
|
+
Components, trackers, hotlists, relationships, and the health check use GET.
|
|
18
|
+
|
|
19
|
+
| Header | Value | POST | Resource GET |
|
|
20
|
+
|--------------|------------------------------------|------|--------------|
|
|
21
|
+
| Content-Type | `application/json` | Yes | Omit |
|
|
22
|
+
| Origin | `https://issuetracker.google.com` | Yes | Yes |
|
|
23
|
+
| Referer | `https://issuetracker.google.com/` | Yes | Yes |
|
|
24
|
+
| User-Agent | Browser or Googlebot user agent | Yes | Yes |
|
|
25
|
+
|
|
26
|
+
Recorded resource GET requests returned HTTP 400 when sent with
|
|
27
|
+
`Content-Type: application/json`. Omit that header for those requests.
|
|
28
|
+
|
|
29
|
+
The Python client provides search, single and batch issue fetches, comments,
|
|
30
|
+
updates, and the health check. The other endpoints below describe direct HTTP
|
|
31
|
+
requests.
|
|
32
|
+
|
|
33
|
+
## Response prefix
|
|
34
|
+
|
|
35
|
+
JSON responses start with `)]}'` followed by a newline. Strip this prefix
|
|
36
|
+
before parsing the JSON:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import json
|
|
40
|
+
|
|
41
|
+
clean = raw_text.removeprefix(")]}'\n")
|
|
42
|
+
data = json.loads(s=clean)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The parser also accepts the prefix followed by CRLF or a literal `\n`.
|
|
46
|
+
The health check returns plain text and has no prefix.
|
|
47
|
+
|
|
48
|
+
## Known trackers
|
|
49
|
+
|
|
50
|
+
The client's `TRACKERS` list contains these 14 trackers. The root component
|
|
51
|
+
IDs below come from the recorded API observations.
|
|
52
|
+
|
|
53
|
+
| Tracker ID | Name | Root Component | Public URL |
|
|
54
|
+
|------------|--------------|----------------|------------------------------------------|
|
|
55
|
+
| `1` | Pigweed | 1194524 | https://issues.pigweed.dev |
|
|
56
|
+
| `27` | Gerrit | 1370273 | https://issues.gerritcodereview.com |
|
|
57
|
+
| `53` | Git | 1320275 | https://git.issues.gerritcodereview.com |
|
|
58
|
+
| `79` | Skia | 1363359 | https://issues.skia.org |
|
|
59
|
+
| `105` | WebRTC | 1363538 | https://issues.webrtc.org |
|
|
60
|
+
| `131` | libyuv | 1363539 | https://libyuv.issues.chromium.org |
|
|
61
|
+
| `157` | Chromium | 1363614 | https://issues.chromium.org |
|
|
62
|
+
| `183` | Fuchsia | 1360843 | https://issues.fuchsia.dev |
|
|
63
|
+
| `235` | ANGLE | 853171 | https://issues.angleproject.org |
|
|
64
|
+
| `261` | AOMedia | 1597128 | https://aomedia.issues.chromium.org |
|
|
65
|
+
| `287` | WebM | 1615215 | https://issues.webmproject.org |
|
|
66
|
+
| `339` | GN | 1636803 | https://gn.issues.chromium.org |
|
|
67
|
+
| `365` | Project Zero | 1638259 | https://project-zero.issues.chromium.org |
|
|
68
|
+
| `391` | OSS Fuzz | 1638179 | https://issues.oss-fuzz.com |
|
|
69
|
+
|
|
70
|
+
Use `Bugpipe(trackers=None)` to search without a tracker filter, or pass names
|
|
71
|
+
or IDs such as `Bugpipe(trackers=["chromium", "fuchsia"])`. For direct tracker
|
|
72
|
+
lookups, pass the numeric ID to `/action/trackers/{id}`.
|
|
73
|
+
|
|
74
|
+
## Endpoints
|
|
75
|
+
|
|
76
|
+
### Search issues
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
POST /action/issues/list
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Request shape:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
[null, null, null, null, null, TRACKER_FILTER, QUERY_PAYLOAD]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Position | Field | Type | Value |
|
|
89
|
+
|----------|----------------|----------------------------------|----------------------------------------------------------------------------|
|
|
90
|
+
| `[5]` | tracker_filter | `list[str] \| null` | `["157"]` for Chromium, `["157", "183"]` for both, or `null` for no filter |
|
|
91
|
+
| `[6][0]` | query | `str` | Search query, such as `"status:open"` |
|
|
92
|
+
| `[6][1]` | reserved | `null` | Leave as `null`; recorded tests returned HTTP 400 for other values |
|
|
93
|
+
| `[6][2]` | page_size | `int` | 25, 50, 100, or 250 |
|
|
94
|
+
| `[6][3]` | page_token | `str`, omitted on the first page | Token from the previous response |
|
|
95
|
+
|
|
96
|
+
Search all trackers:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
[null, null, null, null, null, null, ["status:open", null, 50]]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Fetch the next page for Chromium:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
[null, null, null, null, null, ["157"], ["status:open", null, 50, "SOME_PAGE_TOKEN"]]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Get a single issue
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
POST /action/issues/{issue_id}/getIssue
|
|
112
|
+
POST /action/issues/{issue_id}/getIssue?currentTrackerId={tracker_id}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Request shape:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
[ISSUE_ID, DETAIL_LEVEL, FLAG_2]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| Position | Field | Type | Value |
|
|
122
|
+
|----------|--------------|-------|-----------------------------------------------------------------------|
|
|
123
|
+
| `[0]` | issue_id | `int` | Issue ID |
|
|
124
|
+
| `[1]` | detail_level | `int` | Use `2` to include the body, links, and relationship graph |
|
|
125
|
+
| `[2]` | flag_2 | `int` | The client uses `1`; recorded tests with 0 through 10 found no change |
|
|
126
|
+
|
|
127
|
+
The client sends `[issue_id, 2, 1]` without `currentTrackerId`.
|
|
128
|
+
In the recorded tests, detail levels other than `2` left `TOP[37]`, `TOP[40]`,
|
|
129
|
+
and `TOP[43]` as `null`.
|
|
130
|
+
|
|
131
|
+
### Batch get issues
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
POST /action/issues/batch
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Request shape:
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
["b.BatchGetIssuesRequest", null, null, [ISSUE_IDS, DETAIL_LEVEL, FLAG_2]]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| Position | Field | Type | Value |
|
|
144
|
+
|----------|-------|------|-------|
|
|
145
|
+
| `[1]`, `[2]` | unused | `null` | The client sends `null` |
|
|
146
|
+
| `[3][0]` | issue_ids | `list[int]` | Issue IDs to fetch |
|
|
147
|
+
| `[3][1]` | detail_level | `int` | Use `2` to include the body, links, and relationship graph |
|
|
148
|
+
| `[3][2]` | flag_2 | `int` | The client uses `2`; recorded tests with 0 through 10 found no change |
|
|
149
|
+
|
|
150
|
+
Match results by issue ID. Their order may differ from the request order.
|
|
151
|
+
|
|
152
|
+
### List comments
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
POST /action/issues/{issue_id}/listComments
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Request shape:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
[ISSUE_ID, SORT_ORDER, PAGE_SIZE, PAGE_TOKEN]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
| Position | Field | Type | Value |
|
|
165
|
+
|----------|-------|------|-------|
|
|
166
|
+
| `[0]` | issue_id | `int` | Issue ID |
|
|
167
|
+
| `[1]` | sort_order | `str \| null` | `"ASC"` for oldest first, `"DESC"` or `null` for newest first |
|
|
168
|
+
| `[2]` | page_size | `int` | Up to 500 comments |
|
|
169
|
+
| `[3]` | page_token | `str`, omitted on the first page | Token from the previous response |
|
|
170
|
+
|
|
171
|
+
This endpoint returns text comments. Its `total_count` excludes updates that
|
|
172
|
+
only change fields. Use `/updates` to include field changes.
|
|
173
|
+
|
|
174
|
+
The client defaults to `sort_order="ASC"` and `page_size=500`.
|
|
175
|
+
To request the first three comments, oldest first:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
[496840714, "ASC", 3]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### List issue updates
|
|
182
|
+
|
|
183
|
+
```text
|
|
184
|
+
POST /action/issues/{issue_id}/updates
|
|
185
|
+
POST /action/issues/{issue_id}/updates?currentTrackerId={tracker_id}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The client sends the short form, which requests updates newest first:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
[ISSUE_ID]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The API also accepts this format, tested in {doc}`AUDIT.md <audit>`:
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
[ISSUE_ID, SORT_ORDER, PAGE_SIZE, PAGE_TOKEN, UNKNOWN_FLAG]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
| Position | Field | Type | Value |
|
|
201
|
+
|----------|-------|------|-------|
|
|
202
|
+
| `[0]` | issue_id | `int` | Issue ID |
|
|
203
|
+
| `[1]` | sort_order | `str`, optional | `"ASC"` or `"DESC"`; defaults to `"DESC"` |
|
|
204
|
+
| `[2]` | page_size | `int`, optional | Updates per page; 1100 was tested |
|
|
205
|
+
| `[3]` | page_token | `str \| null` | Token from the previous response |
|
|
206
|
+
| `[4]` | unknown_flag | `int`, optional | `2` in captured browser requests; purpose unknown |
|
|
207
|
+
|
|
208
|
+
Updates can contain comments, field changes, and attachments.
|
|
209
|
+
`IssueUpdatesResult.comments` selects comments and reverses the default
|
|
210
|
+
response order to put the oldest first.
|
|
211
|
+
|
|
212
|
+
### Get a component
|
|
213
|
+
|
|
214
|
+
```text
|
|
215
|
+
GET /action/components/{id}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The response type is `b.Component`. The recorded response places the component
|
|
219
|
+
array at `data[0][28]`:
|
|
220
|
+
|
|
221
|
+
| Index | Field | Type | Meaning |
|
|
222
|
+
|-------|-------|------|---------|
|
|
223
|
+
| `[1]` | component_id | `int` | Component ID |
|
|
224
|
+
| `[2]` | parent_component_id | `int` | Parent ID |
|
|
225
|
+
| `[3]` | component_name | `str` | Component name |
|
|
226
|
+
| `[6][0]` | breadcrumb_ids | `list[int]` | IDs from the root to this component |
|
|
227
|
+
| `[6][1]` | breadcrumb_names | `list[str]` | Names from the root to this component |
|
|
228
|
+
| `[6][2]` | custom_field_defs | `list` | Custom field definitions |
|
|
229
|
+
| `[19]` | tracker_id | `int` | Tracker ID |
|
|
230
|
+
|
|
231
|
+
### Batch get components
|
|
232
|
+
|
|
233
|
+
```text
|
|
234
|
+
GET /action/components?id=X&id=Y&id=Z
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Pass each ID as an `id` query parameter. The response type is
|
|
238
|
+
`b.ListComponentsResponse`. Each component has the same inner array at `[28]`
|
|
239
|
+
as the single component response.
|
|
240
|
+
|
|
241
|
+
### Get a tracker
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
GET /action/trackers/{id}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The response type is `b.Tracker`. The recorded response places the tracker
|
|
248
|
+
array at `data[0][9]`:
|
|
249
|
+
|
|
250
|
+
| Index | Field | Type | Meaning |
|
|
251
|
+
|-------|-------|------|---------|
|
|
252
|
+
| `[0]` | tracker_id | `int` | Tracker ID |
|
|
253
|
+
| `[1]` | root_component_id | `int` | Root component ID |
|
|
254
|
+
| `[2]` | name | `str` | Tracker name |
|
|
255
|
+
| `[5]` | branding | `list` | `[logo_url, type_int, code_of_conduct_url]` |
|
|
256
|
+
| `[7]` | internal_url | `str` | Internal URL, such as `https://g-issues.chromium.org` |
|
|
257
|
+
| `[8]` | public_url | `str` | Public URL, such as `https://issues.chromium.org` |
|
|
258
|
+
| `[10]` | slug | `str \| null` | URL name, such as `"fuchsia"` |
|
|
259
|
+
|
|
260
|
+
### Get a hotlist
|
|
261
|
+
|
|
262
|
+
```text
|
|
263
|
+
GET /action/hotlists/{id}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The response type is `b.Hotlist`. The recorded response places the hotlist
|
|
267
|
+
array at `data[0][18]`:
|
|
268
|
+
|
|
269
|
+
| Index | Field | Type | Meaning |
|
|
270
|
+
|-------|-------|------|---------|
|
|
271
|
+
| `[0]` | hotlist_id | `int` | Hotlist ID |
|
|
272
|
+
| `[1]` | name | `str` | Hotlist name |
|
|
273
|
+
| `[2]` | description | `str \| null` | Description |
|
|
274
|
+
| `[6]` | created_at | timestamp | Creation time |
|
|
275
|
+
| `[7]` | modified_at | timestamp | Last change |
|
|
276
|
+
| `[8]` | admins | `list` | Admin user arrays |
|
|
277
|
+
|
|
278
|
+
### Batch get hotlists
|
|
279
|
+
|
|
280
|
+
```text
|
|
281
|
+
GET /action/hotlists?id=X&id=Y
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Pass each ID as an `id` query parameter. The response type is
|
|
285
|
+
`b.ListHotlistsResponse`.
|
|
286
|
+
|
|
287
|
+
### List issue relationships
|
|
288
|
+
|
|
289
|
+
```text
|
|
290
|
+
GET /action/issues/{id}/relationships?relationshipType=1
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The response type is `b.ListIssueRelationshipsResponse`.
|
|
294
|
+
`relationshipType=1` selects blocking and blocked-by relationships. An empty
|
|
295
|
+
response is `[["b.ListIssueRelationshipsResponse"]]`.
|
|
296
|
+
|
|
297
|
+
`TOP[36]` on an issue lists the issues it blocks. Use the relationships
|
|
298
|
+
endpoint to find the issues that block it.
|
|
299
|
+
|
|
300
|
+
### Health check
|
|
301
|
+
|
|
302
|
+
```text
|
|
303
|
+
GET /action/yes
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
This endpoint returns `yes` as `text/plain`, without a JSON prefix or
|
|
307
|
+
request body. The recorded checks in {doc}`AUDIT.md <audit>` cover 13
|
|
308
|
+
domains.
|
|
309
|
+
|
|
310
|
+
The client exposes this as `echo()`. It strips whitespace from a successful
|
|
311
|
+
response and returns `"no"` for a non-200 response or an HTTP error.
|
|
312
|
+
|
|
313
|
+
## Response shapes
|
|
314
|
+
|
|
315
|
+
Here, `data` is the parsed JSON response. Names in uppercase describe values
|
|
316
|
+
rather than literal JSON.
|
|
317
|
+
|
|
318
|
+
### Search response
|
|
319
|
+
|
|
320
|
+
Type: `b.IssueSearchResponse`.
|
|
321
|
+
|
|
322
|
+
```text
|
|
323
|
+
data[0] = ["b.IssueSearchResponse", ...]
|
|
324
|
+
data[0][6] = [ISSUES, PAGE_TOKEN, TOTAL_COUNT]
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
| Path | Value |
|
|
328
|
+
|------|-------|
|
|
329
|
+
| `[0][6][0]` | Issue arrays |
|
|
330
|
+
| `[0][6][1]` | Next page token, or `null` on the last page |
|
|
331
|
+
| `[0][6][2]` | Approximate total matching issues |
|
|
332
|
+
|
|
333
|
+
### Issue detail response
|
|
334
|
+
|
|
335
|
+
Type: `b.IssueFetchResponse`.
|
|
336
|
+
|
|
337
|
+
```text
|
|
338
|
+
data[0] = ["b.IssueFetchResponse", PAYLOAD]
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
The issue array is usually at `data[0][1][22]`. The parser searches `PAYLOAD`
|
|
342
|
+
from the end and selects the first list with an integer at `[1]`.
|
|
343
|
+
|
|
344
|
+
### Batch response
|
|
345
|
+
|
|
346
|
+
Type: `b.BatchGetIssuesResponse`.
|
|
347
|
+
|
|
348
|
+
```text
|
|
349
|
+
data[0] = ["b.BatchGetIssuesResponse", null, [[ISSUE_1, ISSUE_2, ...]]]
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Issue arrays are at `data[0][2][0]`.
|
|
353
|
+
|
|
354
|
+
### Comments response
|
|
355
|
+
|
|
356
|
+
Type: `b.ListIssueCommentsResponse`.
|
|
357
|
+
|
|
358
|
+
```text
|
|
359
|
+
data[0] = ["b.ListIssueCommentsResponse", [COMMENTS, PAGE_TOKEN, TOTAL_COUNT]]
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
| Path | Value |
|
|
363
|
+
|------|-------|
|
|
364
|
+
| `[0][1][0]` | [Comment arrays](#comment-arrays) |
|
|
365
|
+
| `[0][1][1]` | Next page token, such as `"start_index:2"`, or `null` |
|
|
366
|
+
| `[0][1][2]` | Total text comments |
|
|
367
|
+
|
|
368
|
+
### Updates response
|
|
369
|
+
|
|
370
|
+
Type: `b.ListIssueUpdatesResponse`.
|
|
371
|
+
|
|
372
|
+
```text
|
|
373
|
+
data[0] = ["b.ListIssueUpdatesResponse", [UPDATES, PAGE_TOKEN, TOTAL_COUNT]]
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
| Path | Value |
|
|
377
|
+
|------|-------|
|
|
378
|
+
| `[0][1][0]` | [Update entries](#update-entries) |
|
|
379
|
+
| `[0][1][1]` | Next page token, or `null` |
|
|
380
|
+
| `[0][1][2]` | Total updates |
|
|
381
|
+
|
|
382
|
+
## Issue array
|
|
383
|
+
|
|
384
|
+
`TOP` means one issue array. `details` means `TOP[2]`.
|
|
385
|
+
The reference layout has 48 positions. Recorded search responses also include
|
|
386
|
+
47-element arrays, so array length alone does not identify an issue.
|
|
387
|
+
|
|
388
|
+
| Index | Field | Type | Meaning |
|
|
389
|
+
|-------|-------|------|---------|
|
|
390
|
+
| `[1]` | issue_id | `int` | Issue ID; exposed as `Issue.id` |
|
|
391
|
+
| `[2]` | details | `list` | [Issue metadata](#details-array) |
|
|
392
|
+
| `[4]` | created_at | timestamp | Creation time |
|
|
393
|
+
| `[5]` | modified_at | timestamp | Last change |
|
|
394
|
+
| `[6]` | verified_at | timestamp or `null` | Verification time |
|
|
395
|
+
| `[9]` | star_count | `int \| null` | The parser maps `null` to 0 |
|
|
396
|
+
| `[10]` | unknown | `int` | Recorded value: `3` |
|
|
397
|
+
| `[11]` | comment_count | `int` | Includes updates that only change fields |
|
|
398
|
+
| `[12]` | revision_token | `str` | [Revision token](#revision-token) |
|
|
399
|
+
| `[13]` | owner | user array | Assigned owner |
|
|
400
|
+
| `[14]` | custom_field_defs | `list` | Field definitions; values are in `details[14]` |
|
|
401
|
+
| `[33]` | custom_field_refs | `list[list[int]]` | Field IDs for the component |
|
|
402
|
+
| `[34]` | last_activity_at | timestamp | Last comment or substantive field change, per recorded tests |
|
|
403
|
+
| `[35]` | modified_at_mirror | timestamp | Last write; recorded values match or closely follow `TOP[5]` |
|
|
404
|
+
| `[36]` | blocking_issue_ids | `list[int]` | Issues this issue blocks |
|
|
405
|
+
| `[37]` | relationship_graph | `list` | Recorded shape: `[[this_issue, [[blocked_issue]]]]` |
|
|
406
|
+
| `[40]` | links | `list` | Links from the body: `[[[url], null, type_int]]` |
|
|
407
|
+
| `[41]` | tracker_id | `int \| null` | Tracker ID |
|
|
408
|
+
| `[43]` | body | `list \| null` | Description entry from detail or batch fetches |
|
|
409
|
+
| `[46]` | views | `list \| null` | `[24h_views, 7d_views, 30d_views]`; the parser maps missing counts to 0 |
|
|
410
|
+
| `[47]` | last_modifier | user array | Last person to change the issue |
|
|
411
|
+
|
|
412
|
+
The parser reads views at `[46]` and the last modifier at `[47]`.
|
|
413
|
+
Recorded search arrays can place the last modifier at `[46]`.
|
|
414
|
+
The raw fields above include fields that the `Issue` model does not expose.
|
|
415
|
+
See [parser.py](https://github.com/rly0nheart/bugpipe/blob/master/src/bugpipe/api/parser.py) for the fields it extracts.
|
|
416
|
+
|
|
417
|
+
### Body entry
|
|
418
|
+
|
|
419
|
+
`TOP[43]` contains the description entry. It is `null` in recorded search
|
|
420
|
+
responses. Fetch the issue with detail level `2` to request it.
|
|
421
|
+
|
|
422
|
+
| Index | Field | Type | Meaning |
|
|
423
|
+
|-------|-------|------|---------|
|
|
424
|
+
| `[0]` | text | `str` | Description text, which may contain Markdown |
|
|
425
|
+
| `[1]` | unknown | `null` | Recorded value: `null` |
|
|
426
|
+
| `[2]` | author | user array | Description author |
|
|
427
|
+
| `[3]` | timestamp | timestamp | Description timestamp |
|
|
428
|
+
| `[4]` | unknown | `list` | Recorded value: `[]` |
|
|
429
|
+
| `[5]` | issue_id | `int` | Parent issue |
|
|
430
|
+
| `[6]` | sequence | `int` | Recorded value: `1` |
|
|
431
|
+
|
|
432
|
+
The parser extracts `TOP[43][0]` as `Issue.body`.
|
|
433
|
+
|
|
434
|
+
### Revision token
|
|
435
|
+
|
|
436
|
+
Recorded `TOP[12]` values decode twice from base64 to
|
|
437
|
+
`{issue_id}-{update_rev}-{comment_rev}`. For example:
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
import base64
|
|
441
|
+
|
|
442
|
+
encoded = "TlRFek5USXpORFE0TFRJdE1RPT0="
|
|
443
|
+
plain = base64.b64decode(s=base64.b64decode(s=encoded)).decode()
|
|
444
|
+
issue_id, update_rev, comment_rev = plain.split("-")
|
|
445
|
+
# plain == "513523448-2-1"
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
The recorded observations describe `update_rev` as matching `TOP[11]` and
|
|
449
|
+
`comment_rev` as tracking comment sequence numbers. These values can exceed
|
|
450
|
+
visible counts when comments have been deleted or restricted.
|
|
451
|
+
|
|
452
|
+
In those observations, the token stayed the same across repeated fetches,
|
|
453
|
+
sessions, and detail/batch requests. No captured request sent it back to the
|
|
454
|
+
server. Comparing tokens can detect a revision change. The Python client
|
|
455
|
+
does not expose or send this token.
|
|
456
|
+
|
|
457
|
+
## Details array
|
|
458
|
+
|
|
459
|
+
The reference layout for `TOP[2]` has 32 positions.
|
|
460
|
+
|
|
461
|
+
| Index | Field | Type | Meaning |
|
|
462
|
+
|-------|-------|------|---------|
|
|
463
|
+
| `[0]` | component_id | `int` | Resolve the name with a component request |
|
|
464
|
+
| `[1]` | issue_type | `int` | [Issue type](#issue-type) value |
|
|
465
|
+
| `[2]` | status | `int` | [Status](#status) value |
|
|
466
|
+
| `[3]` | priority | `int` | 1 means P0; 5 means P4 |
|
|
467
|
+
| `[4]` | severity | `int \| null` | 1 means S0; 5 means S4 |
|
|
468
|
+
| `[5]` | title | `str` | Issue title |
|
|
469
|
+
| `[6]` | reporter | user array | Person who filed the issue |
|
|
470
|
+
| `[7]` | verifier | user array | Person who verified the fix |
|
|
471
|
+
| `[9]` | ccs | `list[user_array]` | CC users |
|
|
472
|
+
| `[13]` | hotlist_ids | `list[int]` | Hotlists containing the issue |
|
|
473
|
+
| `[14]` | custom_field_values | `list` | [Custom field entries](#custom-fields) |
|
|
474
|
+
| `[16]` | found_in | `list[str]` | Affected versions; API field name: `found_in_versions` |
|
|
475
|
+
| `[19]` | in_prod | `bool \| null` | `true` means observed in production; the parser preserves only `true`, otherwise `None` |
|
|
476
|
+
| `[21]` | duplicate_issue_ids | `list[int]` | Issues marked as duplicates of this issue |
|
|
477
|
+
| `[30]` | collaborators | `list[user_array]` | Collaborator users |
|
|
478
|
+
| `[31]` | issue_access_level | `list` | Recorded value: `[1]`; meaning unverified |
|
|
479
|
+
|
|
480
|
+
## Custom fields
|
|
481
|
+
|
|
482
|
+
Custom field values are stored at `details[14]`. Trackers can define different
|
|
483
|
+
field IDs. Each entry has this shape:
|
|
484
|
+
|
|
485
|
+
```text
|
|
486
|
+
[field_id, null, null, null, numeric_value, label_values, null, enum_values, null, display_string, ...]
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
| Index | Field | Type |
|
|
490
|
+
|-------|-------|------|
|
|
491
|
+
| `[0]` | field_id | `int` |
|
|
492
|
+
| `[4]` | numeric_value | `int \| float \| null` |
|
|
493
|
+
| `[5]` | label_values | Nested lists of strings |
|
|
494
|
+
| `[7]` | enum_values | Nested lists of strings |
|
|
495
|
+
| `[9]` | display_string | `str \| null` |
|
|
496
|
+
|
|
497
|
+
The parser takes the first usable value in this order: number at `[4]`,
|
|
498
|
+
labels at `[5]`, enum values at `[7]`, then display text at `[9]`.
|
|
499
|
+
It flattens label and enum lists.
|
|
500
|
+
|
|
501
|
+
### Chromium field IDs
|
|
502
|
+
|
|
503
|
+
`CUSTOM_FIELD_IDS` maps these 24 IDs for tracker `157`:
|
|
504
|
+
|
|
505
|
+
| Field ID | Name | Type |
|
|
506
|
+
|----------|-------------------------|-------------|
|
|
507
|
+
| 1222907 | component_tags | `list[str]` |
|
|
508
|
+
| 1223031 | chromium_labels | `list[str]` |
|
|
509
|
+
| 1223032 | design_doc | `str` |
|
|
510
|
+
| 1223033 | build_number | `str` |
|
|
511
|
+
| 1223034 | respin | `str` |
|
|
512
|
+
| 1223081 | flaky_test | `str` |
|
|
513
|
+
| 1223083 | notice | `str` |
|
|
514
|
+
| 1223084 | os | `list[str]` |
|
|
515
|
+
| 1223085 | milestone | `list[str]` |
|
|
516
|
+
| 1223086 | release_block | `list[str]` |
|
|
517
|
+
| 1223087 | merge | `list[str]` |
|
|
518
|
+
| 1223088 | security_release | `list[str]` |
|
|
519
|
+
| 1223131 | design_summary | `str` |
|
|
520
|
+
| 1223134 | merge_request | `list[str]` |
|
|
521
|
+
| 1223135 | vrp_reward | `float` |
|
|
522
|
+
| 1223136 | cve | `list[str]` |
|
|
523
|
+
| 1225154 | next_action | `str` |
|
|
524
|
+
| 1225337 | estimated_days | `float` |
|
|
525
|
+
| 1225362 | backlog_rank | `float` |
|
|
526
|
+
| 1253656 | component_ancestor_tags | `list[str]` |
|
|
527
|
+
| 1300460 | irm_link | `str` |
|
|
528
|
+
| 1358989 | fixed_by_code_changes | `list[str]` |
|
|
529
|
+
| 1410892 | cwe_id | `float` |
|
|
530
|
+
| 1544844 | introduced_in | `str` |
|
|
531
|
+
|
|
532
|
+
The parser stores unknown IDs in `Issue.custom_fields` as `field_{id}`.
|
|
533
|
+
It maps `chromium_labels` to `Issue.labels`. Known fields without a dedicated
|
|
534
|
+
`Issue` attribute, including `design_doc`, `design_summary`, `respin`, and
|
|
535
|
+
`backlog_rank`, remain in `custom_fields` under their mapped names.
|
|
536
|
+
|
|
537
|
+
## User arrays
|
|
538
|
+
|
|
539
|
+
User fields include the reporter, owner, verifier, and CC users. A typical
|
|
540
|
+
array is:
|
|
541
|
+
|
|
542
|
+
```json
|
|
543
|
+
[null, "user@example.com", 1, ["google_domain"]]
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
The parser returns the first string containing `@`. It returns `None` when
|
|
547
|
+
there is no such string. It does not decode the other user fields.
|
|
548
|
+
|
|
549
|
+
## Timestamps
|
|
550
|
+
|
|
551
|
+
Timestamps contain Unix seconds and an optional nanosecond value:
|
|
552
|
+
|
|
553
|
+
```json
|
|
554
|
+
[1657579144, 285000000]
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
The parser treats missing nanoseconds as 0 and returns a UTC `datetime`.
|
|
558
|
+
For this example:
|
|
559
|
+
|
|
560
|
+
```python
|
|
561
|
+
from datetime import datetime, timezone
|
|
562
|
+
|
|
563
|
+
seconds, nanos = [1657579144, 285000000]
|
|
564
|
+
timestamp = datetime.fromtimestamp(
|
|
565
|
+
timestamp=seconds + nanos / 1e9, tz=timezone.utc
|
|
566
|
+
)
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## Enums
|
|
570
|
+
|
|
571
|
+
### Status
|
|
572
|
+
|
|
573
|
+
| API value | Name | Open |
|
|
574
|
+
|-----------|------|------|
|
|
575
|
+
| 1 | NEW | Yes |
|
|
576
|
+
| 2 | ASSIGNED | Yes |
|
|
577
|
+
| 3 | ACCEPTED | Yes |
|
|
578
|
+
| 4 | FIXED | No |
|
|
579
|
+
| 5 | VERIFIED | No |
|
|
580
|
+
| 6 | NOT_REPRODUCIBLE | No |
|
|
581
|
+
| 7 | INTENDED_BEHAVIOR | No |
|
|
582
|
+
| 8 | OBSOLETE | No |
|
|
583
|
+
| 9 | INFEASIBLE | No |
|
|
584
|
+
| 10 | DUPLICATE | No |
|
|
585
|
+
|
|
586
|
+
### Priority and severity
|
|
587
|
+
|
|
588
|
+
Both use values 1 through 5 in the API. The parser subtracts 1 to match the
|
|
589
|
+
Python enums.
|
|
590
|
+
|
|
591
|
+
| API value | Priority | Severity |
|
|
592
|
+
|-----------|----------|----------|
|
|
593
|
+
| 1 | P0 | S0 |
|
|
594
|
+
| 2 | P1 | S1 |
|
|
595
|
+
| 3 | P2 | S2 |
|
|
596
|
+
| 4 | P3 | S3 |
|
|
597
|
+
| 5 | P4 | S4 |
|
|
598
|
+
|
|
599
|
+
### Issue type
|
|
600
|
+
|
|
601
|
+
| API value | Name |
|
|
602
|
+
|-----------|------|
|
|
603
|
+
| 1 | BUG |
|
|
604
|
+
| 2 | FEATURE_REQUEST |
|
|
605
|
+
| 3 | CUSTOMER_ISSUE |
|
|
606
|
+
| 4 | INTERNAL_CLEANUP |
|
|
607
|
+
| 5 | PROCESS |
|
|
608
|
+
| 6 | VULNERABILITY |
|
|
609
|
+
|
|
610
|
+
## Update entries
|
|
611
|
+
|
|
612
|
+
Each update uses this 10-position layout:
|
|
613
|
+
|
|
614
|
+
| Index | Field | Type | Meaning |
|
|
615
|
+
|-------|-------|------|---------|
|
|
616
|
+
| `[0]` | author | user array | Person who made the update |
|
|
617
|
+
| `[1]` | timestamp | timestamp | Update time |
|
|
618
|
+
| `[2]` | comment | `list \| null` | [Comment array](#comment-arrays), if present |
|
|
619
|
+
| `[3]` | sequence_number | `int` | Update sequence |
|
|
620
|
+
| `[4]` | unknown | Unknown | Not parsed |
|
|
621
|
+
| `[5]` | field_changes | `list` | [Field change entries](#field-changes) |
|
|
622
|
+
| `[6]` | comment_number | `int` | Raw comment number |
|
|
623
|
+
| `[7]` | attachments | `list` | Attachment entries |
|
|
624
|
+
| `[8]` | unknown | Unknown | Not parsed |
|
|
625
|
+
| `[9]` | issue_id | `int` | Parent issue |
|
|
626
|
+
|
|
627
|
+
Attachment arrays are at `data[0][1][0][i][7]`. Each starts with:
|
|
628
|
+
|
|
629
|
+
```text
|
|
630
|
+
[attachment_id, mime_type, size_bytes, filename, ...]
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
The parser reads the restriction level at `attachment[9][0][0]`.
|
|
634
|
+
The `AttachmentRestriction` enum maps 1 to `NO_RESTRICTION`, 2 to `RESTRICTED`,
|
|
635
|
+
and 3 to `RESTRICTED_PLUS`.
|
|
636
|
+
|
|
637
|
+
## Comment arrays
|
|
638
|
+
|
|
639
|
+
The parser reads comment fields through index `[18]`. Captured arrays can
|
|
640
|
+
omit trailing fields; the parser returns `None` for missing timestamps and
|
|
641
|
+
user fields.
|
|
642
|
+
|
|
643
|
+
| Index | Field | Type | Meaning |
|
|
644
|
+
|-------|-------|------|---------|
|
|
645
|
+
| `[0]` | body | `str` | Comment text |
|
|
646
|
+
| `[2]` | author | user array | Comment author |
|
|
647
|
+
| `[3]` | modified_at | timestamp | Last edit time; exposed as `Comment.timestamp` |
|
|
648
|
+
| `[4]` | unknown | `list` | Recorded value: `[]` |
|
|
649
|
+
| `[5]` | issue_id | `int` | Parent issue |
|
|
650
|
+
| `[6]` | sequence_number | `int` | Starts at 0 in updates and 1 in `listComments` |
|
|
651
|
+
| `[8]` | unknown | `int` | Recorded values: 1 and 2; meaning unverified |
|
|
652
|
+
| `[9]` | unknown | `list` | Recorded value: `[[1]]`; meaning unverified |
|
|
653
|
+
| `[14]` | comment_token | `str` | Recorded as a stable token per comment; not parsed |
|
|
654
|
+
| `[17]` | last_editor | user array | Last person to edit the comment |
|
|
655
|
+
| `[18]` | created_at | timestamp | Original post time |
|
|
656
|
+
|
|
657
|
+
The parser adds 1 to sequence numbers from `/updates` and preserves those
|
|
658
|
+
from `/listComments`. Both produce `Comment.comment_number` values starting
|
|
659
|
+
at 1.
|
|
660
|
+
|
|
661
|
+
Recorded tests found that `[3]` equals `[18]` for unedited comments and is
|
|
662
|
+
later after an edit. A self-edit can change `[3]` while leaving the author
|
|
663
|
+
and last editor equal.
|
|
664
|
+
|
|
665
|
+
## Field changes
|
|
666
|
+
|
|
667
|
+
Changes are stored at `update[5]`. Each entry has this shape:
|
|
668
|
+
|
|
669
|
+
```text
|
|
670
|
+
[FIELD_NAME, null, OLD_VALUE_WRAPPER, NEW_VALUE_WRAPPER]
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
| Index | Field | Meaning |
|
|
674
|
+
|-------|-------|---------|
|
|
675
|
+
| `[0]` | field_name | Changed field |
|
|
676
|
+
| `[2]` | old_value | Previous value in a typed wrapper |
|
|
677
|
+
| `[3]` | new_value | New value in a typed wrapper |
|
|
678
|
+
|
|
679
|
+
An integer wrapper looks like this:
|
|
680
|
+
|
|
681
|
+
```json
|
|
682
|
+
["type.googleapis.com/google.protobuf.Int32Value", [42]]
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
Recorded field names include `component_id`, `type`, `status`, `priority`,
|
|
686
|
+
`hotlist_ids`, `ccs`, `found_in_versions`, `is_archived`, `is_deleted`, and
|
|
687
|
+
`access_limit`.
|
|
688
|
+
|
|
689
|
+
The parser extracts only the field name. `FieldChange.old_value` and
|
|
690
|
+
`FieldChange.new_value` remain `None`.
|
|
691
|
+
|
|
692
|
+
## Pagination
|
|
693
|
+
|
|
694
|
+
Search, comment, and update responses include a next page token and a total
|
|
695
|
+
count. Search totals are approximate. A `null` token marks the last page.
|
|
696
|
+
|
|
697
|
+
| Request | Where to send the next token |
|
|
698
|
+
|---------|-----------------------------|
|
|
699
|
+
| Search | Request `[6][3]` |
|
|
700
|
+
| Comments | Request `[3]` |
|
|
701
|
+
| Updates, extended form | Request `[3]` |
|
|
702
|
+
|
|
703
|
+
Pass tokens back unchanged. In the client, use
|
|
704
|
+
`next_page(result=search_result)` for searches. For comments, call
|
|
705
|
+
`comments()` with the same issue ID and the result's `next_page_token` as
|
|
706
|
+
`page_token`. The `issue_updates()` method sends one request using the short
|
|
707
|
+
form; it exposes the returned token but does not accept a page token.
|
|
708
|
+
|
|
709
|
+
## Component hierarchy
|
|
710
|
+
|
|
711
|
+
Recorded component lookups place the public trackers under component
|
|
712
|
+
`166797`:
|
|
713
|
+
|
|
714
|
+
```text
|
|
715
|
+
Public Trackers (166797)
|
|
716
|
+
├── Chromium (1362134)
|
|
717
|
+
│ └── Chromium root (1363614)
|
|
718
|
+
│ └── Internals (1456292)
|
|
719
|
+
│ └── Crypto (1768937)
|
|
720
|
+
└── Fuchsia (1360843)
|
|
721
|
+
└── Hardware Platform (1620976)
|
|
722
|
+
└── Zircon Kernel (1478131)
|
|
723
|
+
└── VM (1477815)
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
Issues contain a numeric `component_id`. Resolve its name and parent path
|
|
727
|
+
with `GET /action/components/{id}`, or fetch several components with
|
|
728
|
+
`GET /action/components?id=X&id=Y`.
|