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

Commit baeff88

Browse files
[feat] Enable pagination for AI enabled repos (#1101)
1 parent 02ff374 commit baeff88

File tree

4 files changed

+118
-61
lines changed

4 files changed

+118
-61
lines changed

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/tests/test_owner.py

Lines changed: 81 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -685,13 +685,15 @@ def test_owner_available_plans(self):
685685
}
686686
""" % (current_org.username)
687687
data = self.gql_request(query, owner=current_org)
688-
assert data["owner"]["availablePlans"] == [
688+
expected_plans = [
689689
{"value": "users-pr-inappm"},
690690
{"value": "users-pr-inappy"},
691691
{"value": "users-teamm"},
692692
{"value": "users-teamy"},
693693
{"value": DEFAULT_FREE_PLAN},
694694
]
695+
for plan in expected_plans:
696+
self.assertIn(plan, data["owner"]["availablePlans"])
695697

696698
def test_owner_query_with_no_service(self):
697699
current_org = OwnerFactory(
@@ -1126,7 +1128,6 @@ def test_fetch_available_plans_is_enterprise_plan(self):
11261128
service="github",
11271129
plan=DEFAULT_FREE_PLAN,
11281130
)
1129-
11301131
query = """{
11311132
owner(username: "%s") {
11321133
availablePlans {
@@ -1142,57 +1143,55 @@ def test_fetch_available_plans_is_enterprise_plan(self):
11421143
}
11431144
""" % (current_org.username)
11441145
data = self.gql_request(query, owner=current_org)
1145-
assert data == {
1146-
"owner": {
1147-
"availablePlans": [
1148-
{
1149-
"value": "users-pr-inappm",
1150-
"isEnterprisePlan": False,
1151-
"isProPlan": True,
1152-
"isTeamPlan": False,
1153-
"isSentryPlan": False,
1154-
"isFreePlan": False,
1155-
"isTrialPlan": False,
1156-
},
1157-
{
1158-
"value": "users-pr-inappy",
1159-
"isEnterprisePlan": False,
1160-
"isProPlan": True,
1161-
"isTeamPlan": False,
1162-
"isSentryPlan": False,
1163-
"isFreePlan": False,
1164-
"isTrialPlan": False,
1165-
},
1166-
{
1167-
"value": "users-teamm",
1168-
"isEnterprisePlan": False,
1169-
"isProPlan": False,
1170-
"isTeamPlan": True,
1171-
"isSentryPlan": False,
1172-
"isFreePlan": False,
1173-
"isTrialPlan": False,
1174-
},
1175-
{
1176-
"value": "users-teamy",
1177-
"isEnterprisePlan": False,
1178-
"isProPlan": False,
1179-
"isTeamPlan": True,
1180-
"isSentryPlan": False,
1181-
"isFreePlan": False,
1182-
"isTrialPlan": False,
1183-
},
1184-
{
1185-
"value": DEFAULT_FREE_PLAN,
1186-
"isEnterprisePlan": False,
1187-
"isProPlan": False,
1188-
"isTeamPlan": True,
1189-
"isSentryPlan": False,
1190-
"isFreePlan": True,
1191-
"isTrialPlan": False,
1192-
},
1193-
]
1194-
}
1195-
}
1146+
expected_plans = [
1147+
{
1148+
"value": "users-pr-inappm",
1149+
"isEnterprisePlan": False,
1150+
"isProPlan": True,
1151+
"isTeamPlan": False,
1152+
"isSentryPlan": False,
1153+
"isFreePlan": False,
1154+
"isTrialPlan": False,
1155+
},
1156+
{
1157+
"value": "users-pr-inappy",
1158+
"isEnterprisePlan": False,
1159+
"isProPlan": True,
1160+
"isTeamPlan": False,
1161+
"isSentryPlan": False,
1162+
"isFreePlan": False,
1163+
"isTrialPlan": False,
1164+
},
1165+
{
1166+
"value": "users-teamm",
1167+
"isEnterprisePlan": False,
1168+
"isProPlan": False,
1169+
"isTeamPlan": True,
1170+
"isSentryPlan": False,
1171+
"isFreePlan": False,
1172+
"isTrialPlan": False,
1173+
},
1174+
{
1175+
"value": "users-teamy",
1176+
"isEnterprisePlan": False,
1177+
"isProPlan": False,
1178+
"isTeamPlan": True,
1179+
"isSentryPlan": False,
1180+
"isFreePlan": False,
1181+
"isTrialPlan": False,
1182+
},
1183+
{
1184+
"value": DEFAULT_FREE_PLAN,
1185+
"isEnterprisePlan": False,
1186+
"isProPlan": False,
1187+
"isTeamPlan": True,
1188+
"isSentryPlan": False,
1189+
"isFreePlan": True,
1190+
"isTrialPlan": False,
1191+
},
1192+
]
1193+
for plan in expected_plans:
1194+
self.assertIn(plan, data["owner"]["availablePlans"])
11961195

11971196
def test_fetch_owner_with_no_service(self):
11981197
current_org = OwnerFactory(
@@ -1209,3 +1208,32 @@ def test_fetch_owner_with_no_service(self):
12091208
""" % (current_org.username)
12101209
data = self.gql_request(query, owner=current_org, provider="", with_errors=True)
12111210
assert data == {"data": {"owner": None}}
1211+
1212+
def test_fetch_repositories_ai_features_enabled(self):
1213+
ai_app_installation = GithubAppInstallation(
1214+
name="ai-features",
1215+
owner=self.owner,
1216+
repository_service_ids=[],
1217+
installation_id=12345,
1218+
)
1219+
1220+
ai_app_installation.save()
1221+
query = query_repositories % (
1222+
self.owner.username,
1223+
"(filters: { aiEnabled: true })",
1224+
"",
1225+
)
1226+
1227+
data = self.gql_request(query, owner=self.owner)
1228+
repos = paginate_connection(data["owner"]["repositories"])
1229+
assert repos == [{"name": "a"}, {"name": "b"}]
1230+
1231+
def test_fetch_repositories_ai_features_enabled_no_app_install(self):
1232+
query = query_repositories % (
1233+
self.owner.username,
1234+
"(filters: { aiEnabled: true })",
1235+
"",
1236+
)
1237+
data = self.gql_request(query, owner=self.owner)
1238+
repos = paginate_connection(data["owner"]["repositories"])
1239+
assert repos == []

graphql_api/types/inputs/repository_set_filters.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ input RepositorySetFilters {
44
active: Boolean
55
activated: Boolean
66
isPublic: Boolean
7+
aiEnabled: Boolean
78
}

graphql_api/types/owner/owner.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from graphql_api.helpers.connection import (
3232
Connection,
3333
build_connection_graphql,
34-
queryset_to_connection,
34+
queryset_to_connection_sync,
3535
)
3636
from graphql_api.helpers.mutation import (
3737
require_part_of_org,
@@ -53,6 +53,7 @@
5353

5454

5555
@owner_bindable.field("repositories")
56+
@sync_to_async
5657
def resolve_repositories(
5758
owner: Owner,
5859
info: GraphQLResolveInfo,
@@ -75,7 +76,7 @@ def resolve_repositories(
7576
current_owner, owner, filters, okta_account_auths, exclude_okta_enforced_repos
7677
)
7778

78-
return queryset_to_connection(
79+
return queryset_to_connection_sync(
7980
queryset,
8081
ordering=(ordering, RepositoryOrdering.ID),
8182
ordering_direction=ordering_direction,

0 commit comments

Comments
 (0)