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

Commit f4e4a18

Browse files
authored
bundle analysis: add a paginated option to retrieve assets (#798)
1 parent 4df7869 commit f4e4a18

File tree

8 files changed

+677
-4
lines changed

8 files changed

+677
-4
lines changed

graphql_api/tests/test_commit.py

+552
Large diffs are not rendered by default.

graphql_api/types/bundle_analysis/base.graphql

+19
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ type BundleReport {
7171
filters: BundleAnalysisMeasurementsSetFilters
7272
): [BundleAnalysisMeasurements!]
7373
isCached: Boolean!
74+
assetsPaginated(
75+
ordering: AssetOrdering
76+
orderingDirection: OrderingDirection
77+
first: Int
78+
after: String
79+
last: Int
80+
before: String
81+
): AssetConnection
7482
}
7583

7684
type BundleAnalysisMeasurements{
@@ -79,4 +87,15 @@ type BundleAnalysisMeasurements{
7987
size: BundleData
8088
change: BundleData
8189
measurements: [Measurement!]
90+
}
91+
92+
type AssetConnection {
93+
edges: [AssetEdge]!
94+
totalCount: Int!
95+
pageInfo: PageInfo!
96+
}
97+
98+
type AssetEdge {
99+
cursor: String!
100+
node: BundleAsset!
82101
}

graphql_api/types/bundle_analysis/base.py

+77-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from datetime import datetime
2-
from typing import List, Mapping, Optional
2+
from typing import Dict, List, Mapping, Optional, Union
33

44
from ariadne import ObjectType, convert_kwargs_to_snake_case
55
from graphql import GraphQLResolveInfo
66

7+
from codecov.commands.exceptions import ValidationError
78
from codecov.db import sync_to_async
8-
from graphql_api.types.enums import OrderingDirection
9+
from graphql_api.types.enums import AssetOrdering, OrderingDirection
910
from services.bundle_analysis import (
1011
AssetReport,
1112
BundleAnalysisMeasurementData,
@@ -25,6 +26,16 @@
2526
bundle_report_bindable = ObjectType("BundleReport")
2627

2728

29+
def _find_index_by_cursor(assets: List, cursor: str) -> int:
30+
try:
31+
for i, asset in enumerate(assets):
32+
if asset.id == int(cursor):
33+
return i
34+
except ValueError:
35+
pass
36+
return -1
37+
38+
2839
# ============= Bundle Data Bindable =============
2940

3041

@@ -135,6 +146,70 @@ def resolve_assets(
135146
return list(bundle_report.assets())
136147

137148

149+
@bundle_report_bindable.field("assetsPaginated")
150+
def resolve_assets_paginated(
151+
bundle_report: BundleReport,
152+
info: GraphQLResolveInfo,
153+
ordering: AssetOrdering = AssetOrdering.SIZE,
154+
ordering_direction: OrderingDirection = OrderingDirection.DESC,
155+
first: Optional[int] = None,
156+
after: Optional[str] = None,
157+
last: Optional[int] = None,
158+
before: Optional[str] = None,
159+
) -> Union[Dict[str, object], ValidationError]:
160+
if first is not None and last is not None:
161+
return ValidationError("First and last can not be used at the same time")
162+
if after is not None and before is not None:
163+
return ValidationError("After and before can not be used at the same time")
164+
165+
# All filtered assets before pagination
166+
assets = list(
167+
bundle_report.assets(
168+
ordering=ordering.value,
169+
ordering_desc=ordering_direction.value == OrderingDirection.DESC.value,
170+
)
171+
)
172+
173+
total_count, has_next_page, has_previous_page = len(assets), False, False
174+
start_cursor, end_cursor = None, None
175+
176+
# Apply cursors to edges
177+
if after is not None:
178+
after_edge = _find_index_by_cursor(assets, after)
179+
if after_edge > -1:
180+
assets = assets[after_edge + 1 :]
181+
182+
if before is not None:
183+
before_edge = _find_index_by_cursor(assets, before)
184+
if before_edge > -1:
185+
assets = assets[:before_edge]
186+
187+
# Slice edges by return size
188+
if first is not None and first >= 0:
189+
if len(assets) > first:
190+
assets = assets[:first]
191+
has_next_page = True
192+
193+
if last is not None and last >= 0:
194+
if len(assets) > last:
195+
assets = assets[len(assets) - last :]
196+
has_previous_page = True
197+
198+
if assets:
199+
start_cursor, end_cursor = assets[0].id, assets[-1].id
200+
201+
return {
202+
"edges": [{"cursor": asset.id, "node": asset} for asset in assets],
203+
"total_count": total_count,
204+
"page_info": {
205+
"has_next_page": has_next_page,
206+
"has_previous_page": has_previous_page,
207+
"start_cursor": start_cursor,
208+
"end_cursor": end_cursor,
209+
},
210+
}
211+
212+
138213
@bundle_report_bindable.field("asset")
139214
def resolve_asset(
140215
bundle_report: BundleReport, info: GraphQLResolveInfo, name: str

graphql_api/types/enums/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .enums import (
2+
AssetOrdering,
23
BundleLoadTypes,
34
CommitErrorCode,
45
CommitErrorGeneralType,
@@ -20,6 +21,7 @@
2021
)
2122

2223
__all__ = [
24+
"AssetOrdering",
2325
"BundleLoadTypes",
2426
"CommitErrorCode",
2527
"CommitErrorGeneralType",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
enum AssetOrdering {
2+
NAME
3+
SIZE
4+
TYPE
5+
}

graphql_api/types/enums/enum_types.py

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from timeseries.models import MeasurementName
1212

1313
from .enums import (
14+
AssetOrdering,
1415
BundleLoadTypes,
1516
CoverageLine,
1617
GoalOnboarding,
@@ -52,4 +53,5 @@
5253
EnumType("YamlStates", YamlStates),
5354
EnumType("BundleLoadTypes", BundleLoadTypes),
5455
EnumType("TestResultsOrderingParameter", TestResultsOrderingParameter),
56+
EnumType("AssetOrdering", AssetOrdering),
5557
]

graphql_api/types/enums/enums.py

+6
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,9 @@ class BundleLoadTypes(enum.Enum):
130130
ENTRY = "ENTRY"
131131
INITIAL = "INITIAL"
132132
LAZY = "LAZY"
133+
134+
135+
class AssetOrdering(enum.Enum):
136+
NAME = "name"
137+
SIZE = "size"
138+
TYPE = "asset_type"

services/bundle_analysis.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ def __init__(self, asset: SharedAssetReport):
206206
self.asset = asset
207207
self.all_modules = None
208208

209+
@cached_property
210+
def id(self) -> int:
211+
return self.asset.id
212+
209213
@cached_property
210214
def name(self) -> str:
211215
return self.asset.hashed_name
@@ -249,9 +253,17 @@ def name(self) -> str:
249253
def all_assets(self) -> List[AssetReport]:
250254
return [AssetReport(asset) for asset in self.report.asset_reports()]
251255

252-
def assets(self) -> List[AssetReport]:
256+
def assets(
257+
self, ordering: Optional[str] = None, ordering_desc: Optional[bool] = None
258+
) -> List[AssetReport]:
259+
ordering_dict: Dict[str, Any] = {}
260+
if ordering:
261+
ordering_dict["ordering_column"] = ordering
262+
if ordering_desc is not None:
263+
ordering_dict["ordering_desc"] = ordering_desc
253264
return [
254-
AssetReport(asset) for asset in self.report.asset_reports(**self.filters)
265+
AssetReport(asset)
266+
for asset in self.report.asset_reports(**{**ordering_dict, **self.filters})
255267
]
256268

257269
def asset(self, name: str) -> Optional[AssetReport]:

0 commit comments

Comments
 (0)