diff --git a/src/api/issue.rs b/src/api/issue.rs index 7ed66d6..53e2e5b 100644 --- a/src/api/issue.rs +++ b/src/api/issue.rs @@ -96,6 +96,21 @@ pub struct IssueComment { pub extra: BTreeMap, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct IssueCommentCount { + pub count: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct IssueCommentNotification { + pub id: u64, + pub already_read: bool, + pub reason: u64, + pub user: IssueUser, + pub resource_already_read: bool, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct IssueAttachment { @@ -181,6 +196,41 @@ impl BacklogClient { let value = self.get(&format!("/issues/{}/attachments", key))?; deserialize(value) } + + pub fn count_issue_comments(&self, key: &str) -> Result { + let value = self.get(&format!("/issues/{}/comments/count", key))?; + deserialize(value) + } + + pub fn get_issue_comment(&self, key: &str, comment_id: u64) -> Result { + let value = self.get(&format!("/issues/{}/comments/{}", key, comment_id))?; + deserialize(value) + } + + pub fn get_issue_comment_notifications( + &self, + key: &str, + comment_id: u64, + ) -> Result> { + let value = self.get(&format!( + "/issues/{}/comments/{}/notifications", + key, comment_id + ))?; + deserialize(value) + } + + pub fn add_issue_comment_notifications( + &self, + key: &str, + comment_id: u64, + params: &[(String, String)], + ) -> Result> { + let value = self.post_form( + &format!("/issues/{}/comments/{}/notifications", key, comment_id), + params, + )?; + deserialize(value) + } } #[cfg(test)] @@ -249,6 +299,16 @@ mod tests { }) } + fn notification_json() -> serde_json::Value { + json!({ + "id": 1, + "alreadyRead": false, + "reason": 2, + "user": issue_user_json(), + "resourceAlreadyRead": false + }) + } + fn attachment_json() -> serde_json::Value { json!({ "id": 1, @@ -358,6 +418,68 @@ mod tests { assert_eq!(attachments[0].name, "file.txt"); } + #[test] + fn count_issue_comments_returns_count() { + let server = MockServer::start(); + server.mock(|when, then| { + when.method(GET) + .path("/issues/TEST-1/comments/count") + .query_param("apiKey", TEST_KEY); + then.status(200).json_body(json!({"count": 7})); + }); + let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap(); + let count = client.count_issue_comments("TEST-1").unwrap(); + assert_eq!(count.count, 7); + } + + #[test] + fn get_issue_comment_returns_single() { + let server = MockServer::start(); + server.mock(|when, then| { + when.method(GET) + .path("/issues/TEST-1/comments/1") + .query_param("apiKey", TEST_KEY); + then.status(200).json_body(comment_json()); + }); + let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap(); + let comment = client.get_issue_comment("TEST-1", 1).unwrap(); + assert_eq!(comment.content.as_deref(), Some("A comment")); + } + + #[test] + fn get_issue_comment_notifications_returns_list() { + let server = MockServer::start(); + server.mock(|when, then| { + when.method(GET) + .path("/issues/TEST-1/comments/1/notifications") + .query_param("apiKey", TEST_KEY); + then.status(200).json_body(json!([notification_json()])); + }); + let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap(); + let notifications = client.get_issue_comment_notifications("TEST-1", 1).unwrap(); + assert_eq!(notifications.len(), 1); + assert_eq!(notifications[0].id, 1); + assert!(!notifications[0].already_read); + } + + #[test] + fn add_issue_comment_notifications_returns_list() { + let server = MockServer::start(); + server.mock(|when, then| { + when.method(POST) + .path("/issues/TEST-1/comments/1/notifications") + .query_param("apiKey", TEST_KEY); + then.status(200).json_body(json!([notification_json()])); + }); + let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap(); + let params = vec![("notifiedUserId[]".to_string(), "1".to_string())]; + let notifications = client + .add_issue_comment_notifications("TEST-1", 1, ¶ms) + .unwrap(); + assert_eq!(notifications.len(), 1); + assert_eq!(notifications[0].user.name, "John Doe"); + } + #[test] fn issue_with_null_user_id_deserializes() { let v = json!({ diff --git a/src/api/mod.rs b/src/api/mod.rs index 0be2731..7170959 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -20,7 +20,9 @@ pub mod wiki; use activity::Activity; use disk_usage::DiskUsage; -use issue::{Issue, IssueAttachment, IssueComment, IssueCount}; +use issue::{ + Issue, IssueAttachment, IssueComment, IssueCommentCount, IssueCommentNotification, IssueCount, +}; use licence::Licence; use notification::{Notification, NotificationCount}; use project::{ @@ -139,6 +141,27 @@ pub trait BacklogApi { fn get_issue_attachments(&self, _key: &str) -> Result> { unimplemented!() } + fn count_issue_comments(&self, _key: &str) -> Result { + unimplemented!() + } + fn get_issue_comment(&self, _key: &str, _comment_id: u64) -> Result { + unimplemented!() + } + fn get_issue_comment_notifications( + &self, + _key: &str, + _comment_id: u64, + ) -> Result> { + unimplemented!() + } + fn add_issue_comment_notifications( + &self, + _key: &str, + _comment_id: u64, + _params: &[(String, String)], + ) -> Result> { + unimplemented!() + } fn get_wikis(&self, _params: &[(String, String)]) -> Result> { unimplemented!() } @@ -317,6 +340,31 @@ impl BacklogApi for BacklogClient { self.get_issue_attachments(key) } + fn count_issue_comments(&self, key: &str) -> Result { + self.count_issue_comments(key) + } + + fn get_issue_comment(&self, key: &str, comment_id: u64) -> Result { + self.get_issue_comment(key, comment_id) + } + + fn get_issue_comment_notifications( + &self, + key: &str, + comment_id: u64, + ) -> Result> { + self.get_issue_comment_notifications(key, comment_id) + } + + fn add_issue_comment_notifications( + &self, + key: &str, + comment_id: u64, + params: &[(String, String)], + ) -> Result> { + self.add_issue_comment_notifications(key, comment_id, params) + } + fn get_wikis(&self, params: &[(String, String)]) -> Result> { self.get_wikis(params) } diff --git a/src/cmd/issue/comment/count.rs b/src/cmd/issue/comment/count.rs new file mode 100644 index 0000000..c1dc6cf --- /dev/null +++ b/src/cmd/issue/comment/count.rs @@ -0,0 +1,75 @@ +use anstream::println; +use anyhow::{Context, Result}; + +use crate::api::{BacklogApi, BacklogClient}; + +pub struct IssueCommentCountArgs { + key: String, + json: bool, +} + +impl IssueCommentCountArgs { + pub fn new(key: String, json: bool) -> Self { + Self { key, json } + } +} + +pub fn count(args: &IssueCommentCountArgs) -> Result<()> { + let client = BacklogClient::from_config()?; + count_with(args, &client) +} + +pub fn count_with(args: &IssueCommentCountArgs, api: &dyn BacklogApi) -> Result<()> { + let result = api.count_issue_comments(&args.key)?; + if args.json { + println!( + "{}", + serde_json::to_string_pretty(&result).context("Failed to serialize JSON")? + ); + } else { + println!("{}", result.count); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::api::issue::IssueCommentCount; + use anyhow::anyhow; + + struct MockApi { + count: Option, + } + + impl crate::api::BacklogApi for MockApi { + fn count_issue_comments(&self, _key: &str) -> anyhow::Result { + self.count + .map(|c| IssueCommentCount { count: c }) + .ok_or_else(|| anyhow!("no count")) + } + } + + fn args(json: bool) -> IssueCommentCountArgs { + IssueCommentCountArgs::new("TEST-1".to_string(), json) + } + + #[test] + fn count_with_text_output_succeeds() { + let api = MockApi { count: Some(5) }; + assert!(count_with(&args(false), &api).is_ok()); + } + + #[test] + fn count_with_json_output_succeeds() { + let api = MockApi { count: Some(5) }; + assert!(count_with(&args(true), &api).is_ok()); + } + + #[test] + fn count_with_propagates_api_error() { + let api = MockApi { count: None }; + let err = count_with(&args(false), &api).unwrap_err(); + assert!(err.to_string().contains("no count")); + } +} diff --git a/src/cmd/issue/comment/mod.rs b/src/cmd/issue/comment/mod.rs index 8ccc418..704205f 100644 --- a/src/cmd/issue/comment/mod.rs +++ b/src/cmd/issue/comment/mod.rs @@ -1,9 +1,14 @@ pub mod add; +pub mod count; pub mod delete; pub mod list; +pub mod notification; +pub mod show; pub mod update; pub use add::{IssueCommentAddArgs, add}; +pub use count::{IssueCommentCountArgs, count}; pub use delete::{IssueCommentDeleteArgs, delete}; pub use list::{IssueCommentListArgs, list}; +pub use show::{IssueCommentShowArgs, show}; pub use update::{IssueCommentUpdateArgs, update}; diff --git a/src/cmd/issue/comment/notification/add.rs b/src/cmd/issue/comment/notification/add.rs new file mode 100644 index 0000000..3a67b73 --- /dev/null +++ b/src/cmd/issue/comment/notification/add.rs @@ -0,0 +1,142 @@ +use anstream::println; +use anyhow::{Context, Result}; + +use crate::api::{BacklogApi, BacklogClient}; +use crate::cmd::issue::comment::notification::list::format_notification_row; + +#[derive(Debug)] +pub struct IssueCommentNotificationAddArgs { + key: String, + comment_id: u64, + notified_user_ids: Vec, + json: bool, +} + +impl IssueCommentNotificationAddArgs { + pub fn try_new( + key: String, + comment_id: u64, + notified_user_ids: Vec, + json: bool, + ) -> anyhow::Result { + if notified_user_ids.is_empty() { + anyhow::bail!("at least one --notified-user-id is required"); + } + Ok(Self { + key, + comment_id, + notified_user_ids, + json, + }) + } +} + +pub fn add(args: &IssueCommentNotificationAddArgs) -> Result<()> { + let client = BacklogClient::from_config()?; + add_with(args, &client) +} + +pub fn add_with(args: &IssueCommentNotificationAddArgs, api: &dyn BacklogApi) -> Result<()> { + let params: Vec<(String, String)> = args + .notified_user_ids + .iter() + .map(|id| ("notifiedUserId[]".to_string(), id.to_string())) + .collect(); + let notifications = api.add_issue_comment_notifications(&args.key, args.comment_id, ¶ms)?; + if args.json { + println!( + "{}", + serde_json::to_string_pretty(¬ifications).context("Failed to serialize JSON")? + ); + } else { + for n in ¬ifications { + println!("{}", format_notification_row(n)); + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::api::issue::IssueCommentNotification; + use crate::cmd::issue::comment::notification::list::sample_notification; + use anyhow::anyhow; + + use std::cell::RefCell; + + struct MockApi { + notifications: Option>, + captured_params: RefCell>, + } + + impl MockApi { + fn new(notifications: Option>) -> Self { + Self { + notifications, + captured_params: RefCell::new(vec![]), + } + } + } + + impl crate::api::BacklogApi for MockApi { + fn add_issue_comment_notifications( + &self, + _key: &str, + _comment_id: u64, + params: &[(String, String)], + ) -> anyhow::Result> { + *self.captured_params.borrow_mut() = params.to_vec(); + self.notifications + .clone() + .ok_or_else(|| anyhow!("no notifications")) + } + } + + fn args(json: bool) -> IssueCommentNotificationAddArgs { + IssueCommentNotificationAddArgs::try_new("TEST-1".to_string(), 1, vec![2], json).unwrap() + } + + #[test] + fn add_with_text_output_succeeds() { + let api = MockApi::new(Some(vec![sample_notification()])); + assert!(add_with(&args(false), &api).is_ok()); + } + + #[test] + fn add_with_json_output_succeeds() { + let api = MockApi::new(Some(vec![sample_notification()])); + assert!(add_with(&args(true), &api).is_ok()); + } + + #[test] + fn add_with_propagates_api_error() { + let api = MockApi::new(None); + let err = add_with(&args(false), &api).unwrap_err(); + assert!(err.to_string().contains("no notifications")); + } + + #[test] + fn add_with_builds_correct_params() { + let api = MockApi::new(Some(vec![sample_notification()])); + let a = + IssueCommentNotificationAddArgs::try_new("TEST-1".to_string(), 1, vec![1, 2], false) + .unwrap(); + add_with(&a, &api).unwrap(); + let params = api.captured_params.borrow(); + assert_eq!( + *params, + vec![ + ("notifiedUserId[]".to_string(), "1".to_string()), + ("notifiedUserId[]".to_string(), "2".to_string()), + ] + ); + } + + #[test] + fn try_new_rejects_empty_user_ids() { + let err = IssueCommentNotificationAddArgs::try_new("TEST-1".to_string(), 1, vec![], false) + .unwrap_err(); + assert!(err.to_string().contains("at least one")); + } +} diff --git a/src/cmd/issue/comment/notification/list.rs b/src/cmd/issue/comment/notification/list.rs new file mode 100644 index 0000000..378dc5d --- /dev/null +++ b/src/cmd/issue/comment/notification/list.rs @@ -0,0 +1,126 @@ +use anstream::println; +use anyhow::{Context, Result}; + +use crate::api::{BacklogApi, BacklogClient, issue::IssueCommentNotification}; + +pub struct IssueCommentNotificationListArgs { + key: String, + comment_id: u64, + json: bool, +} + +impl IssueCommentNotificationListArgs { + pub fn new(key: String, comment_id: u64, json: bool) -> Self { + Self { + key, + comment_id, + json, + } + } +} + +pub fn list(args: &IssueCommentNotificationListArgs) -> Result<()> { + let client = BacklogClient::from_config()?; + list_with(args, &client) +} + +pub fn list_with(args: &IssueCommentNotificationListArgs, api: &dyn BacklogApi) -> Result<()> { + let notifications = api.get_issue_comment_notifications(&args.key, args.comment_id)?; + if args.json { + println!( + "{}", + serde_json::to_string_pretty(¬ifications).context("Failed to serialize JSON")? + ); + } else { + for n in ¬ifications { + println!("{}", format_notification_row(n)); + } + } + Ok(()) +} + +pub fn format_notification_row(n: &IssueCommentNotification) -> String { + format!("[{}] {}", n.id, n.user.name) +} + +#[cfg(test)] +use crate::api::issue::IssueUser; +#[cfg(test)] +use std::collections::BTreeMap; + +#[cfg(test)] +pub(crate) fn sample_notification() -> IssueCommentNotification { + IssueCommentNotification { + id: 1, + already_read: false, + reason: 2, + user: IssueUser { + id: 1, + user_id: Some("john".to_string()), + name: "John Doe".to_string(), + role_type: 1, + lang: None, + mail_address: None, + extra: BTreeMap::new(), + }, + resource_already_read: false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::anyhow; + + struct MockApi { + notifications: Option>, + } + + impl crate::api::BacklogApi for MockApi { + fn get_issue_comment_notifications( + &self, + _key: &str, + _comment_id: u64, + ) -> anyhow::Result> { + self.notifications + .clone() + .ok_or_else(|| anyhow!("no notifications")) + } + } + + fn args(json: bool) -> IssueCommentNotificationListArgs { + IssueCommentNotificationListArgs::new("TEST-1".to_string(), 1, json) + } + + #[test] + fn list_with_text_output_succeeds() { + let api = MockApi { + notifications: Some(vec![sample_notification()]), + }; + assert!(list_with(&args(false), &api).is_ok()); + } + + #[test] + fn list_with_json_output_succeeds() { + let api = MockApi { + notifications: Some(vec![sample_notification()]), + }; + assert!(list_with(&args(true), &api).is_ok()); + } + + #[test] + fn list_with_propagates_api_error() { + let api = MockApi { + notifications: None, + }; + let err = list_with(&args(false), &api).unwrap_err(); + assert!(err.to_string().contains("no notifications")); + } + + #[test] + fn format_notification_row_contains_id_and_name() { + let row = format_notification_row(&sample_notification()); + assert!(row.contains('1')); + assert!(row.contains("John Doe")); + } +} diff --git a/src/cmd/issue/comment/notification/mod.rs b/src/cmd/issue/comment/notification/mod.rs new file mode 100644 index 0000000..2c67c0d --- /dev/null +++ b/src/cmd/issue/comment/notification/mod.rs @@ -0,0 +1,5 @@ +pub mod add; +pub mod list; + +pub use add::{IssueCommentNotificationAddArgs, add}; +pub use list::{IssueCommentNotificationListArgs, list}; diff --git a/src/cmd/issue/comment/show.rs b/src/cmd/issue/comment/show.rs new file mode 100644 index 0000000..35dd0b7 --- /dev/null +++ b/src/cmd/issue/comment/show.rs @@ -0,0 +1,84 @@ +use anstream::println; +use anyhow::{Context, Result}; + +use crate::api::{BacklogApi, BacklogClient}; +use crate::cmd::issue::comment::list::format_comment_row; + +pub struct IssueCommentShowArgs { + key: String, + comment_id: u64, + json: bool, +} + +impl IssueCommentShowArgs { + pub fn new(key: String, comment_id: u64, json: bool) -> Self { + Self { + key, + comment_id, + json, + } + } +} + +pub fn show(args: &IssueCommentShowArgs) -> Result<()> { + let client = BacklogClient::from_config()?; + show_with(args, &client) +} + +pub fn show_with(args: &IssueCommentShowArgs, api: &dyn BacklogApi) -> Result<()> { + let comment = api.get_issue_comment(&args.key, args.comment_id)?; + if args.json { + println!( + "{}", + serde_json::to_string_pretty(&comment).context("Failed to serialize JSON")? + ); + } else { + println!("{}", format_comment_row(&comment)); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::api::issue::IssueComment; + use crate::cmd::issue::comment::list::sample_comment; + use anyhow::anyhow; + + struct MockApi { + comment: Option, + } + + impl crate::api::BacklogApi for MockApi { + fn get_issue_comment(&self, _key: &str, _comment_id: u64) -> anyhow::Result { + self.comment.clone().ok_or_else(|| anyhow!("no comment")) + } + } + + fn args(json: bool) -> IssueCommentShowArgs { + IssueCommentShowArgs::new("TEST-1".to_string(), 1, json) + } + + #[test] + fn show_with_text_output_succeeds() { + let api = MockApi { + comment: Some(sample_comment()), + }; + assert!(show_with(&args(false), &api).is_ok()); + } + + #[test] + fn show_with_json_output_succeeds() { + let api = MockApi { + comment: Some(sample_comment()), + }; + assert!(show_with(&args(true), &api).is_ok()); + } + + #[test] + fn show_with_propagates_api_error() { + let api = MockApi { comment: None }; + let err = show_with(&args(false), &api).unwrap_err(); + assert!(err.to_string().contains("no comment")); + } +} diff --git a/src/main.rs b/src/main.rs index 3ca66a7..bbff8c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,12 @@ use clap::{Parser, Subcommand}; use cmd::auth::AuthStatusArgs; use cmd::issue::attachment::IssueAttachmentListArgs; +use cmd::issue::comment::notification::{ + IssueCommentNotificationAddArgs, IssueCommentNotificationListArgs, +}; use cmd::issue::comment::{ - IssueCommentAddArgs, IssueCommentDeleteArgs, IssueCommentListArgs, IssueCommentUpdateArgs, + IssueCommentAddArgs, IssueCommentCountArgs, IssueCommentDeleteArgs, IssueCommentListArgs, + IssueCommentShowArgs, IssueCommentUpdateArgs, }; use cmd::issue::{ IssueCountArgs, IssueCreateArgs, IssueDeleteArgs, IssueListArgs, IssueShowArgs, @@ -498,6 +502,56 @@ enum IssueCommentCommands { #[arg(long)] json: bool, }, + /// Count comments on an issue + Count { + /// Issue ID or key + id_or_key: String, + /// Output as JSON + #[arg(long)] + json: bool, + }, + /// Show a specific comment + Show { + /// Issue ID or key + id_or_key: String, + /// Comment ID + comment_id: u64, + /// Output as JSON + #[arg(long)] + json: bool, + }, + /// Manage comment notifications + Notification { + #[command(subcommand)] + action: IssueCommentNotificationCommands, + }, +} + +#[derive(Subcommand)] +enum IssueCommentNotificationCommands { + /// List notifications for a comment + List { + /// Issue ID or key + id_or_key: String, + /// Comment ID + comment_id: u64, + /// Output as JSON + #[arg(long)] + json: bool, + }, + /// Add notifications for a comment + Add { + /// Issue ID or key + id_or_key: String, + /// Comment ID + comment_id: u64, + /// User ID to notify (repeatable) + #[arg(long = "notified-user-id", value_name = "ID")] + notified_user_ids: Vec, + /// Output as JSON + #[arg(long)] + json: bool, + }, } #[derive(Subcommand)] @@ -990,6 +1044,38 @@ fn run() -> Result<()> { } => cmd::issue::comment::delete(&IssueCommentDeleteArgs::new( id_or_key, comment_id, json, )), + IssueCommentCommands::Count { id_or_key, json } => { + cmd::issue::comment::count(&IssueCommentCountArgs::new(id_or_key, json)) + } + IssueCommentCommands::Show { + id_or_key, + comment_id, + json, + } => cmd::issue::comment::show(&IssueCommentShowArgs::new( + id_or_key, comment_id, json, + )), + IssueCommentCommands::Notification { action } => match action { + IssueCommentNotificationCommands::List { + id_or_key, + comment_id, + json, + } => cmd::issue::comment::notification::list( + &IssueCommentNotificationListArgs::new(id_or_key, comment_id, json), + ), + IssueCommentNotificationCommands::Add { + id_or_key, + comment_id, + notified_user_ids, + json, + } => cmd::issue::comment::notification::add( + &IssueCommentNotificationAddArgs::try_new( + id_or_key, + comment_id, + notified_user_ids, + json, + )?, + ), + }, }, IssueCommands::Attachment { action } => match action { IssueAttachmentCommands::List { id_or_key, json } => { diff --git a/website/docs/commands.md b/website/docs/commands.md index acf2d68..74d57a5 100644 --- a/website/docs/commands.md +++ b/website/docs/commands.md @@ -444,6 +444,42 @@ bl issue comment delete TEST-1 42 bl issue comment delete TEST-1 42 --json ``` +## `bl issue comment count` + +Count comments on an issue. + +```bash +bl issue comment count TEST-1 +bl issue comment count TEST-1 --json +``` + +## `bl issue comment show` + +Show a specific comment. + +```bash +bl issue comment show TEST-1 42 +bl issue comment show TEST-1 42 --json +``` + +## `bl issue comment notification list` + +List notifications for a comment. + +```bash +bl issue comment notification list TEST-1 42 +bl issue comment notification list TEST-1 42 --json +``` + +## `bl issue comment notification add` + +Add notifications for a comment. + +```bash +bl issue comment notification add TEST-1 42 --notified-user-id 1 +bl issue comment notification add TEST-1 42 --notified-user-id 1 --notified-user-id 2 --json +``` + ## `bl issue attachment list` List attachments on an issue. @@ -815,12 +851,12 @@ The table below maps Backlog API v2 endpoints to `bl` commands. | `bl issue delete ` | `DELETE /api/v2/issues/{issueIdOrKey}` | ✅ Implemented | | `bl issue comment list ` | `GET /api/v2/issues/{issueIdOrKey}/comments` | ✅ Implemented | | `bl issue comment add ` | `POST /api/v2/issues/{issueIdOrKey}/comments` | ✅ Implemented | -| `bl issue comment count ` | `GET /api/v2/issues/{issueIdOrKey}/comments/count` | Planned | -| `bl issue comment show ` | `GET /api/v2/issues/{issueIdOrKey}/comments/{commentId}` | Planned | +| `bl issue comment count ` | `GET /api/v2/issues/{issueIdOrKey}/comments/count` | ✅ Implemented | +| `bl issue comment show ` | `GET /api/v2/issues/{issueIdOrKey}/comments/{commentId}` | ✅ Implemented | | `bl issue comment update ` | `PATCH /api/v2/issues/{issueIdOrKey}/comments/{commentId}` | ✅ Implemented | | `bl issue comment delete ` | `DELETE /api/v2/issues/{issueIdOrKey}/comments/{commentId}` | ✅ Implemented | -| `bl issue comment notification list ` | `GET /api/v2/issues/{issueIdOrKey}/comments/{commentId}/notifications` | Planned | -| `bl issue comment notification add ` | `POST /api/v2/issues/{issueIdOrKey}/comments/{commentId}/notifications` | Planned | +| `bl issue comment notification list ` | `GET /api/v2/issues/{issueIdOrKey}/comments/{commentId}/notifications` | ✅ Implemented | +| `bl issue comment notification add ` | `POST /api/v2/issues/{issueIdOrKey}/comments/{commentId}/notifications` | ✅ Implemented | | `bl issue attachment list ` | `GET /api/v2/issues/{issueIdOrKey}/attachments` | ✅ Implemented | | `bl issue attachment get ` | `GET /api/v2/issues/{issueIdOrKey}/attachments/{attachmentId}` | Planned | | `bl issue attachment delete ` | `DELETE /api/v2/issues/{issueIdOrKey}/attachments/{attachmentId}` | Planned | diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/commands.md b/website/i18n/ja/docusaurus-plugin-content-docs/current/commands.md index dc5de1d..df860cc 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/commands.md +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/commands.md @@ -444,6 +444,42 @@ bl issue comment delete TEST-1 42 bl issue comment delete TEST-1 42 --json ``` +## `bl issue comment count` + +課題のコメント数を取得します。 + +```bash +bl issue comment count TEST-1 +bl issue comment count TEST-1 --json +``` + +## `bl issue comment show` + +特定のコメントを表示します。 + +```bash +bl issue comment show TEST-1 42 +bl issue comment show TEST-1 42 --json +``` + +## `bl issue comment notification list` + +コメントの通知一覧を取得します。 + +```bash +bl issue comment notification list TEST-1 42 +bl issue comment notification list TEST-1 42 --json +``` + +## `bl issue comment notification add` + +コメントに通知を追加します。 + +```bash +bl issue comment notification add TEST-1 42 --notified-user-id 1 +bl issue comment notification add TEST-1 42 --notified-user-id 1 --notified-user-id 2 --json +``` + ## `bl issue attachment list` 課題の添付ファイルを一覧表示します。 @@ -819,12 +855,12 @@ Backlog API v2 エンドポイントと `bl` コマンドの対応表です。 | `bl issue delete ` | `DELETE /api/v2/issues/{issueIdOrKey}` | ✅ 実装済み | | `bl issue comment list ` | `GET /api/v2/issues/{issueIdOrKey}/comments` | ✅ 実装済み | | `bl issue comment add ` | `POST /api/v2/issues/{issueIdOrKey}/comments` | ✅ 実装済み | -| `bl issue comment count ` | `GET /api/v2/issues/{issueIdOrKey}/comments/count` | 計画中 | -| `bl issue comment show ` | `GET /api/v2/issues/{issueIdOrKey}/comments/{commentId}` | 計画中 | +| `bl issue comment count ` | `GET /api/v2/issues/{issueIdOrKey}/comments/count` | ✅ 実装済み | +| `bl issue comment show ` | `GET /api/v2/issues/{issueIdOrKey}/comments/{commentId}` | ✅ 実装済み | | `bl issue comment update ` | `PATCH /api/v2/issues/{issueIdOrKey}/comments/{commentId}` | ✅ 実装済み | | `bl issue comment delete ` | `DELETE /api/v2/issues/{issueIdOrKey}/comments/{commentId}` | ✅ 実装済み | -| `bl issue comment notification list ` | `GET /api/v2/issues/{issueIdOrKey}/comments/{commentId}/notifications` | 計画中 | -| `bl issue comment notification add ` | `POST /api/v2/issues/{issueIdOrKey}/comments/{commentId}/notifications` | 計画中 | +| `bl issue comment notification list ` | `GET /api/v2/issues/{issueIdOrKey}/comments/{commentId}/notifications` | ✅ 実装済み | +| `bl issue comment notification add ` | `POST /api/v2/issues/{issueIdOrKey}/comments/{commentId}/notifications` | ✅ 実装済み | | `bl issue attachment list ` | `GET /api/v2/issues/{issueIdOrKey}/attachments` | ✅ 実装済み | | `bl issue attachment get ` | `GET /api/v2/issues/{issueIdOrKey}/attachments/{attachmentId}` | 計画中 | | `bl issue attachment delete ` | `DELETE /api/v2/issues/{issueIdOrKey}/attachments/{attachmentId}` | 計画中 |