feat: implement bl project image (GET /api/v2/projects/{projectIdOrKey}/image)#123
feat: implement bl project image (GET /api/v2/projects/{projectIdOrKey}/image)#123
Conversation
📝 WalkthroughWalkthroughImplement project image download feature by adding Changes
Sequence DiagramsequenceDiagram
participant User
participant CLI
participant BacklogApi
participant Filesystem
User->>CLI: bl project image <key> --output <path>
CLI->>BacklogApi: download_project_image(key)
BacklogApi->>BacklogApi: GET /projects/{key}/image
BacklogApi-->>CLI: (Vec<u8>, filename)
alt output path provided
CLI->>Filesystem: write bytes to args.output
else output path not provided
CLI->>CLI: compute default_output_path(filename)
CLI->>Filesystem: write bytes to computed path
end
Filesystem-->>CLI: success
CLI-->>User: print success message with path & size
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
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.
Pull request overview
Adds support for downloading a project’s icon image via Backlog API v2 and exposes it as a new bl project image <id-or-key> CLI command, completing coverage for the Project-image endpoint.
Changes:
- Implement
GET /api/v2/projects/{projectIdOrKey}/imagedownload support in the API layer and wire it intoBacklogApi. - Add
bl project imagecommand with--output/-ohandling and unit tests, plus CLI dispatch. - Update English/Japanese command docs and the command-coverage tables to mark the endpoint as implemented.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/api/project.rs |
Adds BacklogClient::download_project_image() endpoint wrapper. |
src/api/mod.rs |
Extends BacklogApi trait + BacklogClient impl to expose project image download. |
src/cmd/project/image.rs |
New command implementation + default output path logic + unit tests. |
src/cmd/project/mod.rs |
Re-exports the new project image command/args. |
src/main.rs |
Adds project image subcommand variant and dispatch plumbing. |
website/docs/commands.md |
Documents bl project image and marks endpoint implemented in coverage table. |
website/i18n/ja/.../commands.md |
Japanese documentation + coverage table update for the new command. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/api/project.rs (1)
608-610: Add API-layer tests for the new download endpoint wrapper.This wrapper is correct, but it currently has no dedicated API-layer coverage in this file. Adding one success-path and one fallback-filename test would protect the
/projects/{key}/imagecontract.As per coding guidelines: “For `api/` layer tests, use `httpmock` to spin up a local HTTP server and construct `BacklogClient::new_with(base_url, api_key)` instead of calling `BacklogClient::from_config()`.”🧪 Suggested test additions
#[cfg(test)] mod tests { use super::*; use httpmock::prelude::*; use serde_json::json; @@ + #[test] + fn download_project_image_returns_bytes_and_filename() { + let server = MockServer::start(); + server.mock(|when, then| { + when.method(GET).path("/projects/TEST/image"); + then.status(200) + .header("content-disposition", "attachment; filename=\"logo_mark.png\"") + .body("PNG"); + }); + + let client = BacklogClient::new_with(&server.base_url(), "test-key").unwrap(); + let (bytes, filename) = client.download_project_image("TEST").unwrap(); + assert_eq!(filename, "logo_mark.png"); + assert_eq!(bytes, b"PNG".to_vec()); + } + + #[test] + fn download_project_image_falls_back_to_attachment_filename() { + let server = MockServer::start(); + server.mock(|when, then| { + when.method(GET).path("/projects/TEST/image"); + then.status(200).body("IMG"); + }); + + let client = BacklogClient::new_with(&server.base_url(), "test-key").unwrap(); + let (_bytes, filename) = client.download_project_image("TEST").unwrap(); + assert_eq!(filename, "attachment"); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/api/project.rs` around lines 608 - 610, Add two httpmock-based API-layer tests for the download_project_image wrapper: (1) success-path: spin up an httpmock server, create BacklogClient::new_with(mock.base_url(), "api_key"), set an expectation for GET /projects/{key}/image to return image bytes with a Content-Disposition header (e.g., attachment; filename="img.png"), call BacklogClient::download_project_image(key) and assert the returned Vec<u8> equals the body and the returned filename equals "img.png"; (2) fallback-filename: same setup but return image bytes without Content-Disposition and assert the returned filename falls back to "{key}.png" (or whatever fallback logic the download function uses). Ensure both tests exercise BacklogClient::download (indirectly via download_project_image) and use httpmock server URL with BacklogClient::new_with as recommended.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/cmd/project/image.rs`:
- Around line 34-47: default_output_path currently checks for "attachment" on
the full input before extracting the basename, so paths like
"foo/attachment.png" won't be detected; change the flow to first extract the
basename via file_name() (falling back to "project_image" when file_name is
None), convert that basename to a Rust &str/OsStr and then run the
lowercase/generic-attachment check on that basename; if the basename is empty or
matches "attachment" or starts_with "attachment.", return "project_image",
otherwise return the basename as PathBuf, keeping the function name
default_output_path and variables like normalized/effective/base in mind to
locate where to reorder the logic.
In `@website/docs/commands.md`:
- Line 931: Update the sentence explaining where binary responses are saved to
also state that the client will treat server-provided filenames of "attachment"
or "attachment.*" as missing and fall back to saving the file in the current
directory (using the returned filename or `project_image` if none is provided);
edit the existing line in commands.md that starts "The response is binary
data..." to explicitly mention the "attachment" / "attachment.*" fallback
behavior so readers know that those server filenames are treated the same as no
filename.
In `@website/i18n/ja/docusaurus-plugin-content-docs/current/commands.md`:
- Around line 931-942: The documented fallback filename ("project_image") and
the example output ("Saved: project_image.png") are inconsistent; update the
documentation for the command examples so they match: either state that the
fallback is "project_image.png" (if the CLI appends an extension) or change the
example output to "Saved: project_image (1234 bytes)" to reflect a no-extension
fallback; modify the text mentioning `project_image` and the example line
`Saved: project_image.png` so both use the same fallback behavior.
---
Nitpick comments:
In `@src/api/project.rs`:
- Around line 608-610: Add two httpmock-based API-layer tests for the
download_project_image wrapper: (1) success-path: spin up an httpmock server,
create BacklogClient::new_with(mock.base_url(), "api_key"), set an expectation
for GET /projects/{key}/image to return image bytes with a Content-Disposition
header (e.g., attachment; filename="img.png"), call
BacklogClient::download_project_image(key) and assert the returned Vec<u8>
equals the body and the returned filename equals "img.png"; (2)
fallback-filename: same setup but return image bytes without Content-Disposition
and assert the returned filename falls back to "{key}.png" (or whatever fallback
logic the download function uses). Ensure both tests exercise
BacklogClient::download (indirectly via download_project_image) and use httpmock
server URL with BacklogClient::new_with as recommended.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5811f7de-ff5a-4101-8092-3daaf939406b
📒 Files selected for processing (7)
src/api/mod.rssrc/api/project.rssrc/cmd/project/image.rssrc/cmd/project/mod.rssrc/main.rswebsite/docs/commands.mdwebsite/i18n/ja/docusaurus-plugin-content-docs/current/commands.md
website/i18n/ja/docusaurus-plugin-content-docs/current/commands.md
Outdated
Show resolved
Hide resolved
… align docs Addresses review comments: basename-first attachment detection, CWD-mutation test removal, and doc clarification for attachment fallback
Checklist
mainwebsite/docs/,website/i18n/ja/,README.md)Summary
bl project image <id-or-key>command to download the project icon image--output/-oto specify the save path; defaults to the server-provided filename (orproject_imageif the server returns no filename / a genericattachmentplaceholder)Reason for change
Implements
GET /api/v2/projects/{projectIdOrKey}/imageso all published Backlog API v2 endpoints in the Project category are covered.Changes
src/api/project.rs— adddownload_project_image(key)src/api/mod.rs— adddownload_project_imagetoBacklogApitrait andimpl BacklogApi for BacklogClientsrc/cmd/project/image.rs— new command withimage()/image_with()/default_output_path()and unit testssrc/cmd/project/mod.rs— re-exportProjectImageArgsandimagesrc/main.rs— addProjectCommands::Imagevariant and dispatchwebsite/docs/commands.md,website/i18n/ja/.../commands.md— add command docs and mark as implementedNotes
Tested against real Backlog API:
bl project image SUK→Saved: logo_mark.png (769 bytes).Closes #119