Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 21 additions & 25 deletions libs/shared/shared/django_apps/upload_breadcrumbs/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,9 +560,6 @@ def get_urls(self):
@admin.display(description="Actions", ordering=None)
def resend_upload_button(self, obj: UploadBreadcrumb) -> str:
"""Display resend button in the list view for failed uploads."""
if not self._is_failed_upload(obj):
return "-"

resend_url = reverse(
"admin:upload_breadcrumbs_uploadbreadcrumb_resend_upload", args=[obj.id]
)
Expand All @@ -577,24 +574,16 @@ def resend_upload_action(self, obj: UploadBreadcrumb) -> str:
if not obj.pk: # New object
return "-"

html_parts = []

if self._is_failed_upload(obj):
resend_url = reverse(
"admin:upload_breadcrumbs_uploadbreadcrumb_resend_upload", args=[obj.id]
)
html_parts.append(
f'<a class="button default" href="{resend_url}" '
f"onclick=\"return confirm('Are you sure you want to resend this upload for commit {obj.commit_sha[:7]}?')\">🔄 Resend Upload</a>"
"<br><br>"
"<div><strong>⚠️ Note:</strong> This will create a new upload task for the same commit and repository. "
"The original upload data may no longer be available in storage.</div>"
)
else:
html_parts.append(
"<div>✅ This upload does not appear to have failed. Resend option is not available.</div>"
)

resend_url = reverse(
"admin:upload_breadcrumbs_uploadbreadcrumb_resend_upload", args=[obj.id]
)
html_parts = [
f'<a class="button default" href="{resend_url}" '
f"onclick=\"return confirm('Are you sure you want to resend this upload for commit {obj.commit_sha[:7]}?')\">🔄 Resend Upload</a>"
"<br><br>"
"<div><strong>⚠️ Note:</strong> This will create a new upload task for the same commit and repository. "
"The original upload data may no longer be available in storage.</div>"
]
return format_html("".join(html_parts))

def _is_failed_upload(self, obj: UploadBreadcrumb) -> bool:
Expand Down Expand Up @@ -624,10 +613,6 @@ def resend_upload_view(self, request, object_id):
messages.error(request, "Upload breadcrumb not found.")
return redirect("admin:upload_breadcrumbs_uploadbreadcrumb_changelist")

if not self._is_failed_upload(breadcrumb):
messages.error(request, "This upload does not appear to have failed.")
return redirect("admin:upload_breadcrumbs_uploadbreadcrumb_changelist")

success, error_message = self._resend_upload(breadcrumb, request.user)

if success:
Expand Down Expand Up @@ -706,6 +691,17 @@ def _resend_upload(
"resend_timestamp": timezone.now().isoformat(),
}

# Check if upload_ids is None
if breadcrumb.upload_ids is None:
log.error(
"Cannot resend upload - breadcrumb has no upload IDs",
extra={"breadcrumb_id": breadcrumb.id},
)
return (
False,
"This breadcrumb has no associated upload IDs. It may be a resend breadcrumb that hasn't been processed yet.",
)
Copy link

Choose a reason for hiding this comment

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

Bug: Empty upload IDs list not handled properly

The check if breadcrumb.upload_ids is None only catches None values but not empty lists []. Since upload_ids can be either None or an empty list (both representing no upload IDs), the condition should be if not breadcrumb.upload_ids to handle both cases consistently. Currently, empty lists fall through to a less helpful error message at line 720 instead of the clearer message about breadcrumbs that haven't been processed yet.

Fix in Cursor Fix in Web


log.info(
f"Collecting upload data for {len(breadcrumb.upload_ids)} uploads",
extra={"upload_ids": breadcrumb.upload_ids},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1063,8 +1063,8 @@ def test_resend_upload_button_scenarios(self):
{
"milestone": Milestones.UPLOAD_COMPLETE.value,
},
None,
str,
["🔄 Resend", 'class="button"', "onclick="],
SafeString,
"successful upload",
),
]
Expand All @@ -1079,16 +1079,13 @@ def test_resend_upload_button_scenarios(self):
breadcrumb = UploadBreadcrumbFactory(breadcrumb_data=breadcrumb_data)
result = self.admin.resend_upload_button(breadcrumb)

if expected_contains is None:
self.assertEqual(result, "-", f"Failed for: {description}")
else:
self.assertIsInstance(
result, expected_type, f"Failed for: {description}"
self.assertIsInstance(
result, expected_type, f"Failed for: {description}"
)
for expected_content in expected_contains:
self.assertIn(
expected_content, result, f"Failed for: {description}"
)
for expected_content in expected_contains:
self.assertIn(
expected_content, result, f"Failed for: {description}"
)

def test_resend_upload_action_scenarios(self):
"""Test resend_upload_action with various scenarios."""
Expand Down Expand Up @@ -1120,10 +1117,7 @@ def test_resend_upload_action_scenarios(self):
},
"abcdef1234567890",
True,
[
"✅ This upload does not appear to have failed",
"Resend option is not available",
],
["🔄 Resend Upload", 'class="button default"', "abcdef1", "⚠️ Note:"],
"successful upload",
),
({}, "abcdef1234567890", False, None, "new object without pk"),
Expand Down Expand Up @@ -1170,22 +1164,27 @@ def test_resend_upload_view_with_nonexistent_breadcrumb(self, mock_messages_erro
request, "Upload breadcrumb not found."
)

@patch("django.contrib.messages.error")
def test_resend_upload_view_with_non_failed_upload(self, mock_messages_error):
"""Test resend_upload_view handles non-failed uploads."""
@patch("django.contrib.messages.success")
def test_resend_upload_view_with_non_failed_upload(self, mock_messages_success):
"""Test resend_upload_view allows resending non-failed uploads."""
breadcrumb_data = {
"milestone": Milestones.UPLOAD_COMPLETE.value,
}
breadcrumb = UploadBreadcrumbFactory(breadcrumb_data=breadcrumb_data)
breadcrumb = UploadBreadcrumbFactory(
breadcrumb_data=breadcrumb_data, commit_sha="abcdef1234567890"
)
request = MagicMock()
request.user = self.user

with patch.object(self.admin, "get_object", return_value=breadcrumb):
with (
patch.object(self.admin, "get_object", return_value=breadcrumb),
patch.object(self.admin, "_resend_upload", return_value=(True, None)),
):
response = self.admin.resend_upload_view(request, str(breadcrumb.id))

mock_messages_error.assert_called_once_with(
request, "This upload does not appear to have failed."
)
mock_messages_success.assert_called_once()
success_message = mock_messages_success.call_args[0][1]
self.assertIn("Upload resend triggered successfully", success_message)

@patch("django.contrib.messages.success")
def test_resend_upload_view_successful_resend(self, mock_messages_success):
Expand Down