Skip to content

Conversation

@simonkagwi
Copy link
Contributor

This PR improves the handling of exceptions that occur if a user's refresh token has expired or they don't have permission to access the doc in Google Sheets when publishing a form.

If the refresh token is expired/invalid:
log_in_again

If user doesn't have permission to access the doc in Google Sheets:
no_access

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances error handling in the form publishing flow to provide better user feedback when authentication issues occur. The changes distinguish between expired/invalid refresh tokens and missing spreadsheet permissions, displaying appropriate messages and action buttons for each scenario.

Key changes:

  • Added specific error handling for RefreshError and APIError exceptions with distinct user messages
  • Refactored login URL generation logic into a reusable utility function
  • Created a new template for rendering error summaries with actionable buttons

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/publish_mdm/test_consumer.py Added comprehensive test coverage for permission errors, refresh token errors, and other exceptions
config/templates/publish_mdm/ws/form_template_error_summary.html New template for displaying error messages with actionable buttons
apps/publish_mdm/views.py Simplified by extracting login URL generation to utility function
apps/publish_mdm/utils.py Added reusable get_login_url function with OAuth flow control
apps/publish_mdm/consumers.py Added get_error_summary method to handle different error types with appropriate messages

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"Sorry, you need to log in again to be able to publish. "
"Please click the button below."
)
form_template = FormTemplate.objects.get(id=event_data.get("form_template"))
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The form_template object is retrieved twice using FormTemplate.objects.get(). Consider retrieving it once at the beginning of the method and reusing it to avoid redundant database queries.

Copilot uses AI. Check for mistakes.
f'Google user "{self.scope["user"].email}" appears in the list of '
"people with access."
)
form_template = FormTemplate.objects.get(id=event_data.get("form_template"))
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The form_template object is retrieved twice using FormTemplate.objects.get(). Consider retrieving it once at the beginning of the method and reusing it to avoid redundant database queries.

Copilot uses AI. Check for mistakes.
@tobiasmcnulty
Copy link
Member

I hit a new exception testing this, when I somehow had a form template with no Google sheet selected. After selecting the spreadsheet with the google picker, the error went away.


Traceback (most recent call last):
  File "/Users/tobias/caktus/odk-publish/apps/publish_mdm/consumers.py", line 58, in receive
    self.publish_form_template(event_data=event_data)
  File "/Users/tobias/caktus/odk-publish/apps/publish_mdm/consumers.py", line 146, in publish_form_template
    publish_form_template(
  File "/Users/tobias/caktus/odk-publish/apps/publish_mdm/etl/load.py", line 70, in publish_form_template
    file = form_template.download_user_google_sheet(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tobias/caktus/odk-publish/apps/publish_mdm/models.py", line 329, in download_user_google_sheet
    return download_user_google_sheet(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tobias/caktus/odk-publish/apps/publish_mdm/etl/google.py", line 40, in download_user_google_sheet
    content = export_sheet_by_url(gc=gc, sheet_url=sheet_url)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tobias/caktus/odk-publish/apps/publish_mdm/etl/google.py", line 28, in export_sheet_by_url
    sheet = gc.open_by_url(url=sheet_url)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tobias/caktus/odk-publish/.venv/lib/python3.12/site-packages/gspread/client.py", line 189, in open_by_url
    return self.open_by_key(extract_id_from_url(url))
                            ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/tobias/caktus/odk-publish/.venv/lib/python3.12/site-packages/gspread/utils.py", line 622, in extract_id_from_url
    raise NoValidUrlKeyFound
gspread.exceptions.NoValidUrlKeyFound

Copy link
Member

@tobiasmcnulty tobiasmcnulty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, and I was able to reproduce both errors locally. Thanks and :shipit:

I have a minor suggestion to improve the message for the second error that more closely matched my user experience going through the flow.

diff --git a/apps/publish_mdm/consumers.py b/apps/publish_mdm/consumers.py
index 70df001..eca14a7 100644
--- a/apps/publish_mdm/consumers.py
+++ b/apps/publish_mdm/consumers.py
@@ -123,10 +123,15 @@ class PublishTemplateConsumer(WebsocketConsumer):
                 # Display instructions on how to confirm if they have access
                 error_message = (
                     "Unfortunately, we could not access the form in Google Sheets. "
-                    'Click the button below to access the Spreadsheet, click "Share" '
-                    "(or ask someone with permission to do so), and confirm the "
-                    f'Google user "{self.scope["user"].email}" appears in the list of '
-                    "people with access."
+                    'Click the button below to open the spreadsheet and request access.'
+                    '<br><br>'
+                    'Within the spreadsheet, you or someone else with access will need to click '
+                    '<strong>Share</strong> and confirm the Google user '
+                    f'<strong>{self.scope["user"].email}</strong> appears in the list of '
+                    'people with access.'
+                    '<br><br>'
+                    "When done, return to this page and click "
+                    "<strong>Publish next version</strong> again."
                 )
                 form_template = FormTemplate.objects.get(id=event_data.get("form_template"))
                 button = {"href": form_template.template_url, "text": "Open spreadsheet"}

@simonkagwi simonkagwi merged commit e700072 into main Nov 21, 2025
1 check passed
@simonkagwi simonkagwi deleted the 4874-improve-publish-flow branch November 21, 2025 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants