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

add TestResultsHeaders GQL model #816

Merged
merged 2 commits into from
Oct 3, 2024
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
28 changes: 28 additions & 0 deletions graphql_api/tests/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -1194,3 +1194,31 @@ def test_desc_failure_rate_ordering_on_test_results(self) -> None:
{"node": {"name": test.name, "failureRate": 0.2}},
]
}

def test_test_results_headers(self) -> None:
repo = RepositoryFactory(
author=self.owner, active=True, private=True, branch="main"
)

for i in range(0, 100):
test = TestFactory(repository=repo)
_ = DailyTestRollupFactory(
test=test,
repoid=repo.repoid,
branch="main",
fail_count=1 if i % 5 == 0 else 0,
skip_count=1 if i % 10 == 0 else 0,
pass_count=1,
avg_duration_seconds=float(i),
last_duration_seconds=float(i),
)
res = self.fetch_repository(
repo.name,
"""testResultsHeaders { totalRunTime, slowestTestsRunTime, totalFails, totalSkips }""",
)
assert res["testResultsHeaders"] == {
"totalRunTime": 5940.0,
"slowestTestsRunTime": 850.0,
"totalFails": 20,
"totalSkips": 10,
}
116 changes: 116 additions & 0 deletions graphql_api/tests/test_test_results_headers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from datetime import date, datetime, timedelta

from django.test import TransactionTestCase
from freezegun import freeze_time

from codecov_auth.tests.factories import OwnerFactory
from core.tests.factories import RepositoryFactory
from reports.tests.factories import DailyTestRollupFactory, TestFactory

from .helper import GraphQLTestHelper


@freeze_time(datetime.now().isoformat())
class TestResultTestCase(GraphQLTestHelper, TransactionTestCase):
def setUp(self):
self.owner = OwnerFactory(username="randomOwner")
self.repository = RepositoryFactory(author=self.owner, branch="main")

for i in range(1, 31):
test = TestFactory(repository=self.repository)

_ = DailyTestRollupFactory(
test=test,
date=date.today() - timedelta(days=i),
avg_duration_seconds=float(i),
latest_run=datetime.now() - timedelta(days=i),
fail_count=1,
skip_count=1,
pass_count=0,
branch="main",
)

def test_fetch_test_result_total_runtime(self) -> None:
query = """
query {
owner(username: "%s") {
repository(name: "%s") {
... on Repository {
testResultsHeaders {
totalRunTime
}
}
}
}
}
""" % (self.owner.username, self.repository.name)

result = self.gql_request(query, owner=self.owner)

assert "errors" not in result
assert (
result["owner"]["repository"]["testResultsHeaders"]["totalRunTime"] == 435.0
)

def test_fetch_test_result_slowest_tests_runtime(self) -> None:
query = """
query {
owner(username: "%s") {
repository(name: "%s") {
... on Repository {
testResultsHeaders {
slowestTestsRunTime
}
}
}
}
}
""" % (self.owner.username, self.repository.name)

result = self.gql_request(query, owner=self.owner)

assert "errors" not in result
assert (
result["owner"]["repository"]["testResultsHeaders"]["slowestTestsRunTime"]
== 29.0
)

def test_fetch_test_result_failed_tests(self) -> None:
query = """
query {
owner(username: "%s") {
repository(name: "%s") {
... on Repository {
testResultsHeaders {
totalFails
}
}
}
}
}
""" % (self.owner.username, self.repository.name)

result = self.gql_request(query, owner=self.owner)

assert "errors" not in result
assert result["owner"]["repository"]["testResultsHeaders"]["totalFails"] == 29

def test_fetch_test_result_skipped_tests(self) -> None:
query = """
query {
owner(username: "%s") {
repository(name: "%s") {
... on Repository {
testResultsHeaders {
totalSkips
}
}
}
}
}
""" % (self.owner.username, self.repository.name)

result = self.gql_request(query, owner=self.owner)

assert "errors" not in result
assert result["owner"]["repository"]["testResultsHeaders"]["totalSkips"] == 29
3 changes: 3 additions & 0 deletions graphql_api/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
from .self_hosted_license import self_hosted_license, self_hosted_license_bindable
from .session import session, session_bindable
from .test_results import test_result_bindable, test_results
from .test_results_headers import test_results_headers, test_results_headers_bindable
from .upload import upload, upload_bindable, upload_error_bindable
from .user import user, user_bindable
from .user_token import user_token, user_token_bindable
Expand Down Expand Up @@ -116,6 +117,7 @@
account,
okta_config,
test_results,
test_results_headers,
]

bindables = [
Expand Down Expand Up @@ -174,4 +176,5 @@
account_bindable,
okta_config_bindable,
test_result_bindable,
test_results_headers_bindable,
]
1 change: 1 addition & 0 deletions graphql_api/types/repository/repository.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ type Repository {

"CoverageAnalytics are fields related to Codecov's Coverage product offering"
coverageAnalytics: CoverageAnalytics
testResultsHeaders: TestResultsHeaders
}

