diff --git a/backend/apps/github/api/internal/nodes/issue.py b/backend/apps/github/api/internal/nodes/issue.py index 58e9f70fc6..45b6205c81 100644 --- a/backend/apps/github/api/internal/nodes/issue.py +++ b/backend/apps/github/api/internal/nodes/issue.py @@ -7,6 +7,7 @@ from apps.github.api.internal.nodes.pull_request import PullRequestNode from apps.github.api.internal.nodes.user import UserNode from apps.github.models.issue import Issue +from apps.github.models.label import Label from apps.github.models.pull_request import PullRequest from apps.mentorship.models.issue_user_interest import IssueUserInterest @@ -19,6 +20,12 @@ to_attr="merged_pull_requests", ) +LABELS_PREFETCH = Prefetch( + "labels", + queryset=Label.objects.all(), + to_attr="label_names", +) + @strawberry_django.type( Issue, @@ -52,10 +59,10 @@ def repository_name(self, root: Issue) -> str | None: """Resolve the repository name.""" return root.repository.name if root.repository else None - @strawberry_django.field(prefetch_related=["labels"]) + @strawberry_django.field(prefetch_related=[LABELS_PREFETCH]) def labels(self, root: Issue) -> list[str]: """Resolve label names for the issue.""" - return [label.name for label in root.labels.all()] + return [label.name for label in getattr(root, "label_names", [])] @strawberry_django.field(prefetch_related=[MERGED_PULL_REQUESTS_PREFETCH]) def is_merged(self, root: Issue) -> bool: diff --git a/backend/apps/github/api/internal/nodes/repository.py b/backend/apps/github/api/internal/nodes/repository.py index 189bb3b514..c1acadc9a1 100644 --- a/backend/apps/github/api/internal/nodes/repository.py +++ b/backend/apps/github/api/internal/nodes/repository.py @@ -4,13 +4,15 @@ import strawberry import strawberry_django +from django.db.models import Prefetch from apps.common.utils import normalize_limit -from apps.github.api.internal.nodes.issue import IssueNode +from apps.github.api.internal.nodes.issue import LABELS_PREFETCH, IssueNode from apps.github.api.internal.nodes.milestone import MilestoneNode from apps.github.api.internal.nodes.organization import OrganizationNode from apps.github.api.internal.nodes.release import ReleaseNode from apps.github.api.internal.nodes.repository_contributor import RepositoryContributorNode +from apps.github.models.issue import Issue from apps.github.models.repository import Repository if TYPE_CHECKING: @@ -45,10 +47,24 @@ class RepositoryNode(strawberry.relay.Node): organization: OrganizationNode | None = strawberry_django.field() - @strawberry_django.field(prefetch_related=["issues"]) + @strawberry_django.field( + prefetch_related=[ + Prefetch( + "issues", + queryset=Issue.objects.select_related( + "author__owasp_profile", "repository__organization" + ) + .prefetch_related(LABELS_PREFETCH) + .order_by("-created_at")[:RECENT_ISSUES_LIMIT], + to_attr="recent_issues", + ) + ] + ) def issues(self, root: Repository) -> list[IssueNode]: """Resolve recent issues.""" # TODO(arkid15r): rename this to recent_issues. + if hasattr(root, "recent_issues"): + return root.recent_issues return root.issues.order_by("-created_at")[:RECENT_ISSUES_LIMIT] @strawberry_django.field diff --git a/backend/tests/apps/github/api/internal/nodes/issue_test.py b/backend/tests/apps/github/api/internal/nodes/issue_test.py index 9fb36a6a98..a8f96a7ef5 100644 --- a/backend/tests/apps/github/api/internal/nodes/issue_test.py +++ b/backend/tests/apps/github/api/internal/nodes/issue_test.py @@ -93,7 +93,7 @@ def test_labels(self): mock_label1.name = "bug" mock_label2 = Mock() mock_label2.name = "enhancement" - mock_issue.labels.all.return_value = [mock_label1, mock_label2] + mock_issue.label_names = [mock_label1, mock_label2] field = self._get_field_by_name("labels", IssueNode) result = field.base_resolver.wrapped_func(None, mock_issue) diff --git a/backend/tests/apps/github/api/internal/nodes/repository_test.py b/backend/tests/apps/github/api/internal/nodes/repository_test.py index 5f42907df3..f996403149 100644 --- a/backend/tests/apps/github/api/internal/nodes/repository_test.py +++ b/backend/tests/apps/github/api/internal/nodes/repository_test.py @@ -6,7 +6,7 @@ from apps.github.api.internal.nodes.milestone import MilestoneNode from apps.github.api.internal.nodes.organization import OrganizationNode from apps.github.api.internal.nodes.release import ReleaseNode -from apps.github.api.internal.nodes.repository import RepositoryNode +from apps.github.api.internal.nodes.repository import RECENT_ISSUES_LIMIT, RepositoryNode from apps.github.api.internal.nodes.repository_contributor import RepositoryContributorNode from tests.apps.common.graphql_node_base_test import GraphQLNodeBaseTest @@ -91,16 +91,35 @@ def test_resolve_url(self): assert field is not None assert field.type is str - def test_issues_method(self): - """Test issues method resolution.""" + def test_issues_method_with_recent_issues_attribute(self): + """Test issues method when recent_issues attribute is available.""" mock_repository = Mock() + mock_issues = [] + mock_repository.recent_issues = mock_issues + + field = self._get_field_by_name("issues", RepositoryNode) + result = field.base_resolver.wrapped_func(None, mock_repository) + assert result == mock_issues + + def test_issues_method_without_recent_issues_attribute(self): + """Test issues method when recent_issues doesn't exist.""" + mock_repository = Mock(spec=["issues"]) mock_issues = Mock() - mock_issues.order_by.return_value.__getitem__ = Mock(return_value=[]) + mock_ordered_queryset = Mock() + mock_ordered_queryset.__getitem__ = Mock(return_value=[]) + + mock_issues.order_by.return_value = mock_ordered_queryset mock_repository.issues = mock_issues field = self._get_field_by_name("issues", RepositoryNode) - field.base_resolver.wrapped_func(None, mock_repository) + resolver = field.base_resolver.wrapped_func + result = resolver(None, mock_repository) + mock_issues.order_by.assert_called_with("-created_at") + mock_ordered_queryset.__getitem__.assert_called_with( + slice(None, RECENT_ISSUES_LIMIT, None) + ) + assert result == [] def test_recent_milestones_with_invalid_limit(self): """Test recent_milestones returns empty list for invalid limit.""" diff --git a/backend/tests/apps/github/api/internal/queries/repository_test.py b/backend/tests/apps/github/api/internal/queries/repository_test.py index 6bdb2aca1d..b8f134cb9d 100644 --- a/backend/tests/apps/github/api/internal/queries/repository_test.py +++ b/backend/tests/apps/github/api/internal/queries/repository_test.py @@ -45,7 +45,6 @@ def test_resolve_repository_not_found(self): mock_queryset = MagicMock() mock_queryset.select_related.return_value = mock_queryset mock_queryset.get.side_effect = Repository.DoesNotExist - with patch( "apps.github.models.repository.Repository.objects", mock_queryset,