Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Fetch latest upload error for a commit #1151

Merged
merged 3 commits into from
Feb 11, 2025
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
4 changes: 4 additions & 0 deletions core/commands/commit/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .interactors.get_commit_errors import GetCommitErrorsInteractor
from .interactors.get_file_content import GetFileContentInteractor
from .interactors.get_final_yaml import GetFinalYamlInteractor
from .interactors.get_latest_upload_error import GetLatestUploadErrorInteractor
from .interactors.get_uploads_number import GetUploadsNumberInteractor


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

def get_uploads_number(self, commit):
return self.get_interactor(GetUploadsNumberInteractor).execute(commit)

def get_latest_upload_error(self, commit):
return self.get_interactor(GetLatestUploadErrorInteractor).execute(commit)
40 changes: 40 additions & 0 deletions core/commands/commit/interactors/get_latest_upload_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import logging
from typing import Optional

from codecov.commands.base import BaseInteractor
from codecov.db import sync_to_async
from core.models import Commit
from reports.models import CommitReport, UploadError

log = logging.getLogger(__name__)


class GetLatestUploadErrorInteractor(BaseInteractor):
@sync_to_async
def execute(self, commit: Commit) -> Optional[dict]:
try:
return self._get_latest_error(commit)
except Exception as e:
log.error(f"Error fetching upload error: {e}")
return None

def _get_latest_error(self, commit: Commit) -> Optional[dict]:
latest_error = self._fetch_latest_error(commit)
if not latest_error:
return None

return {
"error_code": latest_error.error_code,
"error_message": latest_error.error_params.get("error_message"),
}

