feat: add Releases API resources#51
Conversation
Add workspace-scoped Releases support to the SDK, matching the plane-ee
external /api/v1 contract:
- releases: CRUD
- releases.labels: workspace CRUD + add/remove/list labels on a release
- releases.tags: workspace CRUD (version identifiers)
- releases.workItems: add/remove/list work items on a release
- releases.comments: CRUD (nested under a release)
- releases.links: CRUD (nested under a release)
Models derived from the plane-ee serializers/DB models (Release,
ReleaseTag, ReleaseLabel, ReleaseComment, ReleaseLink) with exact
request/response shapes and faithful return types (addLabels -> array,
workItems.add -> {message}, paginated lists).
Tests:
- Mocked unit tests (tests/unit/releases/, no creds) asserting exact
URL/verb/body/params + response parsing + error handling.
- Live integration tests (tests/unit/release.test.ts, describeIf-gated).
📝 WalkthroughWalkthroughAdds a typed Releases API with CRUD operations and sub-resources for comments, labels, links, tags, and work items. The resource is wired into ChangesRelease API
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
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.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/api/Releases/index.ts`:
- Around line 52-57: Rename the standard resource method from delete to del in
src/api/Releases/index.ts:52-57, src/api/Releases/Comments.ts:56-61,
src/api/Releases/Labels.ts:49-54, src/api/Releases/Links.ts:44-49, and
src/api/Releases/Tags.ts:36-41, preserving each method’s existing parameters,
return type, and HTTP deletion behavior.
In `@src/models/index.ts`:
- Around line 19-23: Rename the model files exported in the index from
PascalCase to kebab-case: Release, ReleaseComment, ReleaseLabel, ReleaseLink,
and ReleaseTag should become release.ts, release-comment.ts, release-label.ts,
release-link.ts, and release-tag.ts. Update the corresponding export paths and
all imports or references to use the new filenames.
In `@src/models/Release.ts`:
- Line 1: Rename the PascalCase files to kebab-case: src/models/Release.ts to
release.ts, ReleaseComment.ts to release-comment.ts, ReleaseLabel.ts to
release-label.ts, ReleaseLink.ts to release-link.ts, ReleaseTag.ts to
release-tag.ts, and src/api/Releases/Comments.ts, Labels.ts, Links.ts, Tags.ts,
and WorkItems.ts to comments.ts, labels.ts, links.ts, tags.ts, and work-items.ts
respectively; update all affected imports to use the new paths.
- Around line 49-66: Replace the manually duplicated Create DTO fields with
derived types using Pick, Omit, and Partial as appropriate: update CreateRelease
in src/models/Release.ts (49-66), CreateReleaseComment in
src/models/ReleaseComment.ts (22-26), CreateReleaseLabel in
src/models/ReleaseLabel.ts (16-20), CreateReleaseLink in
src/models/ReleaseLink.ts (15-19), and CreateReleaseTag in
src/models/ReleaseTag.ts (16-21) to derive from their corresponding Release,
ReleaseComment, ReleaseLabel, ReleaseLink, and ReleaseTag interfaces while
preserving each DTO’s current create-field optionality.
- Line 18: Replace every any type with unknown at the identified sites:
description_json and index signatures in src/models/Release.ts (lines 18-18,
52-52, 73-73), the index signatures in src/models/ReleaseComment.ts (33-33),
src/models/ReleaseLabel.ts (27-27), src/models/ReleaseLink.ts (26-26),
src/models/ReleaseTag.ts (28-28), and src/api/Releases/WorkItems.ts (27-27),
plus metadata in src/models/ReleaseLink.ts (11-11, 18-18). Preserve the existing
model shapes while removing all uses of any.
In `@tests/unit/release.test.ts`:
- Around line 218-260: Update the “Release Work Items” suite to use
describeIf(!!config.workItemId, ...) so it is reported as skipped when no work
item ID is configured. Remove the individual config.workItemId early-return
guards from the add, list, and remove tests while preserving their existing
assertions and operations.
In `@tests/unit/releases/releases.test.ts`:
- Around line 102-113: Rename release-resource deletion methods from delete to
del to match the standard resource contract, updating the corresponding
implementations and calls. In tests/unit/releases/releases.test.ts:102-113,
tests/unit/releases/tags.test.ts:64-73, tests/unit/release.test.ts:38-59,
301-306, and 351-356, tests/unit/releases/comments.test.ts:68-77,
tests/unit/releases/labels.test.ts:67-76, and
tests/unit/releases/links.test.ts:67-76, use del for releases, tags, labels,
comments, and links, including release, label, and tag cleanup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c831dba1-94a8-4842-af0d-15ad573c397d
📒 Files selected for processing (22)
src/api/Releases/Comments.tssrc/api/Releases/Labels.tssrc/api/Releases/Links.tssrc/api/Releases/Tags.tssrc/api/Releases/WorkItems.tssrc/api/Releases/index.tssrc/client/plane-client.tssrc/index.tssrc/models/Release.tssrc/models/ReleaseComment.tssrc/models/ReleaseLabel.tssrc/models/ReleaseLink.tssrc/models/ReleaseTag.tssrc/models/index.tstests/helpers/mock-axios.tstests/unit/release.test.tstests/unit/releases/comments.test.tstests/unit/releases/labels.test.tstests/unit/releases/links.test.tstests/unit/releases/releases.test.tstests/unit/releases/tags.test.tstests/unit/releases/work-items.test.ts
| /** | ||
| * Delete a release | ||
| */ | ||
| async delete(workspaceSlug: string, releaseId: string): Promise<void> { | ||
| return this.httpDelete(`/workspaces/${workspaceSlug}/releases/${releaseId}/`); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Rename the delete method to del.
These resource classes define a delete method, which violates the coding guideline requiring standard resource methods to be named: list, create, retrieve, update, del.
src/api/Releases/index.ts#L52-L57: Rename method fromdeletetodel.src/api/Releases/Comments.ts#L56-L61: Rename method fromdeletetodel.src/api/Releases/Labels.ts#L49-L54: Rename method fromdeletetodel.src/api/Releases/Links.ts#L44-L49: Rename method fromdeletetodel.src/api/Releases/Tags.ts#L36-L41: Rename method fromdeletetodel.
📍 Affects 5 files
src/api/Releases/index.ts#L52-L57(this comment)src/api/Releases/Comments.ts#L56-L61src/api/Releases/Labels.ts#L49-L54src/api/Releases/Links.ts#L44-L49src/api/Releases/Tags.ts#L36-L41
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/api/Releases/index.ts` around lines 52 - 57, Rename the standard resource
method from delete to del in src/api/Releases/index.ts:52-57,
src/api/Releases/Comments.ts:56-61, src/api/Releases/Labels.ts:49-54,
src/api/Releases/Links.ts:44-49, and src/api/Releases/Tags.ts:36-41, preserving
each method’s existing parameters, return type, and HTTP deletion behavior.
Source: Coding guidelines
| export * from "./Release"; | ||
| export * from "./ReleaseComment"; | ||
| export * from "./ReleaseLabel"; | ||
| export * from "./ReleaseLink"; | ||
| export * from "./ReleaseTag"; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Rename the new model files to kebab-case.
These exports introduce PascalCase filenames. Rename them to release.ts, release-comment.ts, release-label.ts, release-link.ts, and release-tag.ts, then update their imports.
As per coding guidelines, “Use kebab-case for file names.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/models/index.ts` around lines 19 - 23, Rename the model files exported in
the index from PascalCase to kebab-case: Release, ReleaseComment, ReleaseLabel,
ReleaseLink, and ReleaseTag should become release.ts, release-comment.ts,
release-label.ts, release-link.ts, and release-tag.ts. Update the corresponding
export paths and all imports or references to use the new filenames.
Source: Coding guidelines
| @@ -0,0 +1,106 @@ | |||
| import { BaseModel } from "./common"; | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use kebab-case for file names.
These files use PascalCase for their names, which violates the coding guideline requiring kebab-case for all files in src/**/*.ts.
src/models/Release.ts#L1-L1: Rename file torelease.ts.src/models/ReleaseComment.ts#L1-L1: Rename file torelease-comment.ts.src/models/ReleaseLabel.ts#L1-L1: Rename file torelease-label.ts.src/models/ReleaseLink.ts#L1-L1: Rename file torelease-link.ts.src/models/ReleaseTag.ts#L1-L1: Rename file torelease-tag.ts.src/api/Releases/Comments.ts#L1-L1: Rename file tocomments.ts.src/api/Releases/Labels.ts#L1-L1: Rename file tolabels.ts.src/api/Releases/Links.ts#L1-L1: Rename file tolinks.ts.src/api/Releases/Tags.ts#L1-L1: Rename file totags.ts.src/api/Releases/WorkItems.ts#L1-L1: Rename file towork-items.ts.
📍 Affects 10 files
src/models/Release.ts#L1-L1(this comment)src/models/ReleaseComment.ts#L1-L1src/models/ReleaseLabel.ts#L1-L1src/models/ReleaseLink.ts#L1-L1src/models/ReleaseTag.ts#L1-L1src/api/Releases/Comments.ts#L1-L1src/api/Releases/Labels.ts#L1-L1src/api/Releases/Links.ts#L1-L1src/api/Releases/Tags.ts#L1-L1src/api/Releases/WorkItems.ts#L1-L1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/models/Release.ts` at line 1, Rename the PascalCase files to kebab-case:
src/models/Release.ts to release.ts, ReleaseComment.ts to release-comment.ts,
ReleaseLabel.ts to release-label.ts, ReleaseLink.ts to release-link.ts,
ReleaseTag.ts to release-tag.ts, and src/api/Releases/Comments.ts, Labels.ts,
Links.ts, Tags.ts, and WorkItems.ts to comments.ts, labels.ts, links.ts,
tags.ts, and work-items.ts respectively; update all affected imports to use the
new paths.
Source: Coding guidelines
| */ | ||
| export interface ReleaseDescription { | ||
| description_html?: string; | ||
| description_json?: Record<string, any>; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Avoid using any types.
These properties and parameters use the any type, bypassing type safety. As per coding guidelines, avoid any types and use proper typing or unknown instead.
src/models/Release.ts#L18-L18: Replaceanywithunknownindescription_json.src/models/Release.ts#L52-L52: Replaceanywithunknownindescription_json.src/models/Release.ts#L73-L73: Replaceanywithunknownin[key: string]: any.src/models/ReleaseComment.ts#L33-L33: Replaceanywithunknownin[key: string]: any.src/models/ReleaseLabel.ts#L27-L27: Replaceanywithunknownin[key: string]: any.src/models/ReleaseLink.ts#L11-L11: Replaceanywithunknowninmetadata.src/models/ReleaseLink.ts#L18-L18: Replaceanywithunknowninmetadata.src/models/ReleaseLink.ts#L26-L26: Replaceanywithunknownin[key: string]: any.src/models/ReleaseTag.ts#L28-L28: Replaceanywithunknownin[key: string]: any.src/api/Releases/WorkItems.ts#L27-L27: Replaceanywithunknownin[key: string]: any.
📍 Affects 6 files
src/models/Release.ts#L18-L18(this comment)src/models/Release.ts#L52-L52src/models/Release.ts#L73-L73src/models/ReleaseComment.ts#L33-L33src/models/ReleaseLabel.ts#L27-L27src/models/ReleaseLink.ts#L11-L11src/models/ReleaseLink.ts#L18-L18src/models/ReleaseLink.ts#L26-L26src/models/ReleaseTag.ts#L28-L28src/api/Releases/WorkItems.ts#L27-L27
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/models/Release.ts` at line 18, Replace every any type with unknown at the
identified sites: description_json and index signatures in src/models/Release.ts
(lines 18-18, 52-52, 73-73), the index signatures in
src/models/ReleaseComment.ts (33-33), src/models/ReleaseLabel.ts (27-27),
src/models/ReleaseLink.ts (26-26), src/models/ReleaseTag.ts (28-28), and
src/api/Releases/WorkItems.ts (27-27), plus metadata in
src/models/ReleaseLink.ts (11-11, 18-18). Preserve the existing model shapes
while removing all uses of any.
Source: Coding guidelines
| export interface CreateRelease { | ||
| name: string; | ||
| description_html?: string; | ||
| description_json?: Record<string, any>; | ||
| status?: ReleaseStatus; | ||
| // YYYY-MM-DD format | ||
| target_date?: string | null; | ||
| // YYYY-MM-DD format | ||
| release_date?: string | null; | ||
| // User id of the release lead | ||
| lead?: string | null; | ||
| // Release tag id | ||
| tag?: string | null; | ||
| is_latest?: boolean; | ||
| is_prerelease?: boolean; | ||
| external_id?: string; | ||
| external_source?: string; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Derive Create DTOs using Pick or Omit.
These DTO interfaces manually duplicate fields from their corresponding entity models. As per coding guidelines, use Pick, Omit, and Partial to define separate Create/Update DTOs derived from the main entity interfaces.
src/models/Release.ts#L49-L66: RefactorCreateReleaseto derive fromReleaseusingOmitorPick.src/models/ReleaseComment.ts#L22-L26: RefactorCreateReleaseCommentto derive fromReleaseCommentusingOmitorPick.src/models/ReleaseLabel.ts#L16-L20: RefactorCreateReleaseLabelto derive fromReleaseLabelusingOmitorPick.src/models/ReleaseLink.ts#L15-L19: RefactorCreateReleaseLinkto derive fromReleaseLinkusingOmitorPick.src/models/ReleaseTag.ts#L16-L21: RefactorCreateReleaseTagto derive fromReleaseTagusingOmitorPick.
📍 Affects 5 files
src/models/Release.ts#L49-L66(this comment)src/models/ReleaseComment.ts#L22-L26src/models/ReleaseLabel.ts#L16-L20src/models/ReleaseLink.ts#L15-L19src/models/ReleaseTag.ts#L16-L21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/models/Release.ts` around lines 49 - 66, Replace the manually duplicated
Create DTO fields with derived types using Pick, Omit, and Partial as
appropriate: update CreateRelease in src/models/Release.ts (49-66),
CreateReleaseComment in src/models/ReleaseComment.ts (22-26), CreateReleaseLabel
in src/models/ReleaseLabel.ts (16-20), CreateReleaseLink in
src/models/ReleaseLink.ts (15-19), and CreateReleaseTag in
src/models/ReleaseTag.ts (16-21) to derive from their corresponding Release,
ReleaseComment, ReleaseLabel, ReleaseLink, and ReleaseTag interfaces while
preserving each DTO’s current create-field optionality.
Source: Coding guidelines
| describe("Release Work Items", () => { | ||
| it("should add work items to a release", async () => { | ||
| if (!config.workItemId) { | ||
| return; | ||
| } | ||
|
|
||
| const result = await client.releases.workItems.add(workspaceSlug, release.id, { | ||
| work_item_ids: [config.workItemId], | ||
| }); | ||
|
|
||
| expect(result).toBeDefined(); | ||
| expect(result.message).toBeDefined(); | ||
| }); | ||
|
|
||
| it("should list work items in a release", async () => { | ||
| if (!config.workItemId) { | ||
| return; | ||
| } | ||
|
|
||
| const workItems = await client.releases.workItems.list(workspaceSlug, release.id); | ||
|
|
||
| expect(workItems).toBeDefined(); | ||
| expect(Array.isArray(workItems.results)).toBe(true); | ||
|
|
||
| const found = workItems.results.find((wi) => wi.id === config.workItemId); | ||
| expect(found).toBeDefined(); | ||
| expect(found?.project_id).toBeDefined(); | ||
| expect(found?.name).toBeDefined(); | ||
| }); | ||
|
|
||
| it("should remove work items from a release", async () => { | ||
| if (!config.workItemId) { | ||
| return; | ||
| } | ||
|
|
||
| await client.releases.workItems.remove(workspaceSlug, release.id, { | ||
| work_item_ids: [config.workItemId], | ||
| }); | ||
|
|
||
| const workItems = await client.releases.workItems.list(workspaceSlug, release.id); | ||
| const found = workItems.results.find((wi) => wi.id === config.workItemId); | ||
| expect(found).toBeUndefined(); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Skip the work-item suite instead of silently passing its tests.
When config.workItemId is absent, all three tests return successfully without assertions. Wrap this block with describeIf(!!config.workItemId, ...) and remove the per-test guards so the result is reported as skipped.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/unit/release.test.ts` around lines 218 - 260, Update the “Release Work
Items” suite to use describeIf(!!config.workItemId, ...) so it is reported as
skipped when no work item ID is configured. Remove the individual
config.workItemId early-return guards from the add, list, and remove tests while
preserving their existing assertions and operations.
| describe("delete", () => { | ||
| it("DELETEs the release detail URL", async () => { | ||
| mockedAxios.delete.mockResolvedValueOnce(axiosResponse(undefined)); | ||
|
|
||
| await client.releases.delete(MOCK_SLUG, "rel-1"); | ||
|
|
||
| expect(mockedAxios.delete).toHaveBeenCalledWith( | ||
| mockUrl(`/workspaces/${MOCK_SLUG}/releases/rel-1/`), | ||
| expect.anything() | ||
| ); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Expose deletion operations as del, not delete.
The new release resources consistently expose delete, which conflicts with the SDK’s required standard resource-method contract. Rename the corresponding API methods to del and update these tests:
tests/unit/releases/releases.test.ts#L102-L113: callclient.releases.del.tests/unit/releases/tags.test.ts#L64-L73: callclient.releases.tags.del.tests/unit/release.test.ts#L38-L59: usedelfor release, label, and tag cleanup.tests/unit/release.test.ts#L301-L306: callclient.releases.comments.del.tests/unit/release.test.ts#L351-L356: callclient.releases.links.del.tests/unit/releases/comments.test.ts#L68-L77: callclient.releases.comments.del.tests/unit/releases/labels.test.ts#L67-L76: callclient.releases.labels.del.tests/unit/releases/links.test.ts#L67-L76: callclient.releases.links.del.
As per coding guidelines, standard API resource methods must be named list, create, retrieve, update, and del.
📍 Affects 6 files
tests/unit/releases/releases.test.ts#L102-L113(this comment)tests/unit/releases/tags.test.ts#L64-L73tests/unit/release.test.ts#L38-L59tests/unit/release.test.ts#L301-L306tests/unit/release.test.ts#L351-L356tests/unit/releases/comments.test.ts#L68-L77tests/unit/releases/labels.test.ts#L67-L76tests/unit/releases/links.test.ts#L67-L76
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/unit/releases/releases.test.ts` around lines 102 - 113, Rename
release-resource deletion methods from delete to del to match the standard
resource contract, updating the corresponding implementations and calls. In
tests/unit/releases/releases.test.ts:102-113,
tests/unit/releases/tags.test.ts:64-73, tests/unit/release.test.ts:38-59,
301-306, and 351-356, tests/unit/releases/comments.test.ts:68-77,
tests/unit/releases/labels.test.ts:67-76, and
tests/unit/releases/links.test.ts:67-76, use del for releases, tags, labels,
comments, and links, including release, label, and tag cleanup.
Source: Coding guidelines
Summary
Adds workspace-scoped Releases support to the SDK, mirroring the plane-ee external
/api/v1contract (apps/api/plane/api/{urls,views,serializers}/release.py+db/models/release.py, cross-checked against the contract tests intest_releases.py).New client surface:
client.releasesreleases/CRUDclient.releases.labelsreleases/labels/CRUD +addLabels/removeLabels/listLabelsonreleases/{id}/labels/({label_ids})client.releases.tagsreleases/tags/CRUD (version identifiers)client.releases.workItemsadd/remove/listonreleases/{id}/work-items/({work_item_ids})client.releases.commentsreleases/{id}/comments/CRUDclient.releases.linksreleases/{id}/links/CRUDStructure mirrors the existing
Initiativespattern (parent resource composing sub-resources).Models
Release(+ReleaseStatusenum, nestedReleaseDescription),ReleaseTag,ReleaseLabel,ReleaseComment,ReleaseLink— fields derived directly from the serializers/DB models, with faithful return types:labels.addLabels→ReleaseLabel[]workItems.add→{ message: string }void, lists →PaginatedResponse<T>(cursor pagination:per_page/cursor)Tests
tests/unit/releases/, 6 files / 35 tests) — mockaxios, assert exact URL/verb/body/query + response parsing + error handling. Run in CI with no credentials.tests/unit/release.test.ts, 28 tests) —describeIf-gated, real API calls (needs.env.test).tests/helpers/mock-axios.ts.Verification
pnpm build✅pnpm check:lint— 0 errors/0 warnings on new files ✅pnpm check:format✅Notes
ReleasesFeatureFlagPermission); live integration tests require the RELEASES flag enabled on the test workspace.Summary by CodeRabbit