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

Commit bc045f0

Browse files
committed
Merge branch 'main' into Ajay/uv-test
2 parents 0d41ce8 + f4f7dc8 commit bc045f0

File tree

19 files changed

+829
-168
lines changed

19 files changed

+829
-168
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,6 @@ jobs:
3636
uses: codecov/gha-workflows/.github/workflows/[email protected]
3737
secrets: inherit
3838

39-
# ats:
40-
# name: ATS
41-
# needs: [build]
42-
# if: ${{ !github.event.pull_request.head.repo.fork && github.repository_owner == 'codecov' }}
43-
# uses: codecov/gha-workflows/.github/workflows/[email protected]
44-
# secrets: inherit
45-
# with:
46-
# repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}
47-
# codecov_cli_upload_args: '--plugin pycoverage --plugin compress-pycoverage --flag smart-labels'
48-
# app_container_name: api
4939
test:
5040
name: Test
5141
needs: [build]

Makefile

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ test_env.install_cli:
205205

206206
test_env.container_prepare:
207207
apt-get -y install git build-essential netcat-traditional
208-
git config --global --add safe.directory /app
208+
git config --global --add safe.directory /app || true
209209

210210
test_env.container_check_db:
211211
while ! nc -vz postgres 5432; do sleep 1; echo "waiting for postgres"; done
@@ -229,24 +229,6 @@ test_env.run_integration:
229229
test_env.check-for-migration-conflicts:
230230
docker compose exec api python manage.py check_for_migration_conflicts
231231

232-
test_env.static_analysis:
233-
docker compose exec api make test_env.container_static_analysis CODECOV_STATIC_TOKEN=${CODECOV_STATIC_TOKEN}
234-
235-
test_env.label_analysis:
236-
docker compose exec api make test_env.container_label_analysis CODECOV_STATIC_TOKEN=${CODECOV_STATIC_TOKEN}
237-
238-
test_env.ats:
239-
docker compose exec api make test_env.container_ats CODECOV_UPLOAD_TOKEN=${CODECOV_UPLOAD_TOKEN}
240-
241-
test_env.container_static_analysis:
242-
codecovcli -u ${CODECOV_URL} static-analysis --token=${CODECOV_STATIC_TOKEN}
243-
244-
test_env.container_label_analysis:
245-
codecovcli -u ${CODECOV_URL} label-analysis --base-sha=${merge_sha} --token=${CODECOV_STATIC_TOKEN}
246-
247-
test_env.container_ats:
248-
codecovcli -u ${CODECOV_URL} --codecov-yml-path=codecov_cli.yml upload-process --plugin pycoverage --plugin compress-pycoverage --flag smart-labels --fail-on-error
249-
250232
test_env:
251233
make test_env.up
252234
make test_env.prepare

codecov.yml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,5 @@
11
ignore:
22
- "**tests**/test_*.py"
3-
comment:
4-
show_critical_paths: true
5-
6-
beta_groups:
7-
- "labels"
8-
9-
flag_management:
10-
individual_flags:
11-
- name: "smart-labels"
12-
carryforward: true
13-
carryforward_mode: "labels"
143

154
codecov:
165
require_ci_to_pass: false

codecov_cli.yml

Lines changed: 0 additions & 4 deletions
This file was deleted.