def _fetch_latest_error(self, commit: Commit) -> Optional[UploadError]:
return (
UploadError.objects.filter(
report_session__report__commit=commit,
report_session__report__report_type=CommitReport.ReportType.TEST_RESULTS,
)
.only("error_code", "error_params")
.order_by("-created_at")
.first()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
from unittest.mock import patch

from django.test import TransactionTestCase
from shared.django_apps.core.tests.factories import (
CommitFactory,
OwnerFactory,
RepositoryFactory,
)

from core.commands.commit.interactors.get_latest_upload_error import (
GetLatestUploadErrorInteractor,
)
from graphql_api.types.enums import UploadErrorEnum
from reports.models import CommitReport
from reports.tests.factories import (
CommitReportFactory,
UploadErrorFactory,
UploadFactory,
)


class GetLatestUploadErrorInteractorTest(TransactionTestCase):
def setUp(self):
self.org = OwnerFactory()
self.repo = RepositoryFactory(author=self.org)
self.commit = self._create_commit_with_errors()
self.commit_with_no_errors = CommitFactory(repository=self.repo)
self.single_error_commit = self._create_commit_with_single_error()

def _create_commit_with_errors(self):
commit = CommitFactory(repository=self.repo)
report = CommitReportFactory(
commit=commit, report_type=CommitReport.ReportType.TEST_RESULTS
)
upload = UploadFactory(report=report)

# Create two errors with different timestamps
UploadErrorFactory(
report_session=upload,
created_at="2024-01-01T10:00:00Z",
error_code=UploadErrorEnum.FILE_NOT_IN_STORAGE,
error_params={"error_message": "First error"},
)
UploadErrorFactory(
report_session=upload,
created_at="2024-01-01T11:00:00Z",
error_code=UploadErrorEnum.REPORT_EMPTY,
error_params={"error_message": "Latest error"},
)
return commit

def _create_commit_with_single_error(self):
commit = CommitFactory(repository=self.repo)
report = CommitReportFactory(
commit=commit,
report_type=CommitReport.ReportType.TEST_RESULTS,
)
upload = UploadFactory(report=report)
UploadErrorFactory(
report_session=upload,
error_code=UploadErrorEnum.UNKNOWN_PROCESSING,
error_params={"error_message": "Some other error"},
)
return commit

def execute(self, commit, owner=None):
service = owner.service if owner else "github"
return GetLatestUploadErrorInteractor(owner, service).execute(commit)

async def test_when_no_errors_then_returns_none(self):
result = await self.execute(commit=self.commit_with_no_errors, owner=self.org)
assert result is None

async def test_when_multiple_errors_then_returns_most_recent(self):
result = await self.execute(commit=self.commit, owner=self.org)
assert result == {
"error_code": UploadErrorEnum.REPORT_EMPTY,
"error_message": "Latest error",
}

async def test_when_single_error_then_returns_error(self):
result = await self.execute(commit=self.single_error_commit, owner=self.org)
assert result == {
"error_code": UploadErrorEnum.UNKNOWN_PROCESSING,
"error_message": "Some other error",
}

async def test_return_none_on_raised_error(self):
with patch(
"core.commands.commit.interactors.get_latest_upload_error.GetLatestUploadErrorInteractor._get_latest_error",
side_effect=Exception("Test error"),
):
result = await self.execute(commit=self.commit, owner=self.org)
assert result is None
5 changes: 5 additions & 0 deletions core/commands/commit/tests/test_commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ def test_get_commit_errors_delegate_to_interactor(self, interactor_mock):
def test_get_uploads_number_delegate_to_interactor(self, interactor_mock):
self.command.get_uploads_number(self.commit)
interactor_mock.assert_called_once_with(self.commit)

@patch("core.commands.commit.commit.GetLatestUploadErrorInteractor.execute")
def test_get_latest_upload_error_delegate_to_interactor(self, interactor_mock):
self.command.get_latest_upload_error(self.commit)
interactor_mock.assert_called_once_with(self.commit)
42 changes: 42 additions & 0 deletions graphql_api/tests/test_commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3528,3 +3528,45 @@ def test_bundle_analysis_report_info(self, get_storage_service):
assert bundle_info["duration"] == 331
assert bundle_info["bundlerName"] == "rollup"
assert bundle_info["bundlerVersion"] == "3.29.4"

def test_latest_upload_error(self):
commit = CommitFactory(repository=self.repo)
report = CommitReportFactory(
commit=commit, report_type=CommitReport.ReportType.TEST_RESULTS
)
upload = UploadFactory(report=report)
UploadErrorFactory(
report_session=upload,
error_code=UploadErrorEnum.UNKNOWN_PROCESSING,
error_params={"error_message": "Unknown processing error"},
)

query = """
query FetchCommit($org: String!, $repo: String!, $commit: String!) {
owner(username: $org) {
repository(name: $repo) {
... on Repository {
commit(id: $commit) {
latestUploadError {
errorCode
errorMessage
}
}
}
}
}
}
"""

variables = {
"org": self.org.username,
"repo": self.repo.name,
"commit": commit.commitid,
}
data = self.gql_request(query, variables=variables)
commit = data["owner"]["repository"]["commit"]

assert commit["latestUploadError"] == {
"errorCode": "UNKNOWN_PROCESSING",
"errorMessage": "Unknown processing error",
}
6 changes: 6 additions & 0 deletions graphql_api/types/commit/commit.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ type Commit {
coverageStatus: CommitStatus
coverageAnalytics: CommitCoverageAnalytics
bundleAnalysis: CommitBundleAnalysis
latestUploadError: LatestUploadError
}

type LatestUploadError {
errorCode: UploadErrorEnum
errorMessage: String
}

"fields related to Codecov's Coverage product offering"
Expand Down
6 changes: 6 additions & 0 deletions graphql_api/types/commit/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,3 +478,9 @@ def resolve_commit_bundle_analysis_report(commit: Commit, info) -> BundleAnalysi
info.context["commit"] = commit

return bundle_analysis_report


@commit_bindable.field("latestUploadError")
async def resolve_latest_upload_error(commit, info):
command = info.context["executor"].get_command("commit")
return await command.get_latest_upload_error(commit)
1 change: 1 addition & 0 deletions graphql_api/types/enums/upload_error_enum.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ enum UploadErrorEnum {
REPORT_EXPIRED
REPORT_EMPTY
PROCESSING_TIMEOUT
UNSUPPORTED_FILE_FORMAT

UNKNOWN_PROCESSING
UNKNOWN_STORAGE
Expand Down
Loading