Skip to content

Commit

Permalink
Merge pull request #770 from opensafely-core/rw/return-whenever
Browse files Browse the repository at this point in the history
Allow reviewers to return requests at any point
  • Loading branch information
rw251 authored Feb 13, 2025
2 parents 9a40af3 + e89c2bf commit 3c332cd
Show file tree
Hide file tree
Showing 68 changed files with 49 additions and 50 deletions.
2 changes: 1 addition & 1 deletion DEVELOPERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Diagrams

* [Request State Machine](docs/request-states.md) (auto-generated)
* [Request State Machine](docs/reference/request-states.md) (auto-generated)


## Prerequisites for local development
Expand Down
2 changes: 2 additions & 0 deletions airlock/business_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,9 +406,11 @@ def get_approved_requests(self, user: User):
],
RequestStatus.SUBMITTED: [
RequestStatus.PARTIALLY_REVIEWED,
RequestStatus.RETURNED,
],
RequestStatus.PARTIALLY_REVIEWED: [
RequestStatus.REVIEWED,
RequestStatus.RETURNED,
],
RequestStatus.REVIEWED: [
RequestStatus.APPROVED,
Expand Down
23 changes: 12 additions & 11 deletions airlock/templates/file_browser/request/request.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,18 @@
{% endif %}
{% endif %}
{% if content_buttons.return.show %}
{% if content_buttons.return.disabled %}
{% #button disabled=True class="relative group" small=True variant="secondary" id="return-request-button" %}
Return request
{% tooltip class="airlock-tooltip" content=content_buttons.return.tooltip %}
{% /button %}
{% else %}
<form action="{{ content_buttons.return.url }}" method="POST">
{% csrf_token %}
{% #button type="submit" small=True tooltip=content_buttons.return.tooltip variant="secondary" id="return-request-button" %}Return request{% /button %}
</form>
{% endif %}
{% #modal id="returnRequest" button_small=True button_text="Return request" button_variant="secondary" button_tooltip=content_buttons.return.tooltip %}
{% #card container=True title="Return this request" %}
<form action="{{ content_buttons.return.url }}" method="POST">
{% csrf_token %}
<div class="pb-8">
{{content_buttons.return.modal_confirm_message}}
</div>
{% #button type="submit" variant="warning" class="action-button" small=True id="return-request-button" %}Return request{% /button %}
{% #button variant="primary" type="cancel" small=True %}Cancel{% /button %}
</form>
{% /card %}
{% /modal %}
{% endif %}
{% if content_buttons.release_files.show %}
{% if content_buttons.release_files.disabled %}
Expand Down
12 changes: 10 additions & 2 deletions airlock/views/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ def _get_request_button_context(user, release_request):
]:
button.show = True

# We allow output checkers to review at any time
return_btn.disabled = False

try:
permissions.check_user_can_submit_review(user, release_request)
except exceptions.RequestReviewDenied as err:
Expand All @@ -143,11 +146,16 @@ def _get_request_button_context(user, release_request):

if release_request.status == RequestStatus.REVIEWED:
reject_btn.disabled = False
return_btn.disabled = False
return_btn.tooltip = "Return request for changes/clarification"
return_btn.modal_confirm_message = "All reviews have been submitted. Are you ready to return the request to the original author?"
else:
reject_btn.tooltip = "Rejecting a request is disabled until review has been submitted by two reviewers"
return_btn.tooltip = "Returning a request is disabled until review has been submitted by two reviewers"
return_btn.tooltip = "Return request before full review"
return_btn.modal_confirm_message = (
"Are you sure you want to return this request before both reviews "
"are submitted? Typically you would only do this if the request "
"author has asked for an early return to make changes."
)

if not release_request.can_be_released():
release_files_btn.tooltip = "Releasing to jobs.opensafely.org is disabled until all files have been approved by by two reviewers"
Expand Down
2 changes: 1 addition & 1 deletion assets/templates/_components/modal/modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% if custom_button %}
{{ custom_button }}
{% else %}
{% #button class=button_class|default:"flex-shrink-0" small=button_small|default:False variant=button_variant|default:"primary" data-modal=id %}
{% #button class=button_class|default:"flex-shrink-0" small=button_small|default:False variant=button_variant|default:"primary" tooltip=button_tooltip data-modal=id %}
{{ button_text }}
{% /button %}
{% endif %}
Expand Down
4 changes: 3 additions & 1 deletion docs/explanation/workflow-and-permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ flowchart TD
subgraph Release Request Workflow
A([PENDING]):::author -- Adds files and submits --> B([SUBMITTED]):::checker
B -- 1st independent review --> C([PARTIALLY_REVIEWED]):::checker
B -- Return without review --> E([RETURNED]):::author
C -- 2nd independent review --> D([REVIEWED]):::checker
D -- Consolidation --> E([RETURNED]):::author
C -- Return prior to full review --> E
D -- Consolidation --> E
E -- Updates and responds to questions --> B
D -- All files approved ----> F(RELEASED)
D -- Not approved ----> G(REJECTED)
Expand Down
2 changes: 2 additions & 0 deletions docs/how-tos/create-and-submit-a-release-request.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,5 @@ Your release request's status will transition to "Submitted", and you will no lo
able to edit it. Output checkers will be [automatically notified](../explanation/notifications.md) that the request is ready for review.

![Submitted request](../screenshots/submitted_request.png)

If you have made a mistake on a submitted request, you can contact the output checkers. They will be able to return the request so you can make changes.
4 changes: 2 additions & 2 deletions docs/how-tos/review-a-request.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ It also shows a log of recent activity.

This page also contains buttons for various actions that can be performed
on the request as a whole (submitting your review, returning the request to
the researcher etc.). In the example below, all buttons are disabled, as
this request has not yet been reviewed.
the researcher etc.). In the example below, all buttons (except "Return request")
are disabled, as this request has not yet been reviewed.

![Request overview](../screenshots/request_overview.png)

Expand Down
2 changes: 2 additions & 0 deletions docs/reference/request-states.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ stateDiagram-v2
PENDING --> SUBMITTED
PENDING --> WITHDRAWN
SUBMITTED --> PARTIALLY_REVIEWED
SUBMITTED --> RETURNED
PARTIALLY_REVIEWED --> REVIEWED
PARTIALLY_REVIEWED --> RETURNED
REVIEWED --> APPROVED
REVIEWED --> REJECTED
REVIEWED --> RETURNED
Expand Down
20 changes: 0 additions & 20 deletions docs/request-states.md

This file was deleted.

Binary file modified docs/screenshots/add_file_button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/add_file_modal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/changed_tree_file.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/context_and_controls.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/context_modal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/file_approved.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/file_group.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/file_review.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/file_update.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/file_update_modal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/files_released.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_anchor_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_anchor_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_blockquote_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_blockquote_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_bold_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_bold_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_code_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_code_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_italics_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_italics_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_ordered_list_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_ordered_list_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_unordered_list_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/markdown_comment_unordered_list_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/more_dropdown.png
Binary file modified docs/screenshots/more_dropdown_el.png
Binary file modified docs/screenshots/more_dropdown_el_request_file.png
Binary file modified docs/screenshots/multiselect_update.png
Binary file modified docs/screenshots/ready_to_release.png
Binary file modified docs/screenshots/request_independent_review_file_icons.png
Binary file modified docs/screenshots/request_overview.png
Binary file modified docs/screenshots/request_reviewed_file_icons.png
Binary file modified docs/screenshots/request_tree.png
Binary file modified docs/screenshots/request_tree_post_voting.png
Binary file modified docs/screenshots/returned_request_comments.png
Binary file modified docs/screenshots/returned_tree.png
Binary file modified docs/screenshots/reviewed_request_comments.png
Binary file modified docs/screenshots/reviews_index.png
Binary file modified docs/screenshots/submit_request.png
Binary file modified docs/screenshots/submit_review.png
Binary file modified docs/screenshots/submitted_request.png
Binary file modified docs/screenshots/submitted_review.png
Binary file modified docs/screenshots/withdraw_request.png
Binary file modified docs/screenshots/withdraw_request_modal.png
Binary file modified docs/screenshots/withdrawn_file.png
Binary file modified docs/screenshots/workspace_directory_content.png
Binary file modified docs/screenshots/workspace_directory_view.png
Binary file modified docs/screenshots/workspace_file_icons.png
Binary file modified docs/screenshots/workspace_file_view.png
Binary file modified docs/screenshots/workspace_view.png
Binary file modified docs/screenshots/workspaces_index.png
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ manifests:


# generate the automated state diagrams from code
state-diagram file="docs/request-states.md":
state-diagram file="docs/reference/request-states.md":
cat scripts/statemachine.py | {{ just_executable() }} manage shell > {{ file }}

# Run the documentation server: to configure the port, append: ---dev-addr localhost:<port>
Expand Down
1 change: 1 addition & 0 deletions tests/functional/test_docs_screenshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ def do_review(screenshot=True):

# Return to researcher
page.goto(live_server.url + release_request.get_url())
page.locator("button[data-modal=returnRequest]").click()
page.locator("#return-request-button").click()

# Responding to returned request
Expand Down
18 changes: 9 additions & 9 deletions tests/functional/test_request_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ def test_request_buttons(
page.goto(live_server.url + release_request.get_url())

reviewer_buttons = [
"#return-request-button",
"button[data-modal=returnRequest]",
"button[data-modal=rejectRequest]",
"#submit-review-button",
]
Expand Down Expand Up @@ -463,8 +463,8 @@ def test_resubmit_button_visibility(
"login_as,status,checkers, can_return",
[
("author", RequestStatus.SUBMITTED, None, False),
("checker1", RequestStatus.PARTIALLY_REVIEWED, ["checker1"], False),
("checker2", RequestStatus.PARTIALLY_REVIEWED, ["checker1"], False),
("checker1", RequestStatus.PARTIALLY_REVIEWED, ["checker1"], True),
("checker2", RequestStatus.PARTIALLY_REVIEWED, ["checker1"], True),
("checker1", RequestStatus.REVIEWED, ["checker1", "checker2"], True),
],
)
Expand Down Expand Up @@ -506,18 +506,18 @@ def test_request_returnable(
)

login_as_user(live_server, context, user_data[login_as])
return_request_button = page.locator("#return-request-button")
return_request_button = page.locator("button[data-modal=returnRequest]")
modal_return_request_button = page.locator("#return-request-button")
page.goto(live_server.url + release_request.get_url())

if can_return:
expect(return_request_button).to_be_enabled()
expect(modal_return_request_button).not_to_be_visible()
return_request_button.click()
expect(return_request_button).not_to_be_visible()
expect(modal_return_request_button).to_be_visible()

elif login_as == "author":
expect(return_request_button).not_to_be_visible()
else:
expect(return_request_button).to_be_disabled()
expect(return_request_button).not_to_be_visible()


def test_returned_request(live_server, context, page, bll):
Expand Down Expand Up @@ -597,7 +597,7 @@ def test_request_releaseable(live_server, context, page, bll):

release_files_button = page.locator("#release-files-button")
reject_request_button = page.locator("button[data-modal=rejectRequest]")
return_request_button = page.locator("#return-request-button")
return_request_button = page.locator("button[data-modal=returnRequest]")

# Request is currently reviewed twice
# output checker can release, return or reject
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/views/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def test_request_view_with_submitted_request(airlock_client):
response = airlock_client.get(f"/requests/view/{release_request.id}", follow=True)
assert "Rejecting a request is disabled" in response.rendered_content
assert "Releasing to jobs.opensafely.org is disabled" in response.rendered_content
assert "Returning a request is disabled" in response.rendered_content
assert "Return request before full review" in response.rendered_content
assert "Submit review" in response.rendered_content


Expand Down
3 changes: 2 additions & 1 deletion tests/unit/test_business_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,14 +833,15 @@ def test_provider_get_current_request_for_user_output_checker(bll):
(RequestStatus.SUBMITTED, RequestStatus.APPROVED, False, False, None),
(RequestStatus.SUBMITTED, RequestStatus.REJECTED, False, False, None),
(RequestStatus.SUBMITTED, RequestStatus.WITHDRAWN, False, False, None),
(RequestStatus.SUBMITTED, RequestStatus.RETURNED, False, False, None),
(RequestStatus.SUBMITTED, RequestStatus.RETURNED, False, True, None),
(RequestStatus.SUBMITTED, RequestStatus.RELEASED, False, False, None),
(RequestStatus.PARTIALLY_REVIEWED, RequestStatus.PENDING, False, False, None),
(RequestStatus.PARTIALLY_REVIEWED, RequestStatus.SUBMITTED, False, False, None),
(RequestStatus.PARTIALLY_REVIEWED, RequestStatus.REVIEWED, False, True, None),
(RequestStatus.PARTIALLY_REVIEWED, RequestStatus.APPROVED, False, False, None),
(RequestStatus.PARTIALLY_REVIEWED, RequestStatus.REJECTED, False, False, None),
(RequestStatus.PARTIALLY_REVIEWED, RequestStatus.RELEASED, False, False, None),
(RequestStatus.PARTIALLY_REVIEWED, RequestStatus.RETURNED, False, True, None),
(RequestStatus.PARTIALLY_REVIEWED, RequestStatus.WITHDRAWN, False, False, None),
(RequestStatus.REVIEWED, RequestStatus.PENDING, False, False, None),
(RequestStatus.REVIEWED, RequestStatus.SUBMITTED, False, False, None),
Expand Down

0 comments on commit 3c332cd

Please sign in to comment.