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

Commit 3ee723e

Browse files
Add more fields for Test Analytics (#879)
1 parent 1f6b320 commit 3ee723e

File tree

8 files changed

+183
-9
lines changed

8 files changed

+183
-9
lines changed

graphql_api/tests/test_test_analytics.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,45 @@ def test_flake_rate_filtering_on_test_results(self) -> None:
588588
]
589589
}
590590

591+
def test_flake_rate_filtering_by_term(self) -> None:
592+
repo = RepositoryFactory(author=self.owner, active=True, private=True)
593+
test = TestFactory(repository=repo, name="hello")
594+
_ = DailyTestRollupFactory(
595+
test=test,
596+
date=datetime.date.today() - datetime.timedelta(days=1),
597+
repoid=repo.repoid,
598+
pass_count=1,
599+
fail_count=1,
600+
flaky_fail_count=1,
601+
)
602+
_ = DailyTestRollupFactory(
603+
test=test,
604+
date=datetime.date.today(),
605+
repoid=repo.repoid,
606+
pass_count=3,
607+
fail_count=0,
608+
flaky_fail_count=0,
609+
)
610+
test_2 = TestFactory(repository=repo, name="world")
611+
_ = DailyTestRollupFactory(
612+
test=test_2,
613+
date=datetime.date.today(),
614+
repoid=repo.repoid,
615+
pass_count=2,
616+
fail_count=3,
617+
flaky_fail_count=1,
618+
)
619+
res = self.fetch_test_analytics(
620+
repo.name,
621+
"""testResults(filters: { term: "hello" }) { edges { node { name failureRate } } }""",
622+
)
623+
624+
assert res["testResults"] == {
625+
"edges": [
626+
{"node": {"name": test.name, "failureRate": 0.2}},
627+
]
628+
}
629+
591630
def test_desc_flake_rate_ordering_on_test_results(self) -> None:
592631
repo = RepositoryFactory(author=self.owner, active=True, private=True)
593632
test = TestFactory(repository=repo)
@@ -662,17 +701,19 @@ def test_test_results_aggregates(self) -> None:
662701
)
663702
res = self.fetch_test_analytics(
664703
repo.name,
665-
"""testResultsAggregates { totalDuration, slowestTestsDuration, totalFails, totalSkips, totalDurationPercentChange, slowestTestsDurationPercentChange, totalFailsPercentChange, totalSkipsPercentChange }""",
704+
"""testResultsAggregates { totalDuration, slowestTestsDuration, totalFails, totalSkips, totalSlowTests, totalDurationPercentChange, slowestTestsDurationPercentChange, totalFailsPercentChange, totalSkipsPercentChange, totalSlowTestsPercentChange }""",
666705
)
667706
assert res["testResultsAggregates"] == {
668707
"totalDuration": 570.0,
669-
"slowestTestsDuration": 29.0,
670-
"totalFails": 10,
671-
"totalSkips": 5,
672708
"totalDurationPercentChange": -63.1068,
709+
"slowestTestsDuration": 29.0,
673710
"slowestTestsDurationPercentChange": -50.84746,
711+
"totalFails": 10,
674712
"totalFailsPercentChange": 100.0,
713+
"totalSkips": 5,
675714
"totalSkipsPercentChange": -50.0,
715+
"totalSlowTests": 1,
716+
"totalSlowTestsPercentChange": 0.0,
676717
}
677718

678719
def test_test_results_aggregates_no_history(self) -> None:
@@ -696,18 +737,20 @@ def test_test_results_aggregates_no_history(self) -> None:
696737

697738
res = self.fetch_test_analytics(
698739
repo.name,
699-
"""testResultsAggregates { totalDuration, slowestTestsDuration, totalFails, totalSkips, totalDurationPercentChange, slowestTestsDurationPercentChange, totalFailsPercentChange, totalSkipsPercentChange }""",
740+
"""testResultsAggregates { totalDuration, slowestTestsDuration, totalFails, totalSkips, totalSlowTests, totalDurationPercentChange, slowestTestsDurationPercentChange, totalFailsPercentChange, totalSkipsPercentChange, totalSlowTestsPercentChange }""",
700741
)
701742

