Skip to content

chore: adding milestone models and resources#29

Merged
Prashant-Surya merged 2 commits intomainfrom
feat-milestones-resources
Feb 11, 2026
Merged

chore: adding milestone models and resources#29
Prashant-Surya merged 2 commits intomainfrom
feat-milestones-resources

Conversation

@sriramveeraghanta
Copy link
Member

@sriramveeraghanta sriramveeraghanta commented Feb 11, 2026

Description

  • Add Milestones CRUD operations (create, retrieve, update, delete, list) scoped to workspace + project
  • Add milestone work item management (add, remove, list work items)
  • Add corresponding TypeScript model interfaces and unit tests

New files

  • src/models/Milestone.tsMilestone, MilestoneLite, CreateMilestoneRequest, UpdateMilestoneRequest, MilestoneWorkItem interfaces
  • src/api/Milestones.tsMilestones API resource with 8 endpoints
  • tests/unit/milestone.test.ts — Unit tests covering full CRUD and work item operations

Modified files

  • src/client/plane-client.ts — Register milestones resource on PlaneClient
  • src/index.ts — Export Milestones API class
  • src/models/index.ts — Export milestone model interfaces

API Endpoints

Method Endpoint Description
POST /workspaces/{slug}/projects/{id}/milestones/ Create milestone
GET /workspaces/{slug}/projects/{id}/milestones/ List milestones
GET /workspaces/{slug}/projects/{id}/milestones/{mid}/ Retrieve milestone
PATCH /workspaces/{slug}/projects/{id}/milestones/{mid}/ Update milestone
DELETE /workspaces/{slug}/projects/{id}/milestones/{mid}/ Delete milestone
POST /workspaces/{slug}/projects/{id}/milestones/{mid}/work-items/ Add work items
DELETE /workspaces/{slug}/projects/{id}/milestones/{mid}/work-items/ Remove work items
GET /workspaces/{slug}/projects/{id}/milestones/{mid}/work-items/ List work items

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Test Scenarios

  • pnpm test:unit passes with new milestone tests
  • TypeScript compilation succeeds (npx tsc --noEmit)
  • E2E validation against a live Plane instance with milestone-enabled project

Summary by CodeRabbit

  • New Features

    • Milestones: create, retrieve, update, delete, and list milestones for projects
    • Work-item management within milestones: add, remove, and list associated work items
    • Milestones exposed on the public client surface for easy access
  • Tests

    • Added end-to-end unit tests covering milestone lifecycle and work-item associations

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Adds a Milestones API resource with CRUD, listing, and milestone–work-item management; integrates it into PlaneClient and public exports; adds TypeScript models and a unit test suite covering lifecycle and work-item association.

Changes

Cohort / File(s) Summary
API Implementation
src/api/Milestones.ts
New Milestones class (extends BaseResource) with methods: create, retrieve, update, delete, list, addWorkItems, removeWorkItems, listWorkItems. Endpoints under /workspaces/{workspaceSlug}/projects/{projectId}/milestones/....
Client & Public Export
src/client/plane-client.ts, src/index.ts
PlaneClient gains a public milestones: Milestones member and initializes it in the constructor; Milestones is exported from package index.
Models & Re-exports
src/models/Milestone.ts, src/models/index.ts
Adds interfaces: Milestone, MilestoneLite, CreateMilestoneRequest, UpdateMilestoneRequest, MilestoneWorkItem; re-exported from models index.
Tests
tests/unit/milestone.test.ts
New unit tests covering milestone create/retrieve/update/list/delete and work-item add/list/remove, including cleanup and gated execution based on test config.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A tiny hop, a milestone made,

Issues gather, plans are laid,
Eight new paths to track the trail,
Tests attest we’ll never fail,
Hooray — new features in the glade!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: adding milestone models and resources' accurately summarizes the main change: introduction of milestone models and API resources to the SDK.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-milestones-resources

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

@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: 2