core/commands/commit/interactors/get_file_content.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,15 @@ async def get_file_from_service(self, commit: Commit, path: str) -> str | None:
2222
return content.get("content")
2323
return content.get("content").decode("utf-8")
2424
# TODO raise this to the API so we can handle it.
25-
except Exception:
26-
log.info(
25+
except Exception as e:
26+
log.warning(
2727
"GetFileContentInteractor - exception raised",
28-
extra=dict(commitid=commit.commitid),
28+
extra=dict(
29+
commitid=commit.commitid,
30+
path=path,
31+
error_name=type(e).__name__,
32+
error_message=str(e),
33+
),
2934
)
3035
return None
3136

graphql_api/actions/repository.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@
33

44
import sentry_sdk
55
from django.db.models import QuerySet
6-
from shared.django_apps.codecov_auth.models import Owner
6+
from shared.django_apps.codecov_auth.models import GithubAppInstallation, Owner
77
from shared.django_apps.core.models import Repository
88

9+
from utils.config import get_config
10+
911
log = logging.getLogger(__name__)
12+
AI_FEATURES_GH_APP_ID = get_config("github", "ai_features_app_id")
1013

1114

1215
def apply_filters_to_queryset(
13-
queryset: QuerySet, filters: dict[str, Any] | None
16+
queryset: QuerySet, filters: dict[str, Any] | None, owner: Owner | None = None
1417
) -> QuerySet:
1518
filters = filters or {}
1619
term = filters.get("term")
1720
active = filters.get("active")
1821
activated = filters.get("activated")
1922
repo_names = filters.get("repo_names")
2023
is_public = filters.get("is_public")
24+
ai_enabled = filters.get("ai_enabled")
2125

2226
if repo_names:
2327
queryset = queryset.filter(name__in=repo_names)
@@ -29,6 +33,23 @@ def apply_filters_to_queryset(
2933
queryset = queryset.filter(active=active)
3034
if is_public is not None:
3135
queryset = queryset.filter(private=not is_public)
36+
if ai_enabled is not None:
37+
queryset = filter_queryset_by_ai_enabled_repos(queryset, owner)
38+
return queryset
39+
40+
41+
def filter_queryset_by_ai_enabled_repos(queryset: QuerySet, owner: Owner) -> QuerySet:
42+
ai_features_app_install = GithubAppInstallation.objects.filter(
43+
app_id=AI_FEATURES_GH_APP_ID, owner=owner
44+
).first()
45+
46+
if not ai_features_app_install:
47+
return Repository.objects.none()
48+
49+
if ai_features_app_install.repository_service_ids:
50+
queryset = queryset.filter(
51+
service_id__in=ai_features_app_install.repository_service_ids
52+
)
3253

3354
return queryset
3455

@@ -42,15 +63,21 @@ def list_repository_for_owner(
4263
exclude_okta_enforced_repos: bool = True,
4364
) -> QuerySet:
4465
queryset = Repository.objects.viewable_repos(current_owner)
66+
filters = filters or {}
67+
ai_enabled_filter = filters.get("ai_enabled")
68+
69+
if ai_enabled_filter:
70+
return filter_queryset_by_ai_enabled_repos(queryset, owner)
4571

4672
if exclude_okta_enforced_repos:
4773
queryset = queryset.exclude_accounts_enforced_okta(okta_account_auths)
4874

49-
queryset = (
50-
queryset.with_recent_coverage().with_latest_commit_at().filter(author=owner)
51-
)
75+
if not ai_enabled_filter:
76+
queryset = (
77+
queryset.with_recent_coverage().with_latest_commit_at().filter(author=owner)
78+
)
5279

53-
queryset = apply_filters_to_queryset(queryset, filters)
80+
queryset = apply_filters_to_queryset(queryset, filters, owner)
5481
return queryset
5582

5683

graphql_api/helpers/connection.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import enum
2+
from base64 import b64decode
23
from dataclasses import dataclass
34
from functools import cached_property
45
from typing import Any, Dict, List, Optional
56

6-
from cursor_pagination import CursorPage, CursorPaginator
7+
from cursor_pagination import CursorPage, CursorPaginator, InvalidCursor
78
from django.db.models import QuerySet
89

910
from codecov.commands.exceptions import ValidationError
@@ -181,6 +182,7 @@ def page_info(self) -> Dict[str, Any]:
181182

182183

183184
class DictCursorPaginator(CursorPaginator):
185+
NULL_VALUE_REPR = "\x1f"
184186
"""
185187
WARNING: DictCursorPaginator does not work for dict objects where a key contains the following string: "__"
186188
TODO: if instance is a dictionary and not an object, don't split the ordering
@@ -205,6 +207,17 @@ class DictCursorPaginator(CursorPaginator):
205207
if the dict access fails then it throws an exception, although it would be a different
206208
"""
207209

210+
def decode_cursor(self, cursor):
211+
try:
212+
orderings = b64decode(cursor.encode("ascii")).decode("utf8")
213+
orderings = orderings.split(self.delimiter)
214+
return [
215+
None if ordering == self.NULL_VALUE_REPR else ordering
216+
for ordering in orderings
217+
]
218+
except (TypeError, ValueError):
219+
raise InvalidCursor(self.invalid_cursor_message)
220+
208221
def position_from_instance(self, instance):
209222
position = []
210223
for order in self.ordering:
@@ -219,7 +232,7 @@ def position_from_instance(self, instance):
219232
except (KeyError, TypeError):
220233
raise attr_err from None
221234
parts.pop(0)
222-
position.append(str(attr))
235+
position.append(self.NULL_VALUE_REPR if attr is None else str(attr))
223236
return position
224237

225238

graphql_api/helpers/tests/test_connection.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,21 @@ def test_invalid_cursors(self):
155155

156156
with self.assertRaises(ValidationError):
157157
queryset_to_connection_sync(data, first=3, after="invalid")
158+
159+
def test_dict_cursor_paginator_null_encoding(self):
160+
from graphql_api.helpers.connection import DictCursorPaginator, field_order
161+
162+
repo_1 = RepositoryFactory(name="a", active=None)
163+
repo_2 = RepositoryFactory(name="b", active=True)
164+
repo_3 = RepositoryFactory(name="c", active=False)
165+
r = Repository.objects.all()
166+
167+
ordering = tuple(
168+
field_order(field, OrderingDirection.ASC) for field in ("active",)
169+
)
170+
171+
paginator = DictCursorPaginator(r, ordering=ordering)
172+
173+
assert paginator.position_from_instance(repo_1) == ["\x1f"]
174+
assert paginator.position_from_instance(repo_2) == ["True"]
175+
assert paginator.position_from_instance(repo_3) == ["False"]

graphql_api/tests/test_branch.py

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -772,18 +772,15 @@ def test_fetch_path_contents_component_filter_missing_coverage(
772772
@patch("services.components.component_filtered_report")
773773
@patch("services.components.commit_components")
774774
@patch("shared.reports.api_report_service.build_report_from_commit")
775+
@patch("services.path.ReportPaths.files", new_callable=PropertyMock)
775776
def test_fetch_path_contents_component_filter_has_coverage(
776-
self, report_mock, commit_components_mock, filtered_mock
777+
self, files_mock, report_mock, commit_components_mock, filtered_mock
777778
):
778-
components = ["Global"]
779-
variables = {
780-
"org": self.org.username,
781-
"repo": self.repo.name,
782-
"branch": self.branch.name,
783-
"path": "",
784-
"filters": {"components": components},
785-
}
786-
779+
files_mock.return_value = [
780+
"folder/fileB.py",
781+
"folder/subfolder/fileC.py",
782+
"folder/subfolder/fileD.py",
783+
]
787784
report_mock.return_value = MockReport()
788785
commit_components_mock.return_value = [
789786
Component.from_dict(
@@ -810,6 +807,14 @@ def test_fetch_path_contents_component_filter_has_coverage(
810807
]
811808
filtered_mock.return_value = MockReport()
812809

810+
components = ["Global"]
811+
variables = {
812+
"org": self.org.username,
813+
"repo": self.repo.name,
814+
"branch": self.branch.name,
815+
"path": "",
816+
"filters": {"components": components},
817+
}
813818
data = self.gql_request(query_files, variables=variables)
814819

815820
assert data == {
@@ -841,18 +846,15 @@ def test_fetch_path_contents_component_filter_has_coverage(
841846
@patch("services.components.component_filtered_report")
842847
@patch("services.components.commit_components")
843848
@patch("shared.reports.api_report_service.build_report_from_commit")
844-
@patch("services.report.files_belonging_to_flags")
845-
@patch("services.report.files_in_sessions")
849+
@patch("services.path.ReportPaths.files", new_callable=PropertyMock)
846850
def test_fetch_path_contents_component_and_flag_filters(
847851
self,
848-
session_files_mock,
849-
flag_files_mock,
852+
files_mock,
850853
report_mock,
851854
commit_components_mock,
852855
filtered_mock,
853856
):
854-
session_files_mock.return_value = ["fileA.py"]
855-
flag_files_mock.return_value = ["fileA.py"]
857+
files_mock.return_value = ["fileA.py"]
856858
report_mock.return_value = MockReport()
857859
commit_components_mock.return_value = [
858860
Component.from_dict(
@@ -945,11 +947,11 @@ def test_fetch_path_contents_component_and_flag_filters(
945947
@patch("services.components.component_filtered_report")
946948
@patch("services.components.commit_components")
947949
@patch("shared.reports.api_report_service.build_report_from_commit")
948-
@patch("services.report.files_belonging_to_flags")
950+
@patch("services.path.ReportPaths.files", new_callable=PropertyMock)
949951
def test_fetch_path_contents_component_and_flag_filters_unknown_flags(
950-
self, flag_files_mock, report_mock, commit_components_mock, filtered_mock
952+
self, files_mock, report_mock, commit_components_mock, filtered_mock
951953
):
952-
flag_files_mock.return_value = ["fileA.py"]
954+
files_mock.return_value = ["fileA.py"]
953955
report_mock.return_value = MockNoFlagsReport()
954956
commit_components_mock.return_value = [
955957
Component.from_dict(
@@ -1035,18 +1037,15 @@ def test_fetch_path_contents_component_and_flag_filters_unknown_flags(
10351037
@patch("services.components.component_filtered_report")
10361038
@patch("services.components.commit_components")
10371039
@patch("shared.reports.api_report_service.build_report_from_commit")
1038-
@patch("services.report.files_belonging_to_flags")
1039-
@patch("services.report.files_in_sessions")
1040+
@patch("services.path.ReportPaths.files", new_callable=PropertyMock)
10401041
def test_fetch_path_contents_component_flags_filters(
10411042
self,
1042-
session_files_mock,
1043-
flag_files_mock,
1043+
files_mock,
10441044
report_mock,
10451045
commit_components_mock,
10461046
filtered_mock,
10471047
):
1048-
session_files_mock.return_value = ["fileA.py"]
1049-
flag_files_mock.return_value = ["fileA.py"]
1048+
files_mock.return_value = ["fileA.py"]
10501049
report_mock.return_value = MockReport()
10511050
commit_components_mock.return_value = [
10521051
Component.from_dict(

0 commit comments

Comments
 (0)