Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Enable pagination for AI enabled repos #1101

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

rohitvinnakota-codecov
Copy link
Contributor

Purpose/Motivation

What is the feature? Why is this being done?

Links to relevant tickets

What does this PR do?

Include a brief description of the changes in this PR. Bullet points are your friend.

Notes to Reviewer

Anything to note to the team? Any tips on how to review, or where to start?

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. In 2022 this entity acquired Codecov and as result Sentry is going to need some rights from me in order to utilize my contributions in this PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

Copy link

codecov bot commented Jan 15, 2025

Codecov Report

Attention: Patch coverage is 20.00000% with 8 lines in your changes missing coverage. Please review.

Project coverage is 96.05%. Comparing base (ddb4755) to head (0cb9d29).

❌ We are unable to process any of the uploaded JUnit XML files. Please ensure your files are in the right format.

Files with missing lines Patch % Lines
graphql_api/types/owner/owner.py 20.00% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1101      +/-   ##
==========================================
- Coverage   96.09%   96.05%   -0.04%     
==========================================
  Files         829      829              
  Lines       19384    19394      +10     
==========================================
+ Hits        18627    18629       +2     
- Misses        757      765       +8     
Flag Coverage Δ
unit 95.96% <20.00%> (-0.05%) ⬇️
unit-latest-uploader 95.96% <20.00%> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

✅ All tests successful. No failed tests were found.

📣 Thoughts on this report? Let Codecov know! | Powered by Codecov

@codecov-notifications
Copy link

Codecov Report

Attention: Patch coverage is 20.00000% with 8 lines in your changes missing coverage. Please review.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
graphql_api/types/owner/owner.py 20.00% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@rohitvinnakota-codecov
Copy link
Contributor Author

@codecov-ai-reviewer review2

Copy link
Contributor

On it! We are reviewing the PR and will provide feedback shortly.

@codecov codecov deleted a comment from sentry-autofix bot Jan 30, 2025
@codecov codecov deleted a comment from sentry-autofix bot Jan 30, 2025
@rohitvinnakota-codecov
Copy link
Contributor Author

@codecov-ai-reviewer review2

Copy link
Contributor

On it! We are reviewing the PR and will provide feedback shortly.

Comment on lines +1205 to +1247

@patch("services.self_hosted.get_config")
def test_ai_enabled_repositories(self, get_config_mock):
current_org = OwnerFactory(
username="random-plan-user",
service="github",
)

get_config_mock.return_value = [
{"service": "github", "ai_features_app_id": 12345},
]

query = """{
owner(username: "%s") {
aiEnabledRepos
}
}

""" % (current_org.username)
data = self.gql_request(query, owner=current_org)
assert data["owner"]["aiEnabledRepos"] is None


@patch("services.self_hosted.get_config")
def test_ai_enabled_repositories_app_not_configured(self, get_config_mock):
current_org = OwnerFactory(
username="random-plan-user",
service="github",
)

get_config_mock.return_value = [
{"service": "github", "ai_features_app_id": 12345},
]

query = """{
owner(username: "%s") {
aiEnabledRepos
}
}

""" % (current_org.username)
data = self.gql_request(query, owner=current_org)
assert data["owner"]["aiEnabledRepos"] is None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new test cases for aiEnabledRepos are well-structured and cover different scenarios. However, it would be beneficial to add a test case for when the ai_features_app_id is not present in the config. This will ensure that the feature behaves correctly when AI features are not configured.

Comment on lines +1205 to +1247

@patch("services.self_hosted.get_config")
def test_ai_enabled_repositories(self, get_config_mock):
current_org = OwnerFactory(
username="random-plan-user",
service="github",
)

get_config_mock.return_value = [
{"service": "github", "ai_features_app_id": 12345},
]

query = """{
owner(username: "%s") {
aiEnabledRepos
}
}

""" % (current_org.username)
data = self.gql_request(query, owner=current_org)
assert data["owner"]["aiEnabledRepos"] is None


@patch("services.self_hosted.get_config")
def test_ai_enabled_repositories_app_not_configured(self, get_config_mock):
current_org = OwnerFactory(
username="random-plan-user",
service="github",
)

get_config_mock.return_value = [
{"service": "github", "ai_features_app_id": 12345},
]

query = """{
owner(username: "%s") {
aiEnabledRepos
}
}

""" % (current_org.username)
data = self.gql_request(query, owner=current_org)
assert data["owner"]["aiEnabledRepos"] is None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding assertions to check the type of the returned value. For example, assert that data['owner']['aiEnabledRepos'] is a list when it's not None.

Comment on lines +42 to 52
aiEnabledRepositories(
ordering: RepositoryOrdering
orderingDirection: OrderingDirection
first: Int
after: String
last: Int
before: String
): RepositoryConnection! @cost(complexity: 25, multipliers: ["first", "last"])
uploadTokenRequired: Boolean
activatedUserCount: Int
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new aiEnabledRepositories field is well-defined with appropriate parameters. However, consider adding a description for this field to provide more context about its purpose and usage.

