Skip to content

Commit 5f2cb34

Browse files
committed
Refactor and optimize querying pathContents
First of all, this fixes and adds missing type annotations. It then deduplicates the code between `pathContents` and the `deprecated` variant, which were 99% identical. Resolving the path contents was also optimized a little bit by not filtering the report for "files matching *any* session in the report", aka "all files". Other optimizations include avoiding some intermediate allocations by sorting lists in-place, and simplifying some functions.
1 parent 7bd85ae commit 5f2cb34

File tree

4 files changed

+97
-183
lines changed

4 files changed

+97
-183
lines changed

graphql_api/actions/path_contents.py

+3-31
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/types/commit/commit.py

+61-100
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,
@@ -214,32 +211,33 @@ def resolve_path_contents(commit: Commit, info, path: str = None, filters=None):
214211
if len(report_paths.paths) == 0:
215212
# we do not know about this path
216213

217-
if path_service.provider_path_exists(path, commit, current_owner) is False:
214+
if (
215+
not path
216+
or path_service.provider_path_exists(path, commit, current_owner) is False
217+
):
218218
# file doesn't exist
219219
return UnknownPath(f"path does not exist: {path}")
220220

221221
# we're just missing coverage for the file
222222
return MissingCoverage(f"missing coverage for path: {path}")
223223

224+
items: list[File | Dir]
224225
if search_value or display_type == PathContentDisplayType.LIST:
225226
items = report_paths.full_filelist()
226227
else:
227228
items = report_paths.single_directory()
228-
return {"results": sort_path_contents(items, filters)}
229+
return sort_path_contents(items, filters)
229230

230231

231-
@commit_bindable.field("deprecatedPathContents")
232+
@sentry_sdk.trace
233+
@commit_bindable.field("pathContents")
232234
@sync_to_async
233-
def resolve_deprecated_path_contents(
235+
def resolve_path_contents(
234236
commit: Commit,
235-
info,
236-
path: str = None,
237-
filters=None,
238-
first=None,
239-
after=None,
240-
last=None,
241-
before=None,
242-
):
237+
info: GraphQLResolveInfo,
238+
path: str | None = None,
239+
filters: dict | None = None,
240+
) -> Any:
243241
"""
244242
The file directory tree is a list of all the files and directories
245243
extracted from the commit report of the latest, head commit.
@@ -248,80 +246,43 @@ def resolve_deprecated_path_contents(
248246
"""
249247
current_owner = info.context["request"].current_owner
250248

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()
249+
contents = get_sorted_path_contents(current_owner, commit, path, filters)
250+
if isinstance(contents, list):
251+
return {"results": contents}
252+
return contents
257253

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-
)
274254

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
292-
293-
if flags_filter and not commit_report.flags:
294-
return UnknownFlags(f"No coverage with chosen flags: {flags_filter}")
295-
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)
255+
@commit_bindable.field("deprecatedPathContents")
256+
@sync_to_async
257+
def resolve_deprecated_path_contents(
258+
commit: Commit,
259+
info: GraphQLResolveInfo,
260+
path: str | None = None,
261+
filters: dict | None = None,
262+
first: Any = None,
263+
after: Any = None,
264+
last: Any = None,
265+
before: Any = None,
266+
) -> Any:
267+
"""
268+
The file directory tree is a list of all the files and directories
269+
extracted from the commit report of the latest, head commit.
270+
The is resolver results in a list that represent the tree with files
271+
and nested directories.
272+
"""
273+
current_owner = info.context["request"].current_owner
320274

321-
kwargs = {"first": first, "after": after, "last": last, "before": before}
275+
contents = get_sorted_path_contents(current_owner, commit, path, filters)
276+
if not isinstance(contents, list):
277+
return contents
322278

323279
return queryset_to_connection_sync(
324-
sorted_items, ordering_direction=OrderingDirection.ASC, **kwargs
280+
contents,
281+
ordering_direction=OrderingDirection.ASC,
282+
first=first,
283+
last=last,
284+
before=before,
285+
after=after,
325286
)
326287

327288

0 commit comments

Comments
 (0)