Add a new batch operation view
Review Request #12695 — Created Oct. 20, 2022 and submitted
For upcoming feature work, we want the ability to make a single request
to publish a bunch of individual items (review requests and reviews) all
together.To do this, we're introducing a new API-like endpoint for performing
batch operations. This doesn't exist in the official API because it
doesn't really mesh well with REST, and the actual endpoint may be in
flux for a bit.We already have some batch-like operations in the dashboard UI that
currently just make a whole bunch of API requests for closing review
requests or changing visibility state. This new endpoint will also
enable these workflows, so we can handle it much more efficiently.The new batch endpoint supports these operations:
- close
- discard
- archive
- mute
- unarchive (effectively unmute too)
- publish
- Looked at e-mail previews for batch publishes with all combinations of
review requests, review request updates, reviews, and review replies in
both HTML and text mode. - Used all operations except for publish from real UI.
- Ran unit tests.
Summary | ID |
---|---|
b5eb81a05d4a56fef375d145d9a39d848b916382 |
Description | From | Last Updated |
---|---|---|
I don't see any tests for when there's more than one review or review request and one of them fails … |
maubin | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
local variable 'draft' is assigned to but never used Column: 9 Error code: F841 |
reviewbot | |
local variable 'draft' is assigned to but never used Column: 9 Error code: F841 |
reviewbot | |
local variable 'draft' is assigned to but never used Column: 9 Error code: F841 |
reviewbot | |
local variable 'draft' is assigned to but never used Column: 9 Error code: F841 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line unaligned for hanging indent Column: 21 Error code: E131 |
reviewbot | |
continuation line over-indented for hanging indent Column: 21 Error code: E126 |
reviewbot | |
continuation line over-indented for hanging indent Column: 21 Error code: E126 |
reviewbot | |
line too long (81 > 79 characters) Column: 80 Error code: E501 |
reviewbot | |
continuation line unaligned for hanging indent Column: 17 Error code: E131 |
reviewbot | |
continuation line over-indented for hanging indent Column: 21 Error code: E126 |
reviewbot | |
continuation line over-indented for hanging indent Column: 21 Error code: E126 |
reviewbot | |
Missing , optional |
chipx86 | |
Mising , optional |
chipx86 | |
Missing , optional |
chipx86 | |
While here, can you move this above Args (and make it Version Changed)? |
chipx86 | |
This should also go up above and use Version Changed. We've mostly been doing it in order from newest to … |
chipx86 | |
Since this is a private utility endpoint, let's call this _batch/. |
chipx86 | |
Needs a Version Added. |
chipx86 | |
Args are indented too far. |
chipx86 | |
We need to use the accessible queries, or this will end up letting people perform operations on review requests they … |
chipx86 | |
In the common case, we'll have the right number of IDs. So instead of a separate COUNT() query, let's jsut … |
chipx86 | |
We can simplify this if we just define the proper ID field in the conditional and perform the lookup outside … |
chipx86 | |
Let's sort this result, so errors are consistent. |
chipx86 | |
Can we move this entirely into its own function, like the other batch operations? |
chipx86 | |
This also needs to be an accessible check. |
chipx86 | |
Same note above about saving a COUNT() query. |
chipx86 | |
Given the complexity of the arguments (and usage of flags), it'd be nice to force keyword arguments. Same for others. |
chipx86 | |
For consistency with other errors elsewhere, these should all end with a period. |
chipx86 | |
Since we need to pull drafts, our initial query should do a prefetch or a select_related. |
chipx86 | |
We should catch errors here. Extensions can block publishes. |
chipx86 | |
We should catch errors here. |
chipx86 | |
Could mention this PublishError in the docstring. |
maubin | |
Should we check that the error messages in the responses match what we expect them to be? |
maubin | |
Same here and for other invalid requests. I think it would be a good idea to check the error message … |
maubin | |
Seems like there should be a "with" here: "publish op with archive after publish". |
maubin | |
Should we add typing here and for any other helper methods in test files? |
maubin | |
local variable 'queries' is assigned to but never used Column: 9 Error code: F841 |
reviewbot | |
line too long (80 > 79 characters) Column: 80 Error code: E501 |
reviewbot | |
local variable 'queries' is assigned to but never used Column: 9 Error code: F841 |
reviewbot | |
'typing.TYPE_CHECKING' imported but unused Column: 1 Error code: F401 |
reviewbot | |
For functions that take lots of arguments, I'd like to get in the habit of making the arguments keyword-only (short … |
chipx86 | |
This argument isn't documented. |
chipx86 | |
This should have a Version Added. |
chipx86 | |
The description and argument name don't seem to match? Can we clarify the name? |
chipx86 | |
The type for user wouldn't allow this, so I think the type should probably be Optional[User]? Also, can you run … |
chipx86 | |
I'd expect updating this to fail type checking. Can we add types for this and anything else that doesn't infer … |
chipx86 | |
If not properly prepared for this function, the following will result in database queries: review.comments review.user reply.comments reply.user reply.base_reply_to That … |
chipx86 | |
We should add a Dict[str, Any] type here. |
chipx86 | |
T before U. |
chipx86 | |
Can you add -> str:? |
chipx86 | |
Can you update this to use the style used elsewhere where each arg gets its own line? |
chipx86 | |
Trying to keep newer versions first, since those are the ones more relevant when reading through docs. |
chipx86 | |
Rather than Union[..., None], let's do Optional[Union[...]]. While equivalent, it conveys the intent a bit better, and is more consistent. |
chipx86 | |
The ) should be on the next line, with a return type. This parameter should have a trailing comma. |
chipx86 | |
The descriptions need to be indented 1 more space. |
chipx86 | |
While these are being changed, can we update them to use the full class path? |
chipx86 | |
These should be swapped. |
chipx86 | |
reviewboard.testing should be after reviewboard.site. |
chipx86 | |
No period needed here. |
maubin | |
No period needed here. |
maubin | |
We could do a .only('visibility') here and reduce what the test has to fetch. |
chipx86 | |
This could be a good thing to add to Djblets' TestCase class, would be useful for other API tests. |
maubin | |
Can we add doc comments for each of these? Especially given that last comment. |
chipx86 | |
Left-over debugging. |
chipx86 | |
This is undocumented. |
chipx86 | |
Given exc_info=True, this can be logger.exception(). Same with others. |
chipx86 | |
This could probably fit fine with user= beginning on the review.publish line. |
chipx86 | |
Blank line between these. |
chipx86 | |
Params can probably begin on the send_email( line. |
chipx86 | |
Might be worth adding a bulk-updating function for visibility. (Doesn't have to be this change.) |
chipx86 | |
This is undocumented. |
chipx86 | |
As above, this can be logger.exception |
chipx86 | |
This is undocumented. |
chipx86 | |
As above, it'd be nice to support bulk visibility updating. |
chipx86 | |
Can you add a Version Added at the end of this? |
chipx86 | |
Should add a Raises for the Http404. |
chipx86 | |
Can you sort these alphabetically? |
chipx86 | |
It'd be nice to do one query with the combined list, and then sort them into reviews vs. replies in-code. … |
chipx86 | |
continuation line under-indented for visual indent Column: 17 Error code: E128 |
reviewbot |
- Commits:
-
Summary ID e16399f8f426d7ca77f93c381d773b2973993f8a 3dca1f0dc175db6a2c9106c2e11127085492648b - Diff:
-
Revision 2 (+2376 -80)
Checks run (1 failed, 1 succeeded)
flake8
- Commits:
-
Summary ID 3dca1f0dc175db6a2c9106c2e11127085492648b 6345515bd6240f4cc80d30de2a3ab943eb5f6753 - Diff:
-
Revision 3 (+2376 -80)
Checks run (2 succeeded)
-
Looks pretty good so far.
-
I wonder if it would be worthwhile for failed responses to include comprehensive information about every object that causes a fail, instead of just returning a response for the first one that causes a fail. That way people wouldn't have to make a bunch of batch requests and keep discovering errors one by one. But I don't know if it makes sense to return lists of status codes and error messages in a response. What might be better is to gather lists of objects that cause each error, and then check if each list is non-empty, if so return a failed response for that type of error and include all of the items in the list in the error message. So messages would be like
User does not have permission to publish review request(s): 1, 2, 3.
.
-
Overall design looks good. Just to help solidify it, it'd be nice to document the format of each possible request payload in the class's docstring.
Some of my comments are things that I know are in flight (and even mentioned in the description), but I'm leaving my thoughts anyway.
The most critical two things in my mind are:
- This needs to be more query-efficient. There are places we're going to be repeatedly querying the database, which can lead to poor performance. Every unit test should use
assertQueries()
. - There are some potential security issues in the way that review requests and reviews are being fetched. We should make sure to use accessible checks for possible review requests or reviews, so we centralize that logic and avoid exposing information about review requests or reviews that should be hidden.
Regarding the validation stuff mentioned in the review request description...
I like the idea of being able to validate all the publishes before we publish, and would prefer that if it was easy to do. But I don't think it is. There are a couple of problems:
- We could still get errors from writing the review request (such as conflicts) or from the
review_request_published
signal. - We'd be changing the nature of validation (an extension signal handler on
review_request_publishing
responding to review request #2 might be sensitive to the published state of review request #1, or that of state expected to be handled in areview_request_published
).
(The second point presents a larger issue of, do we need to think about the order in which review requests are published? E.g., following
depends_on
. That's probably not something to solve right now, but worth considering.)I think ultimately, we'd be better off doing the full publish operation one-by-one. It's less work, it's probably safer, and it keeps in line with standard behavior.
I do think we should bail at the first review request that fails, though. That's less likely to leave the rest of the review requests in a bad state.
We can provide a list of all review request IDs that published and all that have not, in the order of publish, allowing the caller to continue where it left off if necessary. (Especially useful if we do later make this public API in some form.)
- This needs to be more query-efficient. There are places we're going to be repeatedly querying the database, which can lead to poor performance. Every unit test should use
-
-
-
-
-
This should also go up above and use
Version Changed
.We've mostly been doing it in order from newest to oldest.
-
-
-
-
We need to use the accessible queries, or this will end up letting people perform operations on review requests they don't have access to.
Even if we perform access checks later, we don't want to risk the API being used to even infer anything about inaccessible IDs.
-
In the common case, we'll have the right number of IDs. So instead of a separate
COUNT()
query, let's jsut fetch thefound_ids
and then compare counts. This will save a query per batch operation in most cases. -
We can simplify this if we just define the proper ID field in the conditional and perform the lookup outside of it.
-
-
-
-
-
Given the complexity of the arguments (and usage of flags), it'd be nice to force keyword arguments.
Same for others.
-
-
-
-
- Description:
-
For upcoming feature work, we want the ability to make a single request
to publish a bunch of individual items (review requests and reviews) all together. To do this, we're introducing a new API-like endpoint for performing
batch operations. This doesn't exist in the official API because it doesn't really mesh well with REST, and the actual endpoint may be in flux for a bit. We already have some batch-like operations in the dashboard UI that
currently just make a whole bunch of API requests for closing review requests or changing visibility state. This new endpoint will also enable these workflows, so we can handle it much more efficiently. ~ This is work-in-progress change that does most of what I have planned.
~ This is now mostly complete, and just needs a lot more testing.
- There are two major pieces left to do: - - - Handle the partial-publish case where we get some errors. I've not
yet decided whether to proceed with what we can and return a "mixed"
status with errors, or refactor the publish path inside
ReviewRequestDraft to allow us to do all prep and validation before
actually publishing anything.
- - Create the new bulk e-mail.
- - Unit tests exist for most of the functionality but I'll be expanding
- those too. - Handle the partial-publish case where we get some errors. I've not
- Commits:
-
Summary ID 6345515bd6240f4cc80d30de2a3ab943eb5f6753 3bb11bb0631834e35be316e4a522af561bbeaab4 - Diff:
-
Revision 4 (+3890 -98)
Checks run (2 succeeded)
- Change Summary:
-
- Add an unarchive op for the dashboard.
- Remove WIP tag.
- Summary:
-
[WIP] Add a new batch operation viewAdd a new batch operation view
- Description:
-
For upcoming feature work, we want the ability to make a single request
to publish a bunch of individual items (review requests and reviews) all together. To do this, we're introducing a new API-like endpoint for performing
batch operations. This doesn't exist in the official API because it doesn't really mesh well with REST, and the actual endpoint may be in flux for a bit. We already have some batch-like operations in the dashboard UI that
currently just make a whole bunch of API requests for closing review requests or changing visibility state. This new endpoint will also enable these workflows, so we can handle it much more efficiently. ~ This is now mostly complete, and just needs a lot more testing.
~ The new batch endpoint supports these operations:
+ + - close
+ - discard
+ - archive
+ - mute
+ - unarchive (effectively unmute too)
+ - publish
- Testing Done:
-
~ Ran new unit tests.
~ - Looked at e-mail previews for batch publishes with all combinations of
review requests, review request updates, reviews, and review replies in
both HTML and text mode.
+ - Used all operations except for publish from real UI.
+ - Ran unit tests.
- Looked at e-mail previews for batch publishes with all combinations of
- Commits:
-
Summary ID 3bb11bb0631834e35be316e4a522af561bbeaab4 09fbb06bc9467cec6dbd16857f2685e1e156f7fa - Diff:
-
Revision 5 (+4104 -98)
Checks run (2 succeeded)
-
This is a lot of work, nice job!
-
I don't see any tests for when there's more than one review or review request and one of them fails to publish/close (leading to a
mixed
stat in the response). -
-
-
Same here and for other invalid requests. I think it would be a good idea to check the error message of the response so that we know exactly why the request is failing, and that this matches what we expect.
-
-
- Change Summary:
-
- Fix an issue with publishing replies via batch.
- Check response content to make sure errors match what we expect.
- Fix up response content when reviews fail to publish.
- Make other requested changes.
- Commits:
-
Summary ID 09fbb06bc9467cec6dbd16857f2685e1e156f7fa d4efc48a6d893e6359b9230311701866ec5443a7 - Diff:
-
Revision 6 (+4446 -108)
- Commits:
-
Summary ID d4efc48a6d893e6359b9230311701866ec5443a7 05d393423b789f91015102827ca7efdc573d0d8b - Diff:
-
Revision 7 (+4448 -108)
Checks run (2 succeeded)
- Change Summary:
-
Add unit tests for mixed-result publish operations.
- Commits:
-
Summary ID 05d393423b789f91015102827ca7efdc573d0d8b 96aa656d4b2ef0004687d7554cfb1697e53ba237 - Diff:
-
Revision 8 (+4618 -108)
- Commits:
-
Summary ID 96aa656d4b2ef0004687d7554cfb1697e53ba237 1ae43bad7d6571f875172812e64acb1092853942 - Diff:
-
Revision 9 (+4618 -108)
Checks run (2 succeeded)
-
A lot of this is typing or doc stuff.
Some of it is optimization questions.
Overall though, it looks great!
-
For functions that take lots of arguments, I'd like to get in the habit of making the arguments keyword-only (short of any that make sense as positional). This helps avoid complications down the road if we're trying to add or, especially, deprecate arguments.
Can we add a
*
as the first argument here? -
-
-
-
The type for
user
wouldn't allow this, so I think the type should probably beOptional[User]
?Also, can you run the type checker through this file again? There's some things in here I'd expect it to notice.
-
I'd expect updating this to fail type checking. Can we add types for this and anything else that doesn't infer from a call?
-
If not properly prepared for this function, the following will result in database queries:
review.comments
review.user
reply.comments
reply.user
reply.base_reply_to
That can get expensive fast.
There's a lot going into this. Can we verify that we're not re-querying anything in this code here? If we are, we should fix that up before the call.
Ideally this call would be able to assert that it won't result in any new calls, but that means digging into model caches.. :/
-
-
-
-
-
Trying to keep newer versions first, since those are the ones more relevant when reading through docs.
-
Rather than
Union[..., None]
, let's doOptional[Union[...]]
. While equivalent, it conveys the intent a bit better, and is more consistent. -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
It'd be nice to do one query with the combined list, and then sort them into reviews vs. replies in-code. One less database query.
- Commits:
-
Summary ID 1ae43bad7d6571f875172812e64acb1092853942 52821246faaabea774e0da4e7682a230f77beeec - Diff:
-
Revision 10 (+4706 -110)
- Commits:
-
Summary ID 52821246faaabea774e0da4e7682a230f77beeec b5eb81a05d4a56fef375d145d9a39d848b916382 - Diff:
-
Revision 11 (+4706 -110)