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

Commit 91ada20

Browse files
committed
feat: Fetch latest upload error for a commit
1 parent 0feeb80 commit 91ada20

File tree

8 files changed

+188
-0
lines changed

8 files changed

+188
-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: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from django.test import TransactionTestCase
2+
from shared.django_apps.core.tests.factories import (
3+
CommitFactory,
4+
OwnerFactory,
5+
RepositoryFactory,
6+
)
7+
8+
from core.commands.commit.interactors.get_latest_upload_error import (
9+
GetLatestUploadErrorInteractor,
10+
)
11+
from graphql_api.types.enums import UploadErrorEnum
12+
from reports.models import CommitReport
13+
from reports.tests.factories import (
14+
CommitReportFactory,
15+
UploadErrorFactory,
16+
UploadFactory,
17+
)
18+
19+
20+
class GetLatestUploadErrorInteractorTest(TransactionTestCase):
21+
def setUp(self):
22+
self.org = OwnerFactory()
23+
self.repo = RepositoryFactory(author=self.org)
24+
self.commit = self._create_commit_with_errors()
25+
self.commit_with_no_errors = CommitFactory(repository=self.repo)
26+
self.single_error_commit = self._create_commit_with_single_error()
27+
28+
def _create_commit_with_errors(self):
29+
commit = CommitFactory(repository=self.repo)
30+
report = CommitReportFactory(
31+
commit=commit, report_type=CommitReport.ReportType.TEST_RESULTS
32+
)
33+
upload = UploadFactory(report=report)
34+
35+
# Create two errors with different timestamps
36+
UploadErrorFactory(
37+
report_session=upload,
38+
created_at="2024-01-01T10:00:00Z",
39+
error_code=UploadErrorEnum.FILE_NOT_IN_STORAGE,
40+
error_params={"error_message": "First error"},
41+
)
42+
UploadErrorFactory(
43+
report_session=upload,
44+
created_at="2024-01-01T11:00:00Z",
45+
error_code=UploadErrorEnum.REPORT_EMPTY,
46+
error_params={"error_message": "Latest error"},
47+
)
48+
return commit
49+
50+
def _create_commit_with_single_error(self):
51+
commit = CommitFactory(repository=self.repo)
52+
report = CommitReportFactory(
53+
commit=commit,
54+
report_type=CommitReport.ReportType.TEST_RESULTS,
55+
)
56+
upload = UploadFactory(report=report)
57+
UploadErrorFactory(
58+
report_session=upload,
59+
error_code=UploadErrorEnum.UNKNOWN_PROCESSING,
60+
error_params={"error_message": "Some other error"},
61+
)
62+
return commit
63+
64+
def execute(self, commit, owner=None):
65+
service = owner.service if owner else "github"
66+
return GetLatestUploadErrorInteractor(owner, service).execute(commit)
67+
68+
async def test_when_no_errors_then_returns_none(self):
69+
result = await self.execute(commit=self.commit_with_no_errors, owner=self.org)
70+
assert result is None
71+
72+
async def test_when_multiple_errors_then_returns_most_recent(self):
73+
result = await self.execute(commit=self.commit, owner=self.org)
74+
assert result == {
75+
"error_code": UploadErrorEnum.REPORT_EMPTY,
76+
"error_message": "Latest error",
77+
}
78+
79+
async def test_when_single_error_then_returns_error(self):
80+
result = await self.execute(commit=self.single_error_commit, owner=self.org)
81+
assert result == {
82+
"error_code": UploadErrorEnum.UNKNOWN_PROCESSING,
83+
"error_message": "Some other error",
84+
}

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
@@ -3523,3 +3523,45 @@ def test_bundle_analysis_report_info(self, get_storage_service):
35233523
assert bundle_info["duration"] == 331
35243524
assert bundle_info["bundlerName"] == "rollup"
35253525
assert bundle_info["bundlerVersion"] == "3.29.4"
3526+
3527+
def test_latest_upload_error(self):
3528+
commit = CommitFactory(repository=self.repo)
3529+
report = CommitReportFactory(
3530+
commit=commit, report_type=CommitReport.ReportType.TEST_RESULTS
3531+
)
3532+
upload = UploadFactory(report=report)
3533+
UploadErrorFactory(
3534+
report_session=upload,
3535+
error_code=UploadErrorEnum.UNKNOWN_PROCESSING,
3536+
error_params={"error_message": "Unknown processing error"},
3537+
)
3538+
3539+
query = """
3540+
query FetchCommit($org: String!, $repo: String!, $commit: String!) {
3541+
owner(username: $org) {
3542+
repository(name: $repo) {
3543+
... on Repository {
3544+
commit(id: $commit) {
3545+
latestUploadError {
3546+
errorCode
3547+
errorMessage
3548+
}
3549+
}
3550+
}
3551+
}
3552+
}
3553+
}
3554+
"""
3555+
3556+
variables = {
3557+
"org": self.org.username,
3558+
"repo": self.repo.name,
3559+
"commit": commit.commitid,
3560+
}
3561+
data = self.gql_request(query, variables=variables)
3562+
commit = data["owner"]["repository"]["commit"]
3563+
3564+
assert commit["latestUploadError"] == {
3565+
"errorCode": "UNKNOWN_PROCESSING",
3566+
"errorMessage": "Unknown processing error",
3567+
}

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)