• 
      

    Add the bug tracker Web API resources and fields.

    Review Request #15318 — Created Sept. 17, 2026 and updated

    Information

    Review Board
    release-9.x

    Reviewers

    This change adds API resources for the new bug tracker features:

    /api/bug-trackers/ and /api/bug-trackers/<id>/:

    Read-only listings of the bug trackers usable by the requesting user.
    Disabled trackers, the sentinel, and trackers whose user conditions the
    requester fails are never listed. Configuration management stays in the
    administration UI for this phase.

    /api/bug-trackers/<id>/bugs/?q=&review-request=:

    Typeahead search, implemented with BaseBugTracker.search_bugs(). This
    will the per-integration ajax views that were implemented for Trello and
    Asana. The endpoint requires a review request and verifies, server-side,
    that the tracker applies to it and that the requester passes the
    tracker's user conditions.
    conditions.

    A bugs field on the review request and draft resources:

    Entries of {id, tracker, url?, summary?}, with url/summary omitted
    when the requester fails the tracker's conditions. Draft writes take
    comma-separated [<tracker-id>:]<bug-id> tokens (bare IDs go to the
    default tracker, or stay unattributed). A write replaces the links on
    every tracker the writer passes conditions for; links on trackers the
    writer fails are preserved untouched, and naming such a tracker is an
    error.

    /api/review-requests/?bug=<id> and /api/review-requests/?bug-tracker=<id>:

    Filters on the review request list resource, joining against the bug
    relations.

    Ran unit tests.

    Summary ID
    Add the bug tracker Web API resources and fields.
    This change adds API resources for the new bug tracker features: # `/api/bug-trackers/` and `/api/bug-trackers/<id>/`: Read-only listings of the bug trackers usable by the requesting user. Disabled trackers, the sentinel, and trackers whose user conditions the requester fails are never listed. Configuration management stays in the administration UI for this phase. # `/api/bug-trackers/<id>/bugs/?q=&review-request=`: Typeahead search, implemented with `BaseBugTracker.search_bugs()`. This will the per-integration ajax views that were implemented for Trello and Asana. The endpoint requires a review request and verifies, server-side, that the tracker applies to it and that the requester passes the tracker's user conditions. conditions. # A bugs field on the review request and draft resources: Entries of `{id, tracker, url?, summary?}`, with url/summary omitted when the requester fails the tracker's conditions. Draft writes take comma-separated `[<tracker-id>:]<bug-id>` tokens (bare IDs go to the default tracker, or stay unattributed). A write replaces the links on every tracker the writer passes conditions for; links on trackers the writer fails are preserved untouched, and naming such a tracker is an error. # `/api/review-requests/?bug=<id>` and # `/api/review-requests/?bug-tracker=<id>`: Filters on the review request list resource, joining against the bug relations. Testing Done: Ran unit tests. Fixes bug 1398
    onokvwrqnmxszlwktrmzsotyoxsotrux
    Description From Last Updated

    I don't know what it is, but this description feels a bit opaque, kind of like a technical piece of …

    chipx86 chipx86

    'typing.ClassVar' imported but unused Column: 5 Error code: F401

    reviewbot reviewbot

    'typing.Any' imported but unused Column: 5 Error code: F401

    reviewbot reviewbot

    continuation line under-indented for visual indent Column: 44 Error code: E128

    reviewbot reviewbot

    continuation line under-indented for visual indent Column: 44 Error code: E128

    reviewbot reviewbot

    continuation line under-indented for visual indent Column: 44 Error code: E128

    reviewbot reviewbot

    line too long (82 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    This function feels pretty complex, given the two large conditionals. The biggest piece is the entry-building code, which is subtly …

    chipx86 chipx86

    JSONDict?

    chipx86 chipx86

    We should probably log here?

    chipx86 chipx86

    JSONDict

    chipx86 chipx86

    I get what this is doing but it feels very unclear that this is going to be None or a …

    chipx86 chipx86

    We should log here.

    chipx86 chipx86

    What are we ignoring here and in all the other spots below?

    chipx86 chipx86

    Can you sort the keys in here?

    chipx86 chipx86

    Here and below, what are user conditions? I feel like we should be clear in the docs.

    chipx86 chipx86

    Why the type()? We can get it via the instance. (If it's because we use ClassVar, that only impacts setting.)

    chipx86 chipx86

    We should log an exception here.

    chipx86 chipx86

    We should log an exception here.

    chipx86 chipx86

    It feels like this is a purpose for this resource, but not the only purpose. This is for querying of …

    chipx86 chipx86

    I really don't like using this form. It hurts readability and other variations on this form easily lead to bugs. …

    chipx86 chipx86

    This is a pk, but for Local Sites we need to use a display ID. Otherwise we'd grab the wrong …

    chipx86 chipx86

    We don't use this form and it's doing too much. Can you pull this set-building out and then check against …

    chipx86 chipx86

    It doesn't look like we need to pull out service_cls, since we're just using it immediately below. I also don't …

    chipx86 chipx86

    Can we set these somewhere so they're not magic numbers in here?

    chipx86 chipx86

    The wording could be a bit less terse on the legacy bit. Can we flesh out the guidance a bit? …

    chipx86 chipx86

    All the same notes as above.

    chipx86 chipx86

    And here.

    chipx86 chipx86

    This form makes sense to us but I think it's error-prone for someone working with this. I think it'd be …

    chipx86 chipx86

    We don't have to do all of them, but can you type this one?

    chipx86 chipx86

    The notes above about the format and "Bare bug IDs" apply here.

    chipx86 chipx86

    Same notes here too. I'm not sure what the implications for replacing is, or what the conditions are. I think …

    chipx86 chipx86

    This is missing the full module path.

    chipx86 chipx86

    This should use our standard form. I'm also not sure we should be doing this. Exceptions seem like the right …

    chipx86 chipx86

    Should probably be an if/elif.

    chipx86 chipx86

    One keyword argument per line.

    chipx86 chipx86

    Not sure what the "sentinel" is really meaning here. Just unattributed probably is fine?

    chipx86 chipx86

    Missing parens.

    chipx86 chipx86

    "bug entry" or "bug ID"? We seem to be using both terminology. Also, might be best to use {token!r} so …

    chipx86 chipx86

    Can we query for this and then check the result so this is cleaner? Actually... Doing this once per loop …

    chipx86 chipx86

    Since we're spanning lines, this should be in the standard form of: 'bugs': [ ... ]

    chipx86 chipx86

    Since we're spanning lines, this should be in the standard form of: 'bugs': [ ... ]

    chipx86 chipx86

    Here, too.

    chipx86 chipx86

    And here.

    chipx86 chipx86

    We never use any of this form.

    chipx86 chipx86

    We shouldn't be generating an empty set per-iteration. We can do it once above.

    chipx86 chipx86

    This (and the others below) should be using our URL reversing test helper methods that we use for API tests. …

    chipx86 chipx86

    Here and below, we should be comparing the full payload at once so there are no surprises down the road. …

    chipx86 chipx86

    There should only be one result, right?

    chipx86 chipx86

    We shouldn't need to sort here. The results should be predictable. If we did care, we could set sets. But …

    chipx86 chipx86

    One entry per line. same below.

    chipx86 chipx86

    Missing "Version Added".

    chipx86 chipx86

    This isn't our standard form.

    chipx86 chipx86

    Missing "Version Added".

    chipx86 chipx86
    Checks run (1 failed, 1 succeeded)
    flake8 failed.
    JSHint passed.

    flake8

    david
    Review request changed
    Commits:
    Summary ID
    Add the bug tracker Web API resources and fields.
    This change adds API resources for the new bug tracker features: # `/api/bug-trackers/` and `/api/bug-trackers/<id>/`: Read-only listings of the bug trackers usable by the requesting user. Disabled trackers, the sentinel, and trackers whose user conditions the requester fails are never listed. Configuration management stays in the administration UI for this phase. # `/api/bug-trackers/<id>/bugs/?q=&review-request=`: Typeahead search, implemented with `BaseBugTracker.search_bugs()`. This will the per-integration ajax views that were implemented for Trello and Asana. The endpoint requires a review request and verifies, server-side, that the tracker applies to it and that the requester passes the tracker's user conditions. conditions. # A bugs field on the review request and draft resources: Entries of `{id, tracker, url?, summary?}`, with url/summary omitted when the requester fails the tracker's conditions. Draft writes take comma-separated `[<tracker-id>:]<bug-id>` tokens (bare IDs go to the default tracker, or stay unattributed). A write replaces the links on every tracker the writer passes conditions for; links on trackers the writer fails are preserved untouched, and naming such a tracker is an error. # `/api/review-requests/?bug=<id>` and # `/api/review-requests/?bug-tracker=<id>`: Filters on the review request list resource, joining against the bug relations. Testing Done: Ran unit tests. Fixes bug 1398
    onokvwrqnmxszlwktrmzsotyoxsotrux
    Add the bug tracker Web API resources and fields.
    This change adds API resources for the new bug tracker features: # `/api/bug-trackers/` and `/api/bug-trackers/<id>/`: Read-only listings of the bug trackers usable by the requesting user. Disabled trackers, the sentinel, and trackers whose user conditions the requester fails are never listed. Configuration management stays in the administration UI for this phase. # `/api/bug-trackers/<id>/bugs/?q=&review-request=`: Typeahead search, implemented with `BaseBugTracker.search_bugs()`. This will the per-integration ajax views that were implemented for Trello and Asana. The endpoint requires a review request and verifies, server-side, that the tracker applies to it and that the requester passes the tracker's user conditions. conditions. # A bugs field on the review request and draft resources: Entries of `{id, tracker, url?, summary?}`, with url/summary omitted when the requester fails the tracker's conditions. Draft writes take comma-separated `[<tracker-id>:]<bug-id>` tokens (bare IDs go to the default tracker, or stay unattributed). A write replaces the links on every tracker the writer passes conditions for; links on trackers the writer fails are preserved untouched, and naming such a tracker is an error. # `/api/review-requests/?bug=<id>` and # `/api/review-requests/?bug-tracker=<id>`: Filters on the review request list resource, joining against the bug relations. Testing Done: Ran unit tests. Fixes bug 1398
    onokvwrqnmxszlwktrmzsotyoxsotrux

    Checks run (2 succeeded)

    flake8 passed.
    JSHint passed.
    chipx86
    1. I have to be honest, a lot of this change feels vibe-coded. I really don't want the codebase to lean too heavily towards how Claude prefers to write things (and it's all the same patterns I notice when I use Claude). I'm sure a lot of human effort went into this too, but there's patterns and styling and whole things like the bug tracker unit tests and odd complexity that just full-on are not how we write Review Board code, and I've only marked some of the ones I noticed. I'm seeing this more and more with larger changes and think we need to pull back.

      1. I think the main thing is we're going to need to figure out how to get agents to write code our way, avoid some of the patterns it's trying to use.

        I've personally started with this in my CLAUDE.md:

        ## Python
        
        * We do not use `... if X else Y ` form. Use a standard `if/else` form.
        
        * Dictionaries, lists, sets, and other structures with values across
          multiple lines should be in the form of:
        
          ```python
          [
              item1,
              item2,
              ...,
          ]
          ```
        
        * List comprehensions and other comprehensions must be in a multi-line
          format:
        
          ```python
          [
              item
              for item in ...
              if ...
          ]
          ```
        
        * Blank lines between statements and blocks.
        
        * Functions should have type hints, and they should be in the form of:
        
          ```python
          def function(
              arg1: type1,
              arg2; type2,
          ) -> type:
              ...
          ```
        
        * When a type has a default, the type must be wrapped in params.
          For example: `(type | None) = None`. Do NOT use `type | None = None`.
        
        * Never use `_` as a variable name.
        
        * Don't re-fetch the same instance attributes in a method, or deeply nested
          keys from a dictionary. Prefer pulling out into local variables. This is
          easier to read and review, and more performant.
        
        * For more complex functions, prefer keyword-only arguments rather than
          positional. A positional argument still makes sense if it's directly related
          to the function (e.g., `get_user_details(user, *, ...)`).
        
        * For multi-line call statements, prefer the form:
        
          ```python
          function_call(
              arg1,
              arg2=value2,
              arg3=value3,
          )
          ```
        
          Same for class instantiation.
        
        * Prefer plain English in docstrings and doc comments, and avoid
          overly-technical descriptions.
        

        This is only a start, and it's helped a bit, but it sometimes ignores this. But we'll need to bake in good code consistency so review times are cut (it's hard to focus on substance when the code is littered with Claude-isms that don't match our codebase). The part I don't know how to solve is the overly-verbose approaches Claude sometimes has to solutions (more and more I find its approach to design to be verbose and not always so elegant -- at least compared to what I find you and I tend to write ourselves).

        We'll need to get this nailed down so we can use these tools more effectively and reduce review time.

    2. Show all issues

      I don't know what it is, but this description feels a bit opaque, kind of like a technical piece of a Claude plan rather than something more about the change itself. I've read it a few times and it's just not parsing right in my head.

      That said, second paragraph:

      "... this will the per-integration ..."

      And "conditions." is repeated.

    3. reviewboard/webapi/mixins.py (Diff revision 2)
       
       
       
       
       
       
       
      Show all issues

      This function feels pretty complex, given the two large conditionals.

      The biggest piece is the entry-building code, which is subtly different in both cases. I think it'd be worth consolidating that logic into a helper function both can call, so that it stays the same given the inputs.

    4. reviewboard/webapi/mixins.py (Diff revision 2)
       
       
      Show all issues

      JSONDict?

    5. reviewboard/webapi/mixins.py (Diff revision 2)
       
       
       
      Show all issues

      We should probably log here?

    6. reviewboard/webapi/mixins.py (Diff revision 2)
       
       
      Show all issues

      JSONDict

    7. reviewboard/webapi/mixins.py (Diff revision 2)
       
       
      Show all issues

      I get what this is doing but it feels very unclear that this is going to be None or a value. It reads like a boolean even though the Pythonism will land on the PK or a None value. It'd be nice to instead normalize above what's going into here. Especially since we're doing it once per bug, and we've already checked tracker and handled a PK above.

    8. reviewboard/webapi/mixins.py (Diff revision 2)
       
       
       
      Show all issues

      We should log here.

    9. reviewboard/webapi/mixins.py (Diff revision 2)
       
       
      Show all issues

      What are we ignoring here and in all the other spots below?

    10. Show all issues

      Can you sort the keys in here?

    11. Show all issues

      Here and below, what are user conditions? I feel like we should be clear in the docs.

    12. Show all issues

      Why the type()? We can get it via the instance. (If it's because we use ClassVar, that only impacts setting.)

    13. reviewboard/webapi/resources/bug_tracker.py (Diff revision 2)
       
       
       
      Show all issues

      We should log an exception here.

    14. reviewboard/webapi/resources/bug_tracker.py (Diff revision 2)
       
       
       
      Show all issues

      We should log an exception here.

    15. reviewboard/webapi/resources/bug_tracker_bugs.py (Diff revision 2)
       
       
       
       
      Show all issues

      It feels like this is a purpose for this resource, but not the only purpose. This is for querying of bugs, isn't it?

    16. Show all issues

      I really don't like using this form. It hurts readability and other variations on this form easily lead to bugs. Can we properly normalize these kinds of things?

    17. Show all issues

      This is a pk, but for Local Sites we need to use a display ID. Otherwise we'd grab the wrong review request and end up with either unwanted Access Denied errors or lookups on the wrong review request.

      Callers on a Local Site should never have access to the actual pk and should only have the local_id, so if that's not the case here, that'll need to be fixed.

    18. reviewboard/webapi/resources/bug_tracker_bugs.py (Diff revision 2)
       
       
       
       
       
      Show all issues

      We don't use this form and it's doing too much. Can you pull this set-building out and then check against it?

    19. reviewboard/webapi/resources/bug_tracker_bugs.py (Diff revision 2)
       
       
       
       
      Show all issues

      It doesn't look like we need to pull out service_cls, since we're just using it immediately below. I also don't think we need type() for the reasons outlined above.

    20. Show all issues

      Can we set these somewhere so they're not magic numbers in here?

    21. reviewboard/webapi/resources/review_request.py (Diff revision 2)
       
       
       
       
      Show all issues

      The wording could be a bit less terse on the legacy bit. Can we flesh out the guidance a bit?

      Also we need a deprecated_in key.

    22. reviewboard/webapi/resources/review_request_draft.py (Diff revision 2)
       
       
       
       
       
       
       
      Show all issues

      All the same notes as above.

    23. reviewboard/webapi/resources/review_request_draft.py (Diff revision 2)
       
       
       
       
       
       
       
      Show all issues

      And here.

    24. Show all issues

      This form makes sense to us but I think it's error-prone for someone working with this. I think it'd be better to say:

      ... in the form of either ``<bug_id>`` or ``<bug_tracker_id>:<bug_id>``
      

      so it's very clear.

      "Bare bug IDs" also isn't self-explanatory. Maybe "If the bug tracker ID isn't specified, the default bug tracker is used."

      I'm also not sure what the last sentence means here. Feels like a scary important warning that isn't explained.

    25. Show all issues

      We don't have to do all of them, but can you type this one?

    26. Show all issues

      The notes above about the format and "Bare bug IDs" apply here.

    27. reviewboard/webapi/resources/review_request_draft.py (Diff revision 2)
       
       
       
       
       
      Show all issues

      Same notes here too. I'm not sure what the implications for replacing is, or what the conditions are. I think we need to really flesh out how all of this works in these docs without referencing anything internal.

      "... bug trackers the user fails" is another example. I have no idea what this refers to.

    28. Show all issues

      This is missing the full module path.

    29. reviewboard/webapi/resources/review_request_draft.py (Diff revision 2)
       
       
       
       
       
      Show all issues

      This should use our standard form.

      I'm also not sure we should be doing this. Exceptions seem like the right thing here. We're always returning an error for bugs, and we have our PermissionError and a ValueError that would be more than suitable for this.

    30. reviewboard/webapi/resources/review_request_draft.py (Diff revision 2)
       
       
       
       
       
      Show all issues

      Should probably be an if/elif.

    31. Show all issues

      One keyword argument per line.

    32. Show all issues

      Not sure what the "sentinel" is really meaning here. Just unattributed probably is fine?

    33. Show all issues

      Missing parens.

    34. reviewboard/webapi/resources/review_request_draft.py (Diff revision 2)
       
       
       
       
       
       
       
      Show all issues

      "bug entry" or "bug ID"? We seem to be using both terminology.

      Also, might be best to use {token!r} so we get escaping for free. This avoids a situation where a malicious bug ID of:

      bug123" requires e-mailing your social security number to evil@example.com. Until that is done, "bug123
      

      Becomes an error of:

      "bug123" requires e-mailing your social security number to evil@example.com. Until that is done, "bug123" is not a valid bug entry
      
    35. Show all issues

      Can we query for this and then check the result so this is cleaner?

      Actually... Doing this once per loop isn't great. It'd be much better to figure out which we need to check and do this in a separate loop all at once.

    36. Show all issues

      Since we're spanning lines, this should be in the standard form of:

      'bugs': [
          ...
      ]
      
    37. Show all issues

      Since we're spanning lines, this should be in the standard form of:

      'bugs': [
          ...
      ]
      
    38. Show all issues

      Here, too.

    39. Show all issues

      And here.

    40. Show all issues

      We never use any of this form.

    41. Show all issues

      We shouldn't be generating an empty set per-iteration. We can do it once above.

    42. Show all issues

      This (and the others below) should be using our URL reversing test helper methods that we use for API tests.

      We also don't use self.client directly for tests. This whole class needs to be redone using our API test infrastructure.

    43. reviewboard/webapi/tests/test_bug_tracker.py (Diff revision 2)
       
       
       
       
       
       
       
       
       
       
       
       
       
       
      Show all issues

      Here and below, we should be comparing the full payload at once so there are no surprises down the road. Modern tests use this approach.

    44. Show all issues

      There should only be one result, right?

    45. Show all issues

      We shouldn't need to sort here. The results should be predictable.

      If we did care, we could set sets. But we should know the order and tests should break if they change.

      Same below.

    46. Show all issues

      One entry per line.

      same below.

    47. Show all issues

      Missing "Version Added".

    48. Show all issues

      This isn't our standard form.

    49. Show all issues

      Missing "Version Added".

    50.