Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@mrkeshav-05
Copy link
Contributor

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 endpoints
  • backend/tests/apps/api/rest/v0/milestone_test.py - Comprehensive test suite for milestone schemas

Files Modified:

  • backend/apps/api/rest/v0/__init__.py - Added milestone router registration

Endpoints Implemented:

1. List Milestones - GET /api/v0/milestones/

  • Operation ID: list_milestones
  • Returns paginated list of GitHub milestones

2. Get Milestone - GET /api/v0/milestones/{organization_id}/{repository_id}/{milestone_id}

  • Operation ID: get_milestone
  • Returns detailed information for a specific milestone

Checklist

  • I've run make check-test locally
    • ✅ Backend tests pass (milestone endpoint tests added and passing)
    • ✅ Pre-commit checks pass (linting, formatting, type checking)
    • ⚠️ Frontend test failures are pre-existing and unrelated to this PR (only backend code modified)
  • Code follows the project's coding standards
  • Tests added for new functionality

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Summary by CodeRabbit

  • New Features

    • Added API endpoints for managing GitHub milestones with filtering capabilities (by organization, repository, and state)
    • Implemented pagination support for milestone listings with configurable ordering
    • Added endpoint to retrieve detailed information for individual milestones
    • Enabled response caching for optimized performance
  • Tests

    • Added comprehensive test coverage for milestone API functionality

Walkthrough

Adds 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

Cohort / File(s) Summary
API router registration
backend/apps/api/rest/v0/__init__.py
Imports and registers the milestone subrouter at "/milestones" in the ROUTERS mapping.
Milestone API implementation
backend/apps/api/rest/v0/milestone.py
New Django Ninja router exposing two cached endpoints: paginated list_milestones (filters: organization, repository, state; ordering) and get_milestone (by org/repo/milestone id). Adds schemas: MilestoneBase, Milestone, MilestoneDetail, MilestoneError, MilestoneFilter, and exports router.
Tests: model & routing
backend/tests/apps/api/rest/v0/milestone_test.py, backend/tests/apps/api/rest/v0/urls_test.py
Adds tests for MilestoneDetail construction (field parsing, ISO8601 dates, optional due_on) and updates router-registration tests to expect the /milestones subrouter.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • kasya

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "feat: Add milestone endpoints to v0 API" is directly related to the main change in the changeset. The title accurately reflects the primary objective of the PR, which is to add two new milestone endpoints (list and get) to the v0 API. The title is concise, avoids noise, and is specific enough for a teammate to quickly understand the primary change.
Linked Issues Check ✅ Passed The PR successfully addresses all core coding requirements from issue #2387. It implements both required endpoints: a list_milestones endpoint returning a paginated list of milestones with filtering and ordering support, and a get_milestone endpoint returning detailed milestone information or an error response. The implementation follows existing v0 API conventions using Django Ninja's RouterPaginated, includes proper schema definitions (MilestoneBase, Milestone, MilestoneDetail, MilestoneError, MilestoneFilter), adds comprehensive tests for milestone functionality, and registers the router appropriately. The endpoints are automatically documented via OpenAPI schema generation through Django Ninja, satisfying the documentation requirement.
Out of Scope Changes Check ✅ Passed All changes in the PR are directly scoped to the objective of adding milestone endpoints to the v0 API. The modifications include: registering the milestone router in init.py to expose the endpoints, creating the new milestone.py module with full endpoint and schema implementations, adding tests for the milestone schemas, and updating the URL test file to reflect the new router registration. There are no extraneous changes such as refactoring unrelated code, modifying other API features, or updating dependencies outside the scope of this feature.
Description Check ✅ Passed The pull request description is clearly related to the changeset and provides appropriate detail about the changes. It explicitly references issue #2387, outlines the new files created (milestone.py and milestone_test.py), specifies the files modified (init.py), describes the two endpoints implemented with their operation IDs and purposes, and confirms that tests were added and local checks passed. The description is not vague or generic; it meaningfully connects to the actual code changes present in the PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 a Response object rather than a MilestoneError instance directly. While Django Ninja handles this correctly via the response decorator 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

📥 Commits

Reviewing files that changed from the base of the PR and between 860bc5f and 60479c8.

📒 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.State enum
  • due_on properly 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 _id suffix convention:

  • All single-resource endpoints: sponsor_id, project_id, organization_id, member_id, etc.
  • All hierarchical endpoints: organization_id/repository_id appears in repository.py, release.py, milestone.py, and issue.py
  • It's an API-wide design choice, not a file-specific inconsistency

Moreover, using _id suffix for path parameters to identify resources is a standard REST API naming convention.

If this is a concern, it requires:

  1. Systemic refactoring across all v0 endpoints if the _id suffix truly misleads clients, OR
  2. 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.test and backend/docker/Dockerfile), which is well above Python 3.11. Python 3.11+ natively supports the 'Z' suffix in datetime.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.

@arkid15r arkid15r enabled auto-merge October 23, 2025 19:26
Copy link
Collaborator

@arkid15r arkid15r left a 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

@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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_related for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 60479c8 and 443e4f3.

📒 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

Comment on lines +31 to +33
class Milestone(MilestoneBase):
"""Schema for Milestone (minimal fields for list display)."""

Copy link
Contributor

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 Milestone and use MilestoneBase directly in the list endpoint, or
  • Add specific fields or behavior that distinguishes Milestone from MilestoneBase.

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")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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).

Comment on lines +123 to +124
except MilestoneModel.DoesNotExist:
return Response({"message": "Milestone not found"}, status=HTTPStatus.NOT_FOUND)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

@arkid15r arkid15r added this pull request to the merge queue Oct 23, 2025
Merged via the queue into OWASP:main with commit 4b067ad Oct 23, 2025
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add milestone endpoints to v0 API

2 participants