type TestResultConnection {
Expand Down
13 changes: 12 additions & 1 deletion graphql_api/types/repository/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from services.profiling import CriticalFile, ProfilingSummary
from services.redis_configuration import get_redis_connection
from timeseries.models import Dataset, Interval, MeasurementName
from utils.test_results import aggregate_test_results
from utils.test_results import aggregate_test_results, test_results_headers

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -557,3 +557,14 @@ def resolve_coverage_analytics(
return CoverageAnalyticsProps(
repository=repository,
)


@repository_bindable.field("testResultsHeaders")
@convert_kwargs_to_snake_case
async def resolve_test_results_headers(
repository: Repository,
info: GraphQLResolveInfo,
):
queryset = await sync_to_async(test_results_headers)(repoid=repository.repoid)

return queryset
4 changes: 1 addition & 3 deletions graphql_api/types/test_results/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from shared.license import get_current_license

from graphql_api.helpers.ariadne import ariadne_load_local_graphql

from .test_results import test_result_bindable

test_results = ariadne_load_local_graphql(__file__, "test_results.graphql")

__all__ = ["get_current_license", "test_result_bindable"]
__all__ = ["test_result_bindable"]
24 changes: 18 additions & 6 deletions graphql_api/types/test_results/test_results.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
from datetime import datetime
from typing import TypedDict

from ariadne import ObjectType
from graphql import GraphQLResolveInfo


class TestDict(TypedDict):
name: str
updated_at: datetime
commits_where_fail: int | None
failure_rate: float | None
avg_duration: float | None
last_duration: float | None


test_result_bindable = ObjectType("TestResult")


@test_result_bindable.field("name")
def resolve_name(test, info) -> str:
def resolve_name(test: TestDict, _: GraphQLResolveInfo) -> str:
return test["name"].replace("\x1f", " ")


@test_result_bindable.field("updatedAt")
def resolve_updated_at(test, info) -> datetime:
def resolve_updated_at(test: TestDict, _: GraphQLResolveInfo) -> datetime:
return test["updated_at"]


@test_result_bindable.field("commitsFailed")
def resolve_commits_failed(test, info) -> int | None:
def resolve_commits_failed(test: TestDict, _: GraphQLResolveInfo) -> int | None:
return test["commits_where_fail"]


@test_result_bindable.field("failureRate")
def resolve_failure_rate(test, info) -> float | None:
def resolve_failure_rate(test: TestDict, _: GraphQLResolveInfo) -> float | None:
return test["failure_rate"]


@test_result_bindable.field("avgDuration")
def resolve_avg_duration(test, info) -> float | None:
def resolve_avg_duration(test: TestDict, _: GraphQLResolveInfo) -> float | None:
return test["avg_duration"]


@test_result_bindable.field("lastDuration")
def resolve_last_duration(test, info) -> float | None:
def resolve_last_duration(test: TestDict, _: GraphQLResolveInfo) -> float | None:
return test["last_duration"]
9 changes: 9 additions & 0 deletions graphql_api/types/test_results_headers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from graphql_api.helpers.ariadne import ariadne_load_local_graphql

from .test_results_headers import test_results_headers_bindable

test_results_headers = ariadne_load_local_graphql(
__file__, "test_results_headers.graphql"
)

__all__ = ["test_results_headers_bindable"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type TestResultsHeaders {
totalRunTime: Float!
slowestTestsRunTime: Float!
totalFails: Int!
totalSkips: Int!
}
33 changes: 33 additions & 0 deletions graphql_api/types/test_results_headers/test_results_headers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import TypedDict

from ariadne import ObjectType
from graphql import GraphQLResolveInfo

test_results_headers_bindable = ObjectType("TestResultsHeaders")


class TestResultsHeaders(TypedDict):
total_run_time: float
slowest_tests_duration: float
fails: int
skips: int


@test_results_headers_bindable.field("totalRunTime")
def resolve_name(obj: TestResultsHeaders, _: GraphQLResolveInfo) -> float:
return obj["total_run_time"]


@test_results_headers_bindable.field("slowestTestsRunTime")
def resolve_updated_at(obj: TestResultsHeaders, _: GraphQLResolveInfo) -> float:
return obj["slowest_tests_duration"]


@test_results_headers_bindable.field("totalFails")
def resolve_commits_failed(obj: TestResultsHeaders, _: GraphQLResolveInfo) -> int:
return obj["fails"]


@test_results_headers_bindable.field("totalSkips")
def resolve_failure_rate(obj: TestResultsHeaders, _: GraphQLResolveInfo) -> int:
return obj["skips"]
Loading
Loading