Suggested change
aiEnabledRepositories(
ordering: RepositoryOrdering
orderingDirection: OrderingDirection
first: Int
after: String
last: Int
before: String
): RepositoryConnection! @cost(complexity: 25, multipliers: ["first", "last"])
uploadTokenRequired: Boolean
activatedUserCount: Int
}
aiEnabledRepositories(
ordering: RepositoryOrdering
orderingDirection: OrderingDirection
first: Int
after: String
last: Int
before: String
): RepositoryConnection! @cost(complexity: 25, multipliers: ["first", "last"]) @description("Returns a connection of repositories with AI features enabled for this owner.")

@rohitvinnakota-codecov
Copy link
Contributor Author

@codecov-ai-reviewer review2

Copy link
Contributor

sentry-autofix bot commented Feb 3, 2025

On it! We are reviewing the PR and will provide feedback shortly.

Copy link
Contributor

sentry-autofix bot commented Feb 3, 2025

No changes requiring review at this time.

1 similar comment
Copy link
Contributor

sentry-autofix bot commented Feb 3, 2025

No changes requiring review at this time.

@rohitvinnakota-codecov
Copy link
Contributor Author

@codecov-ai-reviewer review2

@codecov codecov deleted a comment from sentry-autofix bot Feb 3, 2025
@codecov codecov deleted a comment from sentry-autofix bot Feb 3, 2025
Copy link
Contributor

sentry-autofix bot commented Feb 3, 2025

Sentry has generated a new PR with unit tests for this PR. View the new PR(#1126) to review the changes.

@sentry-autofix sentry-autofix bot mentioned this pull request Feb 3, 2025
@rohitvinnakota-codecov
Copy link
Contributor Author

@codecov-ai-reviewer review2

@codecov codecov deleted a comment from sentry-autofix bot Feb 3, 2025
Copy link
Contributor

sentry-autofix bot commented Feb 3, 2025

No changes requiring review at this time.

@rohitvinnakota-codecov
Copy link
Contributor Author

@codecov-ai-reviewer review2

Copy link

codecov-ai bot commented Feb 4, 2025

On it! We are reviewing the PR and will provide feedback shortly.

Comment on lines 1202 to +1247
]
}
}

@patch("services.self_hosted.get_config")
def test_ai_enabled_repositories(self, get_config_mock):
current_org = OwnerFactory(
username="random-plan-user",
service="github",
)

get_config_mock.return_value = [
{"service": "github", "ai_features_app_id": 12345},
]

query = """{
owner(username: "%s") {
aiEnabledRepos
}
}

""" % (current_org.username)
data = self.gql_request(query, owner=current_org)
assert data["owner"]["aiEnabledRepos"] is None


@patch("services.self_hosted.get_config")
def test_ai_enabled_repositories_app_not_configured(self, get_config_mock):
current_org = OwnerFactory(
username="random-plan-user",
service="github",
)

get_config_mock.return_value = [
{"service": "github", "ai_features_app_id": 12345},
]

query = """{
owner(username: "%s") {
aiEnabledRepos
}
}

""" % (current_org.username)
data = self.gql_request(query, owner=current_org)
assert data["owner"]["aiEnabledRepos"] is None
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new test cases are well-structured and cover important scenarios. However, it would be beneficial to add a test case for when ai_features_app_id is not present in the config. This would ensure robustness against potential configuration issues.

Comment on lines 1202 to +1247
]
}
}

@patch("services.self_hosted.get_config")
def test_ai_enabled_repositories(self, get_config_mock):
current_org = OwnerFactory(
username="random-plan-user",
service="github",
)

get_config_mock.return_value = [
{"service": "github", "ai_features_app_id": 12345},
]

query = """{
owner(username: "%s") {
aiEnabledRepos
}
}

""" % (current_org.username)
data = self.gql_request(query, owner=current_org)
assert data["owner"]["aiEnabledRepos"] is None


@patch("services.self_hosted.get_config")
def test_ai_enabled_repositories_app_not_configured(self, get_config_mock):
current_org = OwnerFactory(
username="random-plan-user",
service="github",
)

get_config_mock.return_value = [
{"service": "github", "ai_features_app_id": 12345},
]

query = """{
owner(username: "%s") {
aiEnabledRepos
}
}

""" % (current_org.username)
data = self.gql_request(query, owner=current_org)
assert data["owner"]["aiEnabledRepos"] is None
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a test case where repository_service_ids contains multiple IDs to ensure the function correctly handles multiple repositories.

Comment on lines +42 to +49
aiEnabledRepositories(
ordering: RepositoryOrdering
orderingDirection: OrderingDirection
first: Int
after: String
last: Int
before: String
): RepositoryConnection! @cost(complexity: 25, multipliers: ["first", "last"])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new aiEnabledRepositories field is well-defined with appropriate arguments. However, consider adding a description for this field to improve the schema documentation.

