feat: add bl user activities and bl user recently-viewed commands#31
feat: add bl user activities and bl user recently-viewed commands#31
Conversation
📝 WalkthroughWalkthroughAdds two user-focused API methods ( Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as "CLI (main/run)"
participant Cmd as "cmd::user::<command>"
participant Client as "BacklogClient"
participant API as "Backlog API (server)"
CLI->>Cmd: invoke activities(id) / recently_viewed()
Cmd->>Client: get_user_activities(user_id) / get_recently_viewed_issues()
Client->>API: HTTP GET /api/v2/... (user activities or recentlyViewedIssues)
API-->>Client: JSON response
Client-->>Cmd: parsed Activity[] / RecentlyViewedIssue[]
Cmd->>CLI: render rows or pretty JSON
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
✨ Simplify code
📝 Coding Plan for PR comments
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 two new bl user subcommands to expose additional Backlog user-related endpoints, including full CLI wiring, API client support, tests, and documentation updates.
Changes:
- Add
bl user activities <id>to display recent activities for a specified user. - Add
bl user recently-viewedto display issues recently viewed by the authenticated user. - Extend
BacklogApi/BacklogClientwithget_user_activities()andget_recently_viewed_issues()plus docs updates (EN/JA).
Reviewed changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| website/i18n/ja/docusaurus-plugin-content-docs/current/commands.md | Adds JA docs sections for the new commands and marks them implemented in the coverage table. |
| website/docs/commands.md | Adds EN docs sections for the new commands and marks them implemented in the coverage table. |
| src/main.rs | Adds clap subcommands and dispatch wiring for user activities and user recently-viewed. |
| src/api/user.rs | Adds RecentlyViewedIssue plus BacklogClient methods for user activities and recently viewed issues (with httpmock tests). |
| src/api/mod.rs | Extends BacklogApi trait and its BacklogClient impl with the new user endpoints. |
| src/cmd/user/activities.rs | Implements bl user activities <id> with text/JSON output and cmd-layer tests. |
| src/cmd/user/recently_viewed.rs | Implements bl user recently-viewed with text/JSON output and cmd-layer tests. |
| src/cmd/user/mod.rs | Registers and re-exports the new user subcommands. |
| src/cmd/user/list.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/user/show.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/auth.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/space/activities.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/space/disk_usage.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/space/notification.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/space/show.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/team/list.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/team/show.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/project/activities.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/project/category.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/project/disk_usage.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/project/issue_type.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/project/list.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/project/show.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/project/status.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/project/user.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/project/version.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/count.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/create.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/delete.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/list.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/show.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/update.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/attachment/list.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/comment/add.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/comment/delete.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/comment/list.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/issue/comment/update.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/wiki/attachment/list.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/wiki/create.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/wiki/delete.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/wiki/history.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/wiki/list.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/wiki/show.rs | Updates MockApi test impl with new required trait stubs. |
| src/cmd/wiki/update.rs | Updates MockApi test impl with new required trait stubs. |
You can also share your feedback on Copilot code review. Take the survey.
There was a problem hiding this comment.
🧹 Nitpick comments (4)
src/api/user.rs (2)
64-73: Same clone consideration as above.Consistent with
get_user_activities, this also clones the value for error reporting.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/api/user.rs` around lines 64 - 73, The function get_recently_viewed_issues currently clones `value` to preserve raw JSON for error reporting; instead, serialize `value` to a `raw` string first (using serde_json::to_string_pretty(&value).unwrap_or_else(|_| value.to_string())), then move `value` into serde_json::from_value(value) and, on error, include the previously computed `raw` string in the anyhow::anyhow! message; this removes the extra clone and mirrors the pattern used in get_user_activities while preserving the raw JSON for logging.
53-62: Consider avoiding the unnecessary clone.The
value.clone()on line 55 is only needed for the error path. A more efficient approach would be to attempt deserialization first, then format the error with the original value only on failure.♻️ Optional refactor to avoid clone on success path
pub fn get_user_activities(&self, user_id: u64) -> Result<Vec<Activity>> { let value = self.get(&format!("/users/{user_id}/activities"))?; - serde_json::from_value(value.clone()).map_err(|e| { + serde_json::from_value(value).map_err(|e| { anyhow::anyhow!( - "Failed to deserialize user activities response: {}\nRaw JSON:\n{}", - e, - serde_json::to_string_pretty(&value).unwrap_or_else(|_| value.to_string()) + "Failed to deserialize user activities response: {}", + e ) }) }Alternatively, keep the clone if raw JSON in errors is valuable for debugging API response mismatches.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/api/user.rs` around lines 53 - 62, In get_user_activities: avoid cloning the JSON on the success path by attempting deserialization first and only preparing a cloned/raw JSON when deserialization fails; replace the one-line serde_json::from_value(value.clone()) call with a match on serde_json::from_value(value) (or similar flow) that returns Ok(activity_vec) on success and, in the Err branch, uses the original value (cloned there) to produce the pretty-printed raw JSON for the anyhow error message so the clone only happens for the failure path.src/cmd/user/activities.rs (1)
240-244: Assert the forwardeduser_idin the mock.These tests still pass if
activities_with()hardcodes or drops the requested user id, becauseget_user_activitiesignores its argument. That leaves the command's core per-user behavior untested.♻️ Suggested test tightening
struct MockApi { + expected_user_id: u64, activities: Option<Vec<Activity>>, } - fn get_user_activities(&self, _user_id: u64) -> anyhow::Result<Vec<Activity>> { + fn get_user_activities(&self, user_id: u64) -> anyhow::Result<Vec<Activity>> { + assert_eq!(user_id, self.expected_user_id); self.activities .clone() .ok_or_else(|| anyhow!("no activities")) }Then initialize the mock with
expected_user_id: 1in the tests that callUserActivitiesArgs::new(1, ...).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cmd/user/activities.rs` around lines 240 - 244, The mock's get_user_activities ignores its user_id, so tests can pass without verifying per-user behavior; update the mock used by activities_with() to store an expected_user_id field and in get_user_activities(&self, user_id: u64) assert or compare that user_id == self.expected_user_id before returning self.activities.clone().ok_or_else(|| anyhow!("no activities")), and update tests that construct UserActivitiesArgs::new(1, ...) to initialize the mock with expected_user_id: 1 so the forwarded user_id is actually validated.src/cmd/user/recently_viewed.rs (1)
311-318: Make the assignee fallback assertion unambiguous.
assert!(text.contains('-'))already passes becauseBLG-1contains a hyphen, so this test does not actually prove theNone => "-"branch informat_row()works.♻️ Suggested assertion
assert!(text.contains("[BLG-1]")); assert!(text.contains("Fix login")); assert!(text.contains("Open")); - assert!(text.contains('-')); + assert!(text.contains("(Open, -)"));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cmd/user/recently_viewed.rs` around lines 311 - 318, The test format_row_contains_fields is asserting a hyphen which is ambiguous because "[BLG-1]" contains '-' so update the assignee fallback assertion to explicitly check for the assignee placeholder produced by format_row (e.g., assert that the formatted text contains the exact "-” token as used for missing assignee such as " - " or the full label the function emits). Locate the test function format_row_contains_fields and the formatter function format_row (and sample_item()) and replace the loose assert!(text.contains('-')) with an assertion that matches the exact assignee fallback string emitted by format_row (for example assert!(text.contains(" - ")) or assert!(text.contains("Assignee: -")) depending on format_row's output).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/api/user.rs`:
- Around line 64-73: The function get_recently_viewed_issues currently clones
`value` to preserve raw JSON for error reporting; instead, serialize `value` to
a `raw` string first (using
serde_json::to_string_pretty(&value).unwrap_or_else(|_| value.to_string())),
then move `value` into serde_json::from_value(value) and, on error, include the
previously computed `raw` string in the anyhow::anyhow! message; this removes
the extra clone and mirrors the pattern used in get_user_activities while
preserving the raw JSON for logging.
- Around line 53-62: In get_user_activities: avoid cloning the JSON on the
success path by attempting deserialization first and only preparing a cloned/raw
JSON when deserialization fails; replace the one-line
serde_json::from_value(value.clone()) call with a match on
serde_json::from_value(value) (or similar flow) that returns Ok(activity_vec) on
success and, in the Err branch, uses the original value (cloned there) to
produce the pretty-printed raw JSON for the anyhow error message so the clone
only happens for the failure path.
In `@src/cmd/user/activities.rs`:
- Around line 240-244: The mock's get_user_activities ignores its user_id, so
tests can pass without verifying per-user behavior; update the mock used by
activities_with() to store an expected_user_id field and in
get_user_activities(&self, user_id: u64) assert or compare that user_id ==
self.expected_user_id before returning self.activities.clone().ok_or_else(||
anyhow!("no activities")), and update tests that construct
UserActivitiesArgs::new(1, ...) to initialize the mock with expected_user_id: 1
so the forwarded user_id is actually validated.
In `@src/cmd/user/recently_viewed.rs`:
- Around line 311-318: The test format_row_contains_fields is asserting a hyphen
which is ambiguous because "[BLG-1]" contains '-' so update the assignee
fallback assertion to explicitly check for the assignee placeholder produced by
format_row (e.g., assert that the formatted text contains the exact "-” token as
used for missing assignee such as " - " or the full label the function emits).
Locate the test function format_row_contains_fields and the formatter function
format_row (and sample_item()) and replace the loose assert!(text.contains('-'))
with an assertion that matches the exact assignee fallback string emitted by
format_row (for example assert!(text.contains(" - ")) or
assert!(text.contains("Assignee: -")) depending on format_row's output).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 58b31d81-ae3f-482a-a3da-41209176c8d1
📒 Files selected for processing (44)
src/api/mod.rssrc/api/user.rssrc/cmd/auth.rssrc/cmd/issue/attachment/list.rssrc/cmd/issue/comment/add.rssrc/cmd/issue/comment/delete.rssrc/cmd/issue/comment/list.rssrc/cmd/issue/comment/update.rssrc/cmd/issue/count.rssrc/cmd/issue/create.rssrc/cmd/issue/delete.rssrc/cmd/issue/list.rssrc/cmd/issue/show.rssrc/cmd/issue/update.rssrc/cmd/project/activities.rssrc/cmd/project/category.rssrc/cmd/project/disk_usage.rssrc/cmd/project/issue_type.rssrc/cmd/project/list.rssrc/cmd/project/show.rssrc/cmd/project/status.rssrc/cmd/project/user.rssrc/cmd/project/version.rssrc/cmd/space/activities.rssrc/cmd/space/disk_usage.rssrc/cmd/space/notification.rssrc/cmd/space/show.rssrc/cmd/team/list.rssrc/cmd/team/show.rssrc/cmd/user/activities.rssrc/cmd/user/list.rssrc/cmd/user/mod.rssrc/cmd/user/recently_viewed.rssrc/cmd/user/show.rssrc/cmd/wiki/attachment/list.rssrc/cmd/wiki/create.rssrc/cmd/wiki/delete.rssrc/cmd/wiki/history.rssrc/cmd/wiki/list.rssrc/cmd/wiki/show.rssrc/cmd/wiki/update.rssrc/main.rswebsite/docs/commands.mdwebsite/i18n/ja/docusaurus-plugin-content-docs/current/commands.md
Addresses review comment: assert contains '-' also matched the issue key hyphen
… error format Addresses review comments: add flatten extra map for consistency; factor repeated Raw JSON block into shared helper
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/cmd/user/recently_viewed.rs (2)
263-309: Consider adding a test case with a present assignee.The
sample_issue()helper setsassignee: None(line 290), so all tests exercise the fallback path to"-". Adding a variant with a present assignee would improve coverage offormat_row.💡 Optional: Add test for present assignee
fn sample_issue_with_assignee() -> Issue { let mut issue = sample_issue(); issue.assignee = Some(sample_issue_user()); issue } #[test] fn format_row_with_assignee_contains_name() { let mut item = sample_item(); item.issue = sample_issue_with_assignee(); let text = format_row(&item); assert!(text.contains("(Open, admin)")); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cmd/user/recently_viewed.rs` around lines 263 - 309, The helper sample_issue() always sets assignee: None, so tests only exercise the no-assignee fallback in format_row; add a variant that sets an assignee and use it in a new test to assert the assignee name appears. Implement a helper like sample_issue_with_assignee() that clones sample_issue(), sets issue.assignee = Some(sample_issue_user()), then in a test (e.g., format_row_with_assignee_contains_name) replace item.issue from sample_item() with that variant and assert format_row(&item) contains the assignee (e.g., "(Open, admin)").
21-34: Consider providing user feedback when the list is empty.When
itemsis empty, the function silently returnsOk(())without any output. While this is typical Unix CLI behavior, users might wonder if the command succeeded or if there are genuinely no recently viewed issues.💡 Optional: Add feedback for empty results
pub fn recently_viewed_with(args: &UserRecentlyViewedArgs, api: &dyn BacklogApi) -> Result<()> { let items = api.get_recently_viewed_issues()?; + if items.is_empty() && !args.json { + println!("No recently viewed issues."); + return Ok(()); + } if args.json { println!( "{}", serde_json::to_string_pretty(&items).context("Failed to serialize JSON")? ); } else { for item in &items { println!("{}", format_row(item)); } } Ok(()) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cmd/user/recently_viewed.rs` around lines 21 - 34, In recently_viewed_with (taking &UserRecentlyViewedArgs and &dyn BacklogApi) add an explicit empty-result branch after retrieving items (the items variable from api.get_recently_viewed_issues()) so the CLI provides feedback: if args.json keep current JSON serialization (which will print []), but if not args.json and items.is_empty() print a clear human message like "No recently viewed issues." instead of producing no output; otherwise continue iterating and printing format_row(item) for each entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/cmd/user/recently_viewed.rs`:
- Around line 263-309: The helper sample_issue() always sets assignee: None, so
tests only exercise the no-assignee fallback in format_row; add a variant that
sets an assignee and use it in a new test to assert the assignee name appears.
Implement a helper like sample_issue_with_assignee() that clones sample_issue(),
sets issue.assignee = Some(sample_issue_user()), then in a test (e.g.,
format_row_with_assignee_contains_name) replace item.issue from sample_item()
with that variant and assert format_row(&item) contains the assignee (e.g.,
"(Open, admin)").
- Around line 21-34: In recently_viewed_with (taking &UserRecentlyViewedArgs and
&dyn BacklogApi) add an explicit empty-result branch after retrieving items (the
items variable from api.get_recently_viewed_issues()) so the CLI provides
feedback: if args.json keep current JSON serialization (which will print []),
but if not args.json and items.is_empty() print a clear human message like "No
recently viewed issues." instead of producing no output; otherwise continue
iterating and printing format_row(item) for each entry.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6119db94-594a-456f-b48a-281bbf0d4517
📒 Files selected for processing (1)
src/cmd/user/recently_viewed.rs
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/cmd/user/recently_viewed.rs (1)
312-341: Consider adding a test for empty results.The tests cover success cases with data and error propagation well. An optional enhancement would be to add a test verifying behavior when the API returns an empty list.
📝 Optional: Add empty list test
+ #[test] + fn recently_viewed_with_empty_list_succeeds() { + let api = MockApi { + items: Some(vec![]), + }; + assert!(recently_viewed_with(&UserRecentlyViewedArgs::new(false), &api).is_ok()); + } + #[test] fn recently_viewed_with_propagates_api_error() {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cmd/user/recently_viewed.rs` around lines 312 - 341, Add a test that verifies behavior when the API returns an empty list by calling recently_viewed_with with a MockApi whose items is Some(vec![]) (use UserRecentlyViewedArgs::new(false) and/or new(true) to exercise both output modes) and assert the function returns Ok and/or produces the expected message/empty output; locate tests near format_row_contains_fields/recently_viewed_with_* and use the same fixtures (MockApi, sample_item, recently_viewed_with, UserRecentlyViewedArgs) so the new test mirrors the existing patterns for success cases but with an empty Vec.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/cmd/user/recently_viewed.rs`:
- Around line 312-341: Add a test that verifies behavior when the API returns an
empty list by calling recently_viewed_with with a MockApi whose items is
Some(vec![]) (use UserRecentlyViewedArgs::new(false) and/or new(true) to
exercise both output modes) and assert the function returns Ok and/or produces
the expected message/empty output; locate tests near
format_row_contains_fields/recently_viewed_with_* and use the same fixtures
(MockApi, sample_item, recently_viewed_with, UserRecentlyViewedArgs) so the new
test mirrors the existing patterns for success cases but with an empty Vec.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c1d507f0-24fd-433b-a9f3-14d826995f76
📒 Files selected for processing (2)
src/api/user.rssrc/cmd/user/recently_viewed.rs
Checklist
mainSummary
bl user activities <id>— shows recent activities of a user (GET /api/v2/users/{userId}/activities)bl user recently-viewed— shows issues recently viewed by the authenticated user (GET /api/v2/users/myself/recentlyViewedIssues)Reason for change
Both commands were listed as Planned in the command coverage table.
Changes
src/api/user.rs—RecentlyViewedIssuestruct,get_user_activities(),get_recently_viewed_issues()methodssrc/api/mod.rs— new trait methods addedsrc/cmd/user/activities.rs,src/cmd/user/recently_viewed.rs— command implementations with testssrc/cmd/user/mod.rs— re-exportssrc/main.rs— clap wiring forbl user activitiesandbl user recently-viewedMockApiimpls updated withunimplemented!()stubswebsite/docs/commands.md,website/i18n/ja/.../commands.md— docs added, status updated to ✅ ImplementedNotes
bl user recently-viewedusesGET /api/v2/users/myself/recentlyViewedIssues(authenticated user only), not/{userId}/recentlyViewedIssues. The command coverage table has been updated accordingly.