-
-
Notifications
You must be signed in to change notification settings - Fork 238
feat: Add milestone endpoints to v0 API #2472
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
Conversation
Summary by CodeRabbit
WalkthroughAdds a new milestone API module and router, registers it under "/milestones", implements paginated list and detail endpoints with schemas and caching, and adds unit tests for the milestone model and router registration. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
backend/apps/api/rest/v0/milestone.py (1)
115-124: Clarify return type annotation.The return type annotation specifies
MilestoneDetail | MilestoneError, but line 124 returns aResponseobject rather than aMilestoneErrorinstance directly. While Django Ninja handles this correctly via theresponsedecorator mapping (lines 103-106), the type annotation is inconsistent with the actual return types.For better type safety, consider either:
Option 1: Update the type annotation to reflect actual returns:
-) -> MilestoneDetail | MilestoneError: +) -> MilestoneDetail | Response:Option 2: Return the error schema directly and let Ninja wrap it:
except MilestoneModel.DoesNotExist: - return Response({"message": "Milestone not found"}, status=HTTPStatus.NOT_FOUND) + return (HTTPStatus.NOT_FOUND, MilestoneError(message="Milestone not found"))
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
backend/apps/api/rest/v0/__init__.py(2 hunks)backend/apps/api/rest/v0/milestone.py(1 hunks)backend/tests/apps/api/rest/v0/milestone_test.py(1 hunks)backend/tests/apps/api/rest/v0/urls_test.py(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
backend/tests/apps/api/rest/v0/milestone_test.py (1)
backend/apps/api/rest/v0/milestone.py (1)
MilestoneDetail(34-40)
backend/apps/api/rest/v0/milestone.py (3)
backend/apps/api/decorators/cache.py (1)
cache_response(19-56)backend/apps/github/models/generic_issue_model.py (3)
GenericIssueModel(10-77)State(18-20)repository_id(70-77)backend/apps/github/api/internal/queries/repository.py (1)
repository(14-35)
🔇 Additional comments (7)
backend/tests/apps/api/rest/v0/urls_test.py (1)
10-10: LGTM!The milestone router import and registration follow the established pattern used by other routers in the test file.
Also applies to: 28-28
backend/apps/api/rest/v0/__init__.py (1)
14-14: LGTM!The milestone router import and registration follow the established pattern. The router will be properly registered via the loop at lines 89-90.
Also applies to: 27-27
backend/apps/api/rest/v0/milestone.py (4)
20-46: LGTM!The schema definitions are well-structured:
- Appropriate separation between list (
Milestone) and detail (MilestoneDetail) schemas- Correct use of
GenericIssueModel.Stateenumdue_onproperly typed as optional
49-65: LGTM!The filter schema provides appropriate filtering options with clear descriptions.
68-94: LGTM!The list endpoint implementation is well-designed:
- Proper use of
select_related()to avoid N+1 queries- Case-insensitive filtering with
__iexact- Appropriate default ordering with secondary sort for consistency
- Caching enabled for performance
98-98: I need to verify the actual parameter matching logic to confirm whether the original concern about misleading naming is valid or if this is consistent API design.Based on my verification, the naming concern has a critical scope issue that needs clarification.
The parameter naming pattern is consistent across all v0 endpoints, not unique to milestone.py.
The script output shows every v0 endpoint uses the
_idsuffix convention:
- All single-resource endpoints:
sponsor_id,project_id,organization_id,member_id, etc.- All hierarchical endpoints:
organization_id/repository_idappears in repository.py, release.py, milestone.py, and issue.py- It's an API-wide design choice, not a file-specific inconsistency
Moreover, using
_idsuffix for path parameters to identify resources is a standard REST API naming convention.If this is a concern, it requires:
- Systemic refactoring across all v0 endpoints if the
_idsuffix truly misleads clients, OR- Acknowledgment that this is the established naming convention for the v0 API and should not be changed in isolation
Changing only milestone.py would create inconsistency with the rest of the API.
backend/tests/apps/api/rest/v0/milestone_test.py (1)
52-52: No action required. The review comment is incorrect.The project uses Python 3.13.7 (as defined in both
backend/docker/Dockerfile.testandbackend/docker/Dockerfile), which is well above Python 3.11. Python 3.11+ natively supports the 'Z' suffix indatetime.fromisoformat(), so the test assertions using this method with ISO8601 strings ending in 'Z' will work correctly without any modifications or alternatives.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
Nicely done @mrkeshav-05
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (2)
backend/apps/api/rest/v0/milestone.py (2)
20-42: Consider adding field descriptions for API documentation.The filter fields (lines 53-66) include descriptions and examples, but the schema fields lack them. Adding descriptions and examples to schema fields improves API documentation consistency.
Example:
class MilestoneBase(Schema): """Base schema for Milestone (used in list endpoints).""" - created_at: datetime - number: int - state: GenericIssueModel.State - title: str - updated_at: datetime - url: str + created_at: datetime = Field(description="Timestamp when the milestone was created") + number: int = Field(description="Milestone number", example=1) + state: GenericIssueModel.State = Field(description="Current state of the milestone") + title: str = Field(description="Milestone title", example="Version 1.0") + updated_at: datetime = Field(description="Timestamp when the milestone was last updated") + url: str = Field(description="GitHub URL of the milestone")
110-124: Add query optimization with select_related.The list endpoint uses
select_relatedfor query optimization (line 83), but the detail endpoint doesn't. Adding it here improves performance by reducing database queries.Apply this diff:
def get_milestone( request: HttpRequest, organization_id: str = Path(example="OWASP"), repository_id: str = Path(example="Nest"), milestone_id: int = Path(example=1), ) -> MilestoneDetail | MilestoneError: """Get milestone.""" try: - return MilestoneModel.objects.get( + return MilestoneModel.objects.select_related( + "repository", "repository__organization" + ).get( repository__organization__login__iexact=organization_id, repository__name__iexact=repository_id, number=milestone_id, )
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
backend/apps/api/rest/v0/milestone.py(1 hunks)backend/tests/apps/api/rest/v0/milestone_test.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- backend/tests/apps/api/rest/v0/milestone_test.py
🧰 Additional context used
🧬 Code graph analysis (1)
backend/apps/api/rest/v0/milestone.py (2)
backend/apps/api/decorators/cache.py (1)
cache_response(19-56)backend/apps/github/models/generic_issue_model.py (3)
GenericIssueModel(10-77)State(18-20)repository_id(70-77)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Run frontend e2e tests
- GitHub Check: Run backend tests
- GitHub Check: Run frontend unit tests
| class Milestone(MilestoneBase): | ||
| """Schema for Milestone (minimal fields for list display).""" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Remove redundant schema or add distinguishing fields.
The Milestone class is currently empty and identical to MilestoneBase. This is code duplication. Either:
- Remove
Milestoneand useMilestoneBasedirectly in the list endpoint, or - Add specific fields or behavior that distinguishes
MilestonefromMilestoneBase.
Apply this diff if no distinction is needed:
-class Milestone(MilestoneBase):
- """Schema for Milestone (minimal fields for list display)."""
-
-
class MilestoneDetail(MilestoneBase):
"""Detail schema for Milestone (used in single item endpoints)."""
@@ -73,7 +70,7 @@
"/",
description="Retrieve a paginated list of GitHub milestones.",
operation_id="list_milestones",
- response=list[Milestone],
+ response=list[MilestoneBase],
summary="List milestones",
)
@decorate_view(cache_response())
def list_milestones(
request: HttpRequest,
filters: MilestoneFilter = Query(...),
ordering: Literal["created_at", "-created_at", "updated_at", "-updated_at"] | None = None,
-) -> list[Milestone]:
+) -> list[MilestoneBase]:Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In backend/apps/api/rest/v0/milestone.py around lines 31 to 33, the Milestone
class is a direct duplicate of MilestoneBase and should be removed or
differentiated; either delete the Milestone class and update call sites to use
MilestoneBase for list endpoints, or add the extra fields/validation/metadata
that make Milestone distinct (e.g., trimmed fields for list display or
additional read-only properties) and update tests/docs accordingly.
| if filters.state: | ||
| milestones = milestones.filter(state=filters.state) | ||
|
|
||
| return milestones.order_by(ordering or "-created_at", "-updated_at") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix contradictory ordering logic.
When ordering is provided (e.g., "updated_at"), the code produces order_by("updated_at", "-updated_at"), which contradicts itself. The secondary sort on "-updated_at" should only be added when it's not already the primary sort field.
Apply this diff to fix the ordering:
- return milestones.order_by(ordering or "-created_at", "-updated_at")
+ order_fields = [ordering] if ordering else ["-created_at"]
+ # Add secondary sort on updated_at if not already sorting by it
+ if ordering not in ("updated_at", "-updated_at"):
+ order_fields.append("-updated_at")
+ return milestones.order_by(*order_fields)🤖 Prompt for AI Agents
In backend/apps/api/rest/v0/milestone.py around line 94, the current return
builds order_by(ordering or "-created_at", "-updated_at") which can produce
contradictory pairs like ("updated_at", "-updated_at"); change it so the
secondary "-updated_at" is only appended when the primary ordering (stripped of
any leading "-") is not "updated_at". Implement by determining primary =
ordering or "-created_at", check if (ordering or "").lstrip("-") !=
"updated_at", and call milestones.order_by(primary, "-updated_at") only in that
case, otherwise call milestones.order_by(primary).
| except MilestoneModel.DoesNotExist: | ||
| return Response({"message": "Milestone not found"}, status=HTTPStatus.NOT_FOUND) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return schema instance instead of Response object.
The function signature declares it returns MilestoneDetail | MilestoneError, but line 124 returns a Response object. Django Ninja automatically handles status code mapping based on return type—you should return an instance of MilestoneError schema instead.
Apply this diff:
except MilestoneModel.DoesNotExist:
- return Response({"message": "Milestone not found"}, status=HTTPStatus.NOT_FOUND)
+ return HTTPStatus.NOT_FOUND, MilestoneError(message="Milestone not found")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| except MilestoneModel.DoesNotExist: | |
| return Response({"message": "Milestone not found"}, status=HTTPStatus.NOT_FOUND) | |
| except MilestoneModel.DoesNotExist: | |
| return HTTPStatus.NOT_FOUND, MilestoneError(message="Milestone not found") |
🤖 Prompt for AI Agents
In backend/apps/api/rest/v0/milestone.py around lines 123-124, the except block
currently returns a Django Response object which violates the declared return
types; replace the Response return with an instance of the MilestoneError schema
(e.g., MilestoneError(message="Milestone not found")) so Django Ninja can map
the status correctly, and ensure MilestoneError is imported at the top of the
file.
Description
This PR adds milestone endpoints to the v0 API as requested in issue #2387. The implementation includes two endpoints for listing and retrieving GitHub milestone information, following the same structure and conventions as other existing v0 API endpoints.
Related Issue
Fixed #2387
New Files Created:
backend/apps/api/rest/v0/milestone.py- Complete milestone API implementation with schemas and endpointsbackend/tests/apps/api/rest/v0/milestone_test.py- Comprehensive test suite for milestone schemasFiles Modified:
backend/apps/api/rest/v0/__init__.py- Added milestone router registrationEndpoints Implemented:
1. List Milestones -
GET /api/v0/milestones/list_milestones2. Get Milestone -
GET /api/v0/milestones/{organization_id}/{repository_id}/{milestone_id}get_milestoneChecklist
make check-testlocally