🤖 Fix all issues with AI agents
In `@src/api/Milestones.ts`:
- Around line 1-126: Format src/api/Milestones.ts with Prettier to satisfy CI:
run prettier --write on the file (or apply your project's Prettier config) and
ensure the file's formatting for the Milestones class and its methods
(constructor, create, retrieve, update, delete, list, addWorkItems,
removeWorkItems, listWorkItems) matches the repo style; stage and commit the
updated file so the Prettier check passes.

In `@tests/unit/milestone.test.ts`:
- Around line 111-124: The test uses the unreliable global fail() which can be
undefined in recent Jest versions; update the "should delete a milestone" test
to throw an explicit error when retrieval unexpectedly succeeds (e.g., replace
fail("Expected an error when retrieving a deleted milestone") with throw new
Error("Expected an error when retrieving a deleted milestone")) or alternatively
assert via expect(client.milestones.retrieve(...)).rejects, ensuring the error
path for client.milestones.retrieve(workspaceSlug, projectId, milestone.id) is
correctly detected and keep the cleanup line milestone = undefined as is.
🧹 Nitpick comments (3)
src/models/Milestone.ts (2)

38-42: Consider making response fields non-optional on MilestoneWorkItem.

When returned from listWorkItems, id, issue, and milestone should always be present. Having all fields optional weakens type safety for consumers. Consider splitting into a request type (optional fields) and a response type (required fields), or making the response fields required.


11-20: Remove unused MilestoneLite interface or defer its addition until it's needed.

MilestoneLite is exported but never imported or used anywhere in the codebase—not in the API resource, request/response types, or tests. Consider removing it if it's not part of the immediate scope.

src/api/Milestones.ts (1)

71-80: Replace params?: any with a typed parameter.

Using any bypasses TypeScript's type checking. Consider Record<string, unknown> or a dedicated query-params interface for listing endpoints.

♻️ Proposed fix
   async list(
     workspaceSlug: string,
     projectId: string,
-    params?: any
+    params?: Record<string, unknown>
   ): Promise<PaginatedResponse<Milestone>> {
   async listWorkItems(
     workspaceSlug: string,
     projectId: string,
     milestoneId: string,
-    params?: any
+    params?: Record<string, unknown>
   ): Promise<PaginatedResponse<MilestoneWorkItem>> {

Also applies to: 115-125

Comment on lines +111 to +124
it("should delete a milestone", async () => {
await client.milestones.delete(workspaceSlug, projectId, milestone.id);

// Verify it's deleted by trying to retrieve it
try {
await client.milestones.retrieve(workspaceSlug, projectId, milestone.id);
fail("Expected an error when retrieving a deleted milestone");
} catch (error) {
expect(error).toBeDefined();
}

// Prevent afterAll from trying to delete again
milestone = undefined as any;
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

fail() is not a reliable Jest global — use throw new Error() instead.

fail() is not defined in newer Jest versions (27+) and can cause ReferenceError at runtime, which would be caught by the surrounding catch block and make the test pass incorrectly even when the retrieve succeeds.

🐛 Proposed fix
     try {
       await client.milestones.retrieve(workspaceSlug, projectId, milestone.id);
-      fail("Expected an error when retrieving a deleted milestone");
+      throw new Error("Expected an error when retrieving a deleted milestone");
     } catch (error) {
-      expect(error).toBeDefined();
+      expect(error).toBeDefined();
+      // Ensure we didn't catch our own thrown error
+      expect((error as Error).message).not.toBe("Expected an error when retrieving a deleted milestone");
     }
🧰 Tools
🪛 ESLint

[error] 111-111: 'it' is not defined.

(no-undef)


[error] 117-117: 'fail' is not defined.

(no-undef)


[error] 119-119: 'expect' is not defined.

(no-undef)

🤖 Prompt for AI Agents
In `@tests/unit/milestone.test.ts` around lines 111 - 124, The test uses the
unreliable global fail() which can be undefined in recent Jest versions; update
the "should delete a milestone" test to throw an explicit error when retrieval
unexpectedly succeeds (e.g., replace fail("Expected an error when retrieving a
deleted milestone") with throw new Error("Expected an error when retrieving a
deleted milestone")) or alternatively assert via
expect(client.milestones.retrieve(...)).rejects, ensuring the error path for
client.milestones.retrieve(workspaceSlug, projectId, milestone.id) is correctly
detected and keep the cleanup line milestone = undefined as is.

@Prashant-Surya Prashant-Surya merged commit c8fe12f into main Feb 11, 2026
1 of 2 checks passed
@Prashant-Surya Prashant-Surya deleted the feat-milestones-resources branch February 11, 2026 16:46
Copy link

@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

🤖 Fix all issues with AI agents
In `@src/api/Milestones.ts`:
- Line 55: Replace the use of the `any` type for query params in the Milestones
API methods by defining and using a proper type (e.g., a dedicated interface
like MilestoneQueryParams or at minimum Record<string, unknown>) and update the
method signatures where `params?: any` appears (notably in the `list` method and
the other occurrence around line 92); ensure any downstream usage of `params` is
typed or guarded (use typed properties or type-narrowing) so callers and
callers' code compile without `any`.
- Around line 48-50: The method currently named delete in the Milestones class
should be renamed to del to match the standard resource method pattern; locate
the async method delete(workspaceSlug, projectId, milestoneId) (and any places
referencing it) and rename the function to async del(workspaceSlug: string,
projectId: string, milestoneId: string): Promise<void>, keeping the same
implementation (return this.httpDelete(...)); also update any internal or
external callers/tests/imports that reference Milestones.delete to use
Milestones.del to avoid breakage.
- Around line 65-68: Rename the parameter issueIds to workItemIds in the
addWorkItems method signature (async addWorkItems(workspaceSlug, projectId,
milestoneId, workItemIds): Promise<void>) and pass workItemIds as the value for
the existing payload key "issues" when calling this.post so the backend contract
stays unchanged; also apply the same
rename-to-workItemIds-but-keep-payload-key="issues" pattern to the other
method(s) in this file around the 74-82 region to match Modules.ts/Cycles.ts
conventions.
🧹 Nitpick comments (1)
src/api/Milestones.ts (1)

1-1: File name should use kebab-case.

Milestones.ts uses PascalCase but the coding guidelines specify **/*.ts files should use kebab-case. Rename to milestones.ts and update all import paths that reference this file.

Comment on lines +48 to +50
async delete(workspaceSlug: string, projectId: string, milestoneId: string): Promise<void> {
return this.httpDelete(`/workspaces/${workspaceSlug}/projects/${projectId}/milestones/${milestoneId}/`);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Rename delete to del to match the standard resource method pattern.

Per coding guidelines, standard resource methods should follow the pattern: list, create, retrieve, update, del. As per coding guidelines, "Standard resource methods should follow the pattern: list, create, retrieve, update, del".

♻️ Proposed fix
-  async delete(workspaceSlug: string, projectId: string, milestoneId: string): Promise<void> {
+  async del(workspaceSlug: string, projectId: string, milestoneId: string): Promise<void> {
     return this.httpDelete(`/workspaces/${workspaceSlug}/projects/${projectId}/milestones/${milestoneId}/`);
   }
📝 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
async delete(workspaceSlug: string, projectId: string, milestoneId: string): Promise<void> {
return this.httpDelete(`/workspaces/${workspaceSlug}/projects/${projectId}/milestones/${milestoneId}/`);
}
async del(workspaceSlug: string, projectId: string, milestoneId: string): Promise<void> {
return this.httpDelete(`/workspaces/${workspaceSlug}/projects/${projectId}/milestones/${milestoneId}/`);
}
🤖 Prompt for AI Agents
In `@src/api/Milestones.ts` around lines 48 - 50, The method currently named
delete in the Milestones class should be renamed to del to match the standard
resource method pattern; locate the async method delete(workspaceSlug,
projectId, milestoneId) (and any places referencing it) and rename the function
to async del(workspaceSlug: string, projectId: string, milestoneId: string):
Promise<void>, keeping the same implementation (return this.httpDelete(...));
also update any internal or external callers/tests/imports that reference
Milestones.delete to use Milestones.del to avoid breakage.

/**
* List milestones with optional filtering
*/
async list(workspaceSlug: string, projectId: string, params?: any): Promise<PaginatedResponse<Milestone>> {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace any with proper typing for query params.

params?: any on Lines 55 and 92 violates the guideline to avoid any types. Use Record<string, unknown> or a dedicated query-params interface.

♻️ Proposed fix
-  async list(workspaceSlug: string, projectId: string, params?: any): Promise<PaginatedResponse<Milestone>> {
+  async list(workspaceSlug: string, projectId: string, params?: Record<string, unknown>): Promise<PaginatedResponse<Milestone>> {
-    params?: any
+    params?: Record<string, unknown>

As per coding guidelines, "Avoid any types; use proper typing or unknown with type guards".

Also applies to: 92-92

🤖 Prompt for AI Agents
In `@src/api/Milestones.ts` at line 55, Replace the use of the `any` type for
query params in the Milestones API methods by defining and using a proper type
(e.g., a dedicated interface like MilestoneQueryParams or at minimum
Record<string, unknown>) and update the method signatures where `params?: any`
appears (notably in the `list` method and the other occurrence around line 92);
ensure any downstream usage of `params` is typed or guarded (use typed
properties or type-narrowing) so callers and callers' code compile without
`any`.

Comment on lines +65 to +68
async addWorkItems(workspaceSlug: string, projectId: string, milestoneId: string, issueIds: string[]): Promise<void> {
return this.post<void>(`/workspaces/${workspaceSlug}/projects/${projectId}/milestones/${milestoneId}/work-items/`, {
issues: issueIds,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/api/Milestones.ts | head -90

Repository: makeplane/plane-node-sdk

Length of output: 3413


🏁 Script executed:

# Check if "issues" is a backend API contract by looking at other API usages and responses
rg -n "issues" --type=ts src/api/ -B 2 -A 2 | head -100

Repository: makeplane/plane-node-sdk

Length of output: 5317


🏁 Script executed:

# Check if there are any type definitions or models that define the payload structure
fd -e ts -e json . src/models src/types 2>/dev/null | head -20

Repository: makeplane/plane-node-sdk

Length of output: 515


🏁 Script executed:

# Check Modules.ts to see the full pattern of how payload is structured
sed -n '75,90p' src/api/Modules.ts

Repository: makeplane/plane-node-sdk

Length of output: 456


🏁 Script executed:

# Check Cycles.ts to see the same pattern
sed -n '100,110p' src/api/Cycles.ts

Repository: makeplane/plane-node-sdk

Length of output: 346


🏁 Script executed:

# Search for any backend response or test fixtures that might show the expected payload structure
rg -n "work_items|work-items" --type=ts src/ | head -20

Repository: makeplane/plane-node-sdk

Length of output: 2863


Rename "issue" parameter to "work item" terminology; keep backend API payload contract.

The parameter issueIds violates the coding guideline (never use "Issue" in names). Rename it to workItemIds to align with the rest of the codebase.

However, the payload key issues must remain unchanged—it is the backend API contract. Other similar methods in the codebase (Modules.ts, Cycles.ts) already follow this pattern: parameter uses workItemIds, but the payload key stays as issues.

♻️ Proposed fix
-  async addWorkItems(workspaceSlug: string, projectId: string, milestoneId: string, issueIds: string[]): Promise<void> {
+  async addWorkItems(workspaceSlug: string, projectId: string, milestoneId: string, workItemIds: string[]): Promise<void> {
     return this.post<void>(`/workspaces/${workspaceSlug}/projects/${projectId}/milestones/${milestoneId}/work-items/`, {
-      issues: issueIds,
+      issues: workItemIds,
     });
   }
   async removeWorkItems(
     workspaceSlug: string,
     projectId: string,
     milestoneId: string,
-    issueIds: string[]
+    workItemIds: string[]
   ): Promise<void> {
     return this.httpDelete(`/workspaces/${workspaceSlug}/projects/${projectId}/milestones/${milestoneId}/work-items/`, {
-      issues: issueIds,
+      issues: workItemIds,
     });
   }

Also applies to: 74-82

🤖 Prompt for AI Agents
In `@src/api/Milestones.ts` around lines 65 - 68, Rename the parameter issueIds to
workItemIds in the addWorkItems method signature (async
addWorkItems(workspaceSlug, projectId, milestoneId, workItemIds): Promise<void>)
and pass workItemIds as the value for the existing payload key "issues" when
calling this.post so the backend contract stays unchanged; also apply the same
rename-to-workItemIds-but-keep-payload-key="issues" pattern to the other
method(s) in this file around the 74-82 region to match Modules.ts/Cycles.ts
conventions.

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.

2 participants