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

Commit 115912a

Browse files
authored
Refactor and optimize querying pathContents (#1141)
1 parent 06167fb commit 115912a

File tree

5 files changed

+106
-186
lines changed

5 files changed

+106
-186
lines changed
Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,18 @@
1-
from typing import Iterable, Union
2-
31
import sentry_sdk
42

53
from graphql_api.types.enums import PathContentDisplayType
64
from services.path import Dir, File
75

86

9-
def partition_list_into_files_and_directories(
10-
items: Iterable[Union[File, Dir]],
11-
) -> tuple[Union[File, Dir]]:
12-
files = []
13-
directories = []
14-
15-
# Separate files and directories
16-
for item in items:
17-
if isinstance(item, Dir):
18-
directories.append(item)
19-
else:
20-
files.append(item)
21-
22-
return (files, directories)
23-
24-
25-
def sort_list_by_directory(
26-
items: Iterable[Union[File, Dir]],
27-
) -> Iterable[Union[File, Dir]]:
28-
(files, directories) = partition_list_into_files_and_directories(items=items)
29-
return directories + files
30-
31-
327
@sentry_sdk.trace
33-
def sort_path_contents(
34-
items: Iterable[Union[File, Dir]], filters={}
35-
) -> Iterable[Union[File, Dir]]:
8+
def sort_path_contents(items: list[File | Dir], filters: dict = {}) -> list[File | Dir]:
369
filter_parameter = filters.get("ordering", {}).get("parameter")
3710
filter_direction = filters.get("ordering", {}).get("direction")
3811

3912
if filter_parameter and filter_direction:
4013
parameter_value = filter_parameter.value
4114
direction_value = filter_direction.value
42-
items = sorted(
43-
items,
15+
items.sort(
4416
key=lambda item: getattr(item, parameter_value),
4517
reverse=direction_value == "descending",
4618
)
@@ -49,6 +21,6 @@ def sort_path_contents(
4921
parameter_value == "name"
5022
and display_type is not PathContentDisplayType.LIST
5123
):
52-
items = sort_list_by_directory(items=items)
24+
items.sort(key=lambda item: isinstance(item, File))
5325

5426
return items

graphql_api/tests/test_branch.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ class MockSession(object):
154154
pass
155155

156156

157+
class MockFile:
158+
def __init__(self, name: str):
159+
self.name = name
160+
161+
157162
class MockReport(object):
158163
def __init__(self):
159164
self.sessions = {1: MockSession()}
@@ -171,6 +176,10 @@ def files(self):
171176
"folder/subfolder/fileD.py",
172177
]
173178

179+
def __iter__(self):
180+
for name in self.files:
181+
yield MockFile(name)
182+
174183
@property
175184
def flags(self):
176185
return {"flag-a": MockFlag()}
@@ -774,11 +783,9 @@ def test_fetch_path_contents_component_filter_missing_coverage(
774783
@patch("services.components.component_filtered_report")
775784
@patch("services.components.commit_components")
776785
@patch("shared.reports.api_report_service.build_report_from_commit")
777-
@patch("services.report.files_in_sessions")
778786
def test_fetch_path_contents_component_filter_has_coverage(
779-
self, session_files_mock, report_mock, commit_components_mock, filtered_mock
787+
self, report_mock, commit_components_mock, filtered_mock
780788
):
781-
session_files_mock.return_value = MockReport().files
782789
components = ["Global"]
783790
variables = {
784791
"org": self.org.username,

graphql_api/types/commit/commit.py

Lines changed: 57 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import services.components as components_service
1515
import services.path as path_service
1616
from codecov.db import sync_to_async
17+
from codecov_auth.models import Owner
1718
from core.models import Commit
1819
from graphql_api.actions.commits import commit_status, commit_uploads
1920
from graphql_api.actions.comparison import validate_commit_comparison
@@ -44,7 +45,7 @@
4445
from services.bundle_analysis import BundleAnalysisComparison, BundleAnalysisReport
4546
from services.comparison import Comparison, ComparisonReport
4647
from services.components import Component
47-
from services.path import ReportPaths
48+
from services.path import Dir, File, ReportPaths
4849
from services.profiling import CriticalFile, ProfilingSummary
4950
from services.yaml import (
5051
YamlStates,
@@ -146,23 +147,19 @@ def resolve_critical_files(commit: Commit, info, **kwargs) -> List[CriticalFile]
146147
return profiling_summary.critical_files
147148

148149

149-
@sentry_sdk.trace
150-
@commit_bindable.field("pathContents")
151-
@sync_to_async
152-
def resolve_path_contents(commit: Commit, info, path: str = None, filters=None):
153-
"""
154-
The file directory tree is a list of all the files and directories
155-
extracted from the commit report of the latest, head commit.
156-
The is resolver results in a list that represent the tree with files
157-
and nested directories.
158-
"""
159-
current_owner = info.context["request"].current_owner
160-
150+
def get_sorted_path_contents(
151+
current_owner: Owner,
152+
commit: Commit,
153+
path: str | None = None,
154+
filters: dict | None = None,
155+
) -> (
156+
list[File | Dir] | MissingHeadReport | MissingCoverage | UnknownFlags | UnknownPath
157+
):
161158
# TODO: Might need to add reports here filtered by flags in the future
162-
commit_report = report_service.build_report_from_commit(
159+
report = report_service.build_report_from_commit(
163160
commit, report_class=ReadOnlyReport
164161
)
165-
if not commit_report:
162+
if not report:
166163
return MissingHeadReport()
167164

168165
if filters is None:
@@ -189,9 +186,9 @@ def resolve_path_contents(commit: Commit, info, path: str = None, filters=None):
189186

190187
for component in filtered_components:
191188
component_paths.extend(component.paths)
192-
if commit_report.flags:
189+
if report.flags:
193190
component_flags.extend(
194-
component.get_matching_flags(commit_report.flags.keys())
191+
component.get_matching_flags(report.flags.keys())
195192
)
196193

197194
if component_flags:
@@ -200,11 +197,11 @@ def resolve_path_contents(commit: Commit, info, path: str = None, filters=None):
200197
else:
201198
flags_filter = component_flags
202199

203-
if flags_filter and not commit_report.flags:
200+
if flags_filter and not report.flags:
204201
return UnknownFlags(f"No coverage with chosen flags: {flags_filter}")
205202

206203
report_paths = ReportPaths(
207-
report=commit_report,
204+
report=report,
208205
path=path,
209206
search_term=search_value,
210207
filter_flags=flags_filter,
@@ -221,25 +218,23 @@ def resolve_path_contents(commit: Commit, info, path: str = None, filters=None):
221218
# we're just missing coverage for the file
222219
return MissingCoverage(f"missing coverage for path: {path}")
223220

221+
items: list[File | Dir]
224222
if search_value or display_type == PathContentDisplayType.LIST:
225223
items = report_paths.full_filelist()
226224
else:
227225
items = report_paths.single_directory()
228-
return {"results": sort_path_contents(items, filters)}
226+
return sort_path_contents(items, filters)
229227

230228

231-
@commit_bindable.field("deprecatedPathContents")
229+
@sentry_sdk.trace
230+
@commit_bindable.field("pathContents")
232231
@sync_to_async
233-
def resolve_deprecated_path_contents(
232+
def resolve_path_contents(
234233
commit: Commit,
235-
info,
236-
path: str = None,
237-
filters=None,
238-
first=None,
239-
after=None,
240-
last=None,
241-
before=None,
242-
):
234+
info: GraphQLResolveInfo,
235+
path: str | None = None,
236+
filters: dict | None = None,
237+
) -> Any:
243238
"""
244239
The file directory tree is a list of all the files and directories
245240
extracted from the commit report of the latest, head commit.
@@ -248,80 +243,43 @@ def resolve_deprecated_path_contents(
248243
"""
249244
current_owner = info.context["request"].current_owner
250245

251-
# TODO: Might need to add reports here filtered by flags in the future
252-
commit_report = report_service.build_report_from_commit(
253-
commit, report_class=ReadOnlyReport
254-
)
255-
if not commit_report:
256-
return MissingHeadReport()
257-
258-
if filters is None:
259-
filters = {}
260-
search_value = filters.get("search_value")
261-
display_type = filters.get("display_type")
262-
263-
flags_filter = filters.get("flags", [])
264-
component_filter = filters.get("components", [])
265-
266-
component_paths = []
267-
component_flags = []
268-
269-
if component_filter:
270-
all_components = components_service.commit_components(commit, current_owner)
271-
filtered_components = components_service.filter_components_by_name_or_id(
272-
all_components, component_filter
273-
)
274-
275-
if not filtered_components:
276-
return MissingCoverage(
277-
f"missing coverage for report with components: {component_filter}"
278-
)
279-
280-
for component in filtered_components:
281-
component_paths.extend(component.paths)
282-
if commit_report.flags:
283-
component_flags.extend(
284-
component.get_matching_flags(commit_report.flags.keys())
285-
)
286-
287-
if component_flags:
288-
if flags_filter:
289-
flags_filter = list(set(flags_filter) & set(component_flags))
290-
else:
291-
flags_filter = component_flags
246+
contents = get_sorted_path_contents(current_owner, commit, path, filters)
247+
if isinstance(contents, list):
248+
return {"results": contents}
249+
return contents
292250

293-
if flags_filter and not commit_report.flags:
294-
return UnknownFlags(f"No coverage with chosen flags: {flags_filter}")
295251

296-
report_paths = ReportPaths(
297-
report=commit_report,
298-
path=path,
299-
search_term=search_value,
300-
filter_flags=flags_filter,
301-
filter_paths=component_paths,
302-
)
303-
304-
if len(report_paths.paths) == 0:
305-
# we do not know about this path
306-
307-
if path_service.provider_path_exists(path, commit, current_owner) is False:
308-
# file doesn't exist
309-
return UnknownPath(f"path does not exist: {path}")
310-
311-
# we're just missing coverage for the file
312-
return MissingCoverage(f"missing coverage for path: {path}")
313-
314-
if search_value or display_type == PathContentDisplayType.LIST:
315-
items = report_paths.full_filelist()
316-
else:
317-
items = report_paths.single_directory()
318-
319-
sorted_items = sort_path_contents(items, filters)
252+
@commit_bindable.field("deprecatedPathContents")
253+
@sync_to_async
254+
def resolve_deprecated_path_contents(
255+
commit: Commit,
256+
info: GraphQLResolveInfo,
257+
path: str | None = None,
258+
filters: dict | None = None,
259+
first: Any = None,
260+
after: Any = None,
261+
last: Any = None,
262+
before: Any = None,
263+
) -> Any:
264+
"""
265+
The file directory tree is a list of all the files and directories
266+
extracted from the commit report of the latest, head commit.
267+
The is resolver results in a list that represent the tree with files
268+
and nested directories.
269+
"""
270+
current_owner = info.context["request"].current_owner
320271

321-
kwargs = {"first": first, "after": after, "last": last, "before": before}
272+
contents = get_sorted_path_contents(current_owner, commit, path, filters)
273+
if not isinstance(contents, list):
274+
return contents
322275

323276
return queryset_to_connection_sync(
324-
sorted_items, ordering_direction=OrderingDirection.ASC, **kwargs
277+
contents,
278+
ordering_direction=OrderingDirection.ASC,
279+
first=first,
280+
last=last,
281+
before=before,
282+
after=after,
325283
)
326284

327285

0 commit comments

Comments
 (0)