Suggested change
aiEnabledRepositories(
ordering: RepositoryOrdering
orderingDirection: OrderingDirection
first: Int
after: String
last: Int
before: String
): RepositoryConnection! @cost(complexity: 25, multipliers: ["first", "last"])
aiEnabledRepositories(
ordering: RepositoryOrdering
orderingDirection: OrderingDirection
first: Int
after: String
last: Int
before: String
): RepositoryConnection! @cost(complexity: 25, multipliers: ["first", "last"]) @description("Returns a connection of repositories that have AI features enabled")

Comment on lines +399 to +428

@owner_bindable.field("aiEnabledRepositories")
def resolve_ai_enabled_repositories(
owner: Owner,
info: GraphQLResolveInfo,
ordering: Optional[RepositoryOrdering] = RepositoryOrdering.ID,
ordering_direction: Optional[OrderingDirection] = OrderingDirection.ASC,
**kwargs: Any,
) -> Coroutine[Any, Any, Connection]:
ai_features_app_install = GithubAppInstallation.objects.filter(
app_id=AI_FEATURES_GH_APP_ID, owner=owner
).first()

if not ai_features_app_install:
return None

current_owner = info.context["request"].current_owner
queryset = Repository.objects.filter(author=owner).viewable_repos(current_owner)

if ai_features_app_install.repository_service_ids:
queryset = queryset.filter(
service_id__in=ai_features_app_install.repository_service_ids
)

return queryset_to_connection(
queryset,
ordering=(ordering, RepositoryOrdering.ID),
ordering_direction=ordering_direction,
**kwargs,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resolve_ai_enabled_repositories function is well-implemented. However, consider adding error handling for cases where the database query might fail.

Comment on lines +399 to +428

@owner_bindable.field("aiEnabledRepositories")
def resolve_ai_enabled_repositories(
owner: Owner,
info: GraphQLResolveInfo,
ordering: Optional[RepositoryOrdering] = RepositoryOrdering.ID,
ordering_direction: Optional[OrderingDirection] = OrderingDirection.ASC,
**kwargs: Any,
) -> Coroutine[Any, Any, Connection]:
ai_features_app_install = GithubAppInstallation.objects.filter(
app_id=AI_FEATURES_GH_APP_ID, owner=owner
).first()

if not ai_features_app_install:
return None

current_owner = info.context["request"].current_owner
queryset = Repository.objects.filter(author=owner).viewable_repos(current_owner)

if ai_features_app_install.repository_service_ids:
queryset = queryset.filter(
service_id__in=ai_features_app_install.repository_service_ids
)

return queryset_to_connection(
queryset,
ordering=(ordering, RepositoryOrdering.ID),
ordering_direction=ordering_direction,
**kwargs,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't handle the case where AI_FEATURES_GH_APP_ID might not be defined. Consider adding a check for this to prevent potential KeyError exceptions.

Comment on lines +399 to +428

@owner_bindable.field("aiEnabledRepositories")
def resolve_ai_enabled_repositories(
owner: Owner,
info: GraphQLResolveInfo,
ordering: Optional[RepositoryOrdering] = RepositoryOrdering.ID,
ordering_direction: Optional[OrderingDirection] = OrderingDirection.ASC,
**kwargs: Any,
) -> Coroutine[Any, Any, Connection]:
ai_features_app_install = GithubAppInstallation.objects.filter(
app_id=AI_FEATURES_GH_APP_ID, owner=owner
).first()

if not ai_features_app_install:
return None

current_owner = info.context["request"].current_owner
queryset = Repository.objects.filter(author=owner).viewable_repos(current_owner)

if ai_features_app_install.repository_service_ids:
queryset = queryset.filter(
service_id__in=ai_features_app_install.repository_service_ids
)

return queryset_to_connection(
queryset,
ordering=(ordering, RepositoryOrdering.ID),
ordering_direction=ordering_direction,
**kwargs,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function returns None if ai_features_app_install is not found. However, the GraphQL schema defines this field as non-nullable (RepositoryConnection!). Consider returning an empty connection instead to match the schema.

Suggested change
@owner_bindable.field("aiEnabledRepositories")
def resolve_ai_enabled_repositories(
owner: Owner,
info: GraphQLResolveInfo,
ordering: Optional[RepositoryOrdering] = RepositoryOrdering.ID,
ordering_direction: Optional[OrderingDirection] = OrderingDirection.ASC,
**kwargs: Any,
) -> Coroutine[Any, Any, Connection]:
ai_features_app_install = GithubAppInstallation.objects.filter(
app_id=AI_FEATURES_GH_APP_ID, owner=owner
).first()
if not ai_features_app_install:
return None
current_owner = info.context["request"].current_owner
queryset = Repository.objects.filter(author=owner).viewable_repos(current_owner)
if ai_features_app_install.repository_service_ids:
queryset = queryset.filter(
service_id__in=ai_features_app_install.repository_service_ids
)
return queryset_to_connection(
queryset,
ordering=(ordering, RepositoryOrdering.ID),
ordering_direction=ordering_direction,
**kwargs,
)
if not ai_features_app_install:
return Connection(edges=[], page_info=PageInfo(has_next_page=False, has_previous_page=False))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant