Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit 659b4ec

Browse files
authored
feat: Fetch latest upload error for a commit (#1151)
1 parent 8174946 commit 659b4ec

File tree

8 files changed

+198
-0
lines changed

8 files changed

+198
-0
lines changed

core/commands/commit/commit.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .interactors.get_commit_errors import GetCommitErrorsInteractor
55
from .interactors.get_file_content import GetFileContentInteractor
66
from .interactors.get_final_yaml import GetFinalYamlInteractor
7+
from .interactors.get_latest_upload_error import GetLatestUploadErrorInteractor
78
from .interactors.get_uploads_number import GetUploadsNumberInteractor
89

910

@@ -24,3 +25,6 @@ def get_commit_errors(self, commit, error_type):
2425

2526
def get_uploads_number(self, commit):
2627
return self.get_interactor(GetUploadsNumberInteractor).execute(commit)
28+
29+
def get_latest_upload_error(self, commit):
30+
return self.get_interactor(GetLatestUploadErrorInteractor).execute(commit)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import logging
2+
from typing import Optional
3+
4+
from codecov.commands.base import BaseInteractor
5+
from codecov.db import sync_to_async
6+
from core.models import Commit
7+
from reports.models import CommitReport, UploadError
8+
9+
log = logging.getLogger(__name__)
10+
11+
12+
class GetLatestUploadErrorInteractor(BaseInteractor):
13+
@sync_to_async
14+
def execute(self, commit: Commit) -> Optional[dict]:
15+
try:
16+
return self._get_latest_error(commit)
17+
except Exception as e:
18+
log.error(f"Error fetching upload error: {e}")
19+
return None
20+
21+
def _get_latest_error(self, commit: Commit) -> Optional[dict]:
22+
latest_error = self._fetch_latest_error(commit)
23+
if not latest_error:
24+
return None
25+
26+
return {
27+
"error_code": latest_error.error_code,
28+
"error_message": latest_error.error_params.get("error_message"),
29+
}
30+
31+
def _fetch_latest_error(self, commit: Commit) -> Optional[UploadError]:
32+
return (
33+
UploadError.objects.filter(
34+
report_session__report__commit=commit,
35+
report_session__report__report_type=CommitReport.ReportType.TEST_RESULTS,
36+
)
37+
.only("error_code", "error_params")
38+
.order_by("-created_at")
39+
.first()
40+
)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from unittest.mock import patch
2+
3+
from django.test import TransactionTestCase
4+
from shared.django_apps.core.tests.factories import (
5+
CommitFactory,
6+
OwnerFactory,
7+
RepositoryFactory,
8+
)
9+
10+
from core.commands.commit.interactors.get_latest_upload_error import (
11+
GetLatestUploadErrorInteractor,
12+
)
13+
from graphql_api.types.enums import UploadErrorEnum
14+
from reports.models import CommitReport
15+
from reports.tests.factories import (
16+
CommitReportFactory,
17+
UploadErrorFactory,
18+
UploadFactory,
19+
)
20+
21+
22+
class GetLatestUploadErrorInteractorTest(TransactionTestCase):
23+
def setUp(self):
24+
self.org = OwnerFactory()
25+
self.repo = RepositoryFactory(author=self.org)
26+
self.commit = self._create_commit_with_errors()
27+
self.commit_with_no_errors = CommitFactory(repository=self.repo)
28+
self.single_error_commit = self._create_commit_with_single_error()
29+
30+
def _create_commit_with_errors(self):
31+
commit = CommitFactory(repository=self.repo)
32+
report = CommitReportFactory(
33+
commit=commit, report_type=CommitReport.ReportType.TEST_RESULTS
34+
)
35+
upload = UploadFactory(report=report)
36+
37+
# Create two errors with different timestamps
38+
UploadErrorFactory(
39+
report_session=upload,
40+
created_at="2024-01-01T10:00:00Z",
41+
error_code=UploadErrorEnum.FILE_NOT_IN_STORAGE,
42+
error_params={"error_message": "First error"},
43+
)
44+
UploadErrorFactory(
45+
report_session=upload,
46+
created_at="2024-01-01T11:00:00Z",
47+
error_code=UploadErrorEnum.REPORT_EMPTY,
48+
error_params={"error_message": "Latest error"},
49+
)
50+
return commit
51+
52+
def _create_commit_with_single_error(self):
53+
commit = CommitFactory(repository=self.repo)
54+
report = CommitReportFactory(
55+
commit=commit,
56+
report_type=CommitReport.ReportType.TEST_RESULTS,
57+
)
58+
upload = UploadFactory(report=report)
59+
UploadErrorFactory(
60+
report_session=upload,
61+
error_code=UploadErrorEnum.UNKNOWN_PROCESSING,
62+
error_params={"error_message": "Some other error"},
63+
)
64+
return commit
65+
66+
def execute(self, commit, owner=None):
67+
service = owner.service if owner else "github"
68+
return GetLatestUploadErrorInteractor(owner, service).execute(commit)
69+
70+
async def test_when_no_errors_then_returns_none(self):
71+
result = await self.execute(commit=self.commit_with_no_errors, owner=self.org)
72+
assert result is None
73+
74+
async def test_when_multiple_errors_then_returns_most_recent(self):
75+
result = await self.execute(commit=self.commit, owner=self.org)
76+
assert result == {
77+
"error_code": UploadErrorEnum.REPORT_EMPTY,
78+
"error_message": "Latest error",
79+
}
80+
81+
async def test_when_single_error_then_returns_error(self):
82+
result = await self.execute(commit=self.single_error_commit, owner=self.org)
83+
assert result == {
84+
"error_code": UploadErrorEnum.UNKNOWN_PROCESSING,
85+
"error_message": "Some other error",
86+
}
87+
88+
async def test_return_none_on_raised_error(self):
89+
with patch(
90+
"core.commands.commit.interactors.get_latest_upload_error.GetLatestUploadErrorInteractor._get_latest_error",
91+
side_effect=Exception("Test error"),
92+
):
93+
result = await self.execute(commit=self.commit, owner=self.org)
94+
assert result is None

core/commands/commit/tests/test_commit.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ def test_get_commit_errors_delegate_to_interactor(self, interactor_mock):
3838
def test_get_uploads_number_delegate_to_interactor(self, interactor_mock):
3939
self.command.get_uploads_number(self.commit)
4040
interactor_mock.assert_called_once_with(self.commit)
41+
42+
@patch("core.commands.commit.commit.GetLatestUploadErrorInteractor.execute")
43+
def test_get_latest_upload_error_delegate_to_interactor(self, interactor_mock):
44+
self.command.get_latest_upload_error(self.commit)
45+
interactor_mock.assert_called_once_with(self.commit)

graphql_api/tests/test_commit.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3528,3 +3528,45 @@ def test_bundle_analysis_report_info(self, get_storage_service):
35283528
assert bundle_info["duration"] == 331
35293529
assert bundle_info["bundlerName"] == "rollup"
35303530
assert bundle_info["bundlerVersion"] == "3.29.4"
3531+
3532+
def test_latest_upload_error(self):
3533+
commit = CommitFactory(repository=self.repo)
3534+
report = CommitReportFactory(
3535+
commit=commit, report_type=CommitReport.ReportType.TEST_RESULTS
3536+
)
3537+
upload = UploadFactory(report=report)
3538+
UploadErrorFactory(
3539+
report_session=upload,
3540+
error_code=UploadErrorEnum.UNKNOWN_PROCESSING,
3541+
error_params={"error_message": "Unknown processing error"},
3542+
)
3543+
3544+
query = """
3545+
query FetchCommit($org: String!, $repo: String!, $commit: String!) {
3546+
owner(username: $org) {
3547+
repository(name: $repo) {
3548+
... on Repository {
3549+
commit(id: $commit) {
3550+
latestUploadError {
3551+
errorCode
3552+
errorMessage
3553+
}
3554+
}
3555+
}
3556+
}
3557+
}
3558+
}
3559+
"""
3560+
3561+
variables = {
3562+
"org": self.org.username,
3563+
"repo": self.repo.name,
3564+
"commit": commit.commitid,
3565+
}
3566+
data = self.gql_request(query, variables=variables)
3567+
commit = data["owner"]["repository"]["commit"]
3568+
3569+
assert commit["latestUploadError"] == {
3570+
"errorCode": "UNKNOWN_PROCESSING",
3571+
"errorMessage": "Unknown processing error",
3572+
}

graphql_api/types/commit/commit.graphql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ type Commit {
2626
coverageStatus: CommitStatus
2727
coverageAnalytics: CommitCoverageAnalytics
2828
bundleAnalysis: CommitBundleAnalysis
29+
latestUploadError: LatestUploadError
30+
}
31+
32+
type LatestUploadError {
33+
errorCode: UploadErrorEnum
34+
errorMessage: String
2935
}
3036

3137
"fields related to Codecov's Coverage product offering"

graphql_api/types/commit/commit.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,3 +478,9 @@ def resolve_commit_bundle_analysis_report(commit: Commit, info) -> BundleAnalysi
478478
info.context["commit"] = commit
479479

480480
return bundle_analysis_report
481+
482+
483+
@commit_bindable.field("latestUploadError")
484+
async def resolve_latest_upload_error(commit, info):
485+
command = info.context["executor"].get_command("commit")
486+
return await command.get_latest_upload_error(commit)

graphql_api/types/enums/upload_error_enum.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ enum UploadErrorEnum {
33
REPORT_EXPIRED
44
REPORT_EMPTY
55
PROCESSING_TIMEOUT
6+
UNSUPPORTED_FILE_FORMAT
67

78
UNKNOWN_PROCESSING
89
UNKNOWN_STORAGE

0 commit comments

Comments
 (0)