702743
assert res["testResultsAggregates"] == {
703744
"totalDuration": 570.0,
704-
"slowestTestsDuration": 29.0,
705-
"totalFails": 10,
706-
"totalSkips": 5,
707745
"totalDurationPercentChange": None,
746+
"slowestTestsDuration": 29.0,
708747
"slowestTestsDurationPercentChange": None,
748+
"totalFails": 10,
709749
"totalFailsPercentChange": None,
750+
"totalSkips": 5,
710751
"totalSkipsPercentChange": None,
752+
"totalSlowTests": 1,
753+
"totalSlowTestsPercentChange": None,
711754
}
712755

713756
def test_flake_aggregates(self) -> None:

graphql_api/tests/test_test_result.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,96 @@ def test_fetch_test_result_avg_duration(self) -> None:
227227
assert result["owner"]["repository"]["testAnalytics"]["testResults"]["edges"][
228228
0
229229
]["node"]["avgDuration"] == (5.6 / 3)
230+
231+
def test_fetch_test_result_total_fail_count(self) -> None:
232+
query = """
233+
query {
234+
owner(username: "%s") {
235+
repository(name: "%s") {
236+
... on Repository {
237+
testAnalytics {
238+
testResults {
239+
edges {
240+
node {
241+
totalFailCount
242+
}
243+
}
244+
}
245+
}
246+
}
247+
}
248+
}
249+
}
250+
""" % (self.owner.username, self.repository.name)
251+
252+
result = self.gql_request(query, owner=self.owner)
253+
254+
assert "errors" not in result
255+
assert (
256+
result["owner"]["repository"]["testAnalytics"]["testResults"]["edges"][0][
257+
"node"
258+
]["totalFailCount"]
259+
== 9
260+
)
261+
262+
def test_fetch_test_result_total_skip_count(self) -> None:
263+
query = """
264+
query {
265+
owner(username: "%s") {
266+
repository(name: "%s") {
267+
... on Repository {
268+
testAnalytics {
269+
testResults {
270+
edges {
271+
node {
272+
totalSkipCount
273+
}
274+
}
275+
}
276+
}
277+
}
278+
}
279+
}
280+
}
281+
""" % (self.owner.username, self.repository.name)
282+
283+
result = self.gql_request(query, owner=self.owner)
284+
285+
assert "errors" not in result
286+
assert (
287+
result["owner"]["repository"]["testAnalytics"]["testResults"]["edges"][0][
288+
"node"
289+
]["totalSkipCount"]
290+
== 6
291+
)
292+
293+
def test_fetch_test_result_total_pass_count(self) -> None:
294+
query = """
295+
query {
296+
owner(username: "%s") {
297+
repository(name: "%s") {
298+
... on Repository {
299+
testAnalytics {
300+
testResults {
301+
edges {
302+
node {
303+
totalPassCount
304+
}
305+
}
306+
}
307+
}
308+
}
309+
}
310+
}
311+
}
312+
""" % (self.owner.username, self.repository.name)
313+
314+
result = self.gql_request(query, owner=self.owner)
315+
316+
assert "errors" not in result
317+
assert (
318+
result["owner"]["repository"]["testAnalytics"]["testResults"]["edges"][0][
319+
"node"
320+
]["totalPassCount"]
321+
== 3
322+
)

graphql_api/types/inputs/test_results_filters.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ input TestResultsFilters {
44
test_suites: [String!]
55
flags: [String!]
66
history: MeasurementInterval
7+
term: String
78
}
89

910
input TestResultsOrdering {

graphql_api/types/test_analytics/test_analytics.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ async def resolve_test_results(
4848
parameter=parameter,
4949
testsuites=filters.get("test_suites") if filters else None,
5050
flags=filters.get("flags") if filters else None,
51+
term=filters.get("term") if filters else None,
5152
)
5253

5354
return await queryset_to_connection(

graphql_api/types/test_results/test_results.graphql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ type TestResult {
66
flakeRate: Float!
77
avgDuration: Float!
88
lastDuration: Float!
9+
totalFailCount: Int!
10+
totalSkipCount: Int!
11+
totalPassCount: Int!
912
}

graphql_api/types/test_results/test_results.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ class TestDict(TypedDict):
1313
avg_duration: float
1414
last_duration: float
1515
flake_rate: float
16+
total_fail_count: int
17+
total_skip_count: int
18+
total_pass_count: int
1619

1720

1821
test_result_bindable = ObjectType("TestResult")
@@ -51,3 +54,18 @@ def resolve_avg_duration(test: TestDict, _: GraphQLResolveInfo) -> float:
5154
@test_result_bindable.field("lastDuration")
5255
def resolve_last_duration(test: TestDict, _: GraphQLResolveInfo) -> float:
5356
return test["last_duration"]
57+
58+
59+
@test_result_bindable.field("totalFailCount")
60+
def resolve_total_fail_count(test: TestDict, _: GraphQLResolveInfo) -> int:
61+
return test["total_fail_count"]
62+
63+
64+
@test_result_bindable.field("totalSkipCount")
65+
def resolve_total_skip_count(test: TestDict, _: GraphQLResolveInfo) -> int:
66+
return test["total_skip_count"]
67+
68+
69+
@test_result_bindable.field("totalPassCount")
70+
def resolve_total_pass_count(test: TestDict, _: GraphQLResolveInfo) -> int:
71+
return test["total_pass_count"]

graphql_api/types/test_results_aggregates/test_results_aggregates.graphql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ type TestResultsAggregates {
77
totalFailsPercentChange: Float
88
totalSkips: Int!
99
totalSkipsPercentChange: Float
10+
totalSlowTests: Int!
11+
totalSlowTestsPercentChange: Float
1012
}

utils/test_results.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def generate_test_results(
6666
parameter: GENERATE_TEST_RESULT_PARAM | None = None,
6767
testsuites: list[str] | None = None,
6868
flags: list[str] | None = None,
69+
term: str | None = None,
6970
) -> QuerySet:
7071
"""
7172
Function that retrieves aggregated information about all tests in a given repository, for a given time range, optionally filtered by branch name.
@@ -103,6 +104,9 @@ def generate_test_results(
103104

104105
totals = totals.filter(test_id__in=test_ids)
105106

107+
if term is not None:
108+
totals = totals.filter(test__name__icontains=term)
109+
106110
match parameter:
107111
case GENERATE_TEST_RESULT_PARAM.FLAKY:
108112
flakes = Flake.objects.filter(
@@ -172,6 +176,8 @@ def generate_test_results(
172176
total_flaky_fail_count=Cast(
173177
Sum(F("flaky_fail_count")), output_field=FloatField()
174178
),
179+
total_skip_count=Cast(Sum(F("skip_count")), output_field=FloatField()),
180+
total_pass_count=Cast(Sum(F("pass_count")), output_field=FloatField()),
175181
failure_rate=Case(
176182
When(
177183
total_test_count=0,
@@ -262,6 +268,7 @@ def get_test_results_aggregate_numbers(
262268
slowest_tests_duration=slowest_tests_duration,
263269
skips=Sum("skip_count"),
264270
fails=Sum("fail_count"),
271+
total_slow_tests=Value(slow_test_threshold(num_tests)),
265272
)
266273

267274
return test_headers[0] if len(test_headers) > 0 else {}
@@ -280,7 +287,13 @@ def generate_test_results_aggregates(
280287
past_numbers = get_test_results_aggregate_numbers(repo, double_time_ago, since)
281288

282289
return curr_numbers | get_percent_change(
283-
["total_duration", "slowest_tests_duration", "skips", "fails"],
290+
[
291+
"total_duration",
292+
"slowest_tests_duration",
293+
"skips",
294+
"fails",
295+
"total_slow_tests",
296+
],
284297
curr_numbers,
285298
past_numbers,
286299
)

0 commit comments

Comments
 (0)