Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions crates/lineark-sdk/src/generated/client_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,49 @@ impl Client {
) -> Result<T, LinearError> {
crate::generated::mutations::comment_create::<T>(self, input).await
}
/// Updates a comment.
///
/// Full type: [`Comment`](super::types::Comment)
pub async fn comment_update<
T: serde::de::DeserializeOwned
+ crate::field_selection::GraphQLFields<FullType = super::types::Comment>,
>(
&self,
skip_edited_at: Option<bool>,
input: CommentUpdateInput,
id: String,
) -> Result<T, LinearError> {
crate::generated::mutations::comment_update::<T>(self, skip_edited_at, input, id).await
}
/// Deletes a comment.
pub async fn comment_delete(&self, id: String) -> Result<serde_json::Value, LinearError> {
crate::generated::mutations::comment_delete(self, id).await
}
/// Resolves a comment.
///
/// Full type: [`Comment`](super::types::Comment)
pub async fn comment_resolve<
T: serde::de::DeserializeOwned
+ crate::field_selection::GraphQLFields<FullType = super::types::Comment>,
>(
&self,
resolving_comment_id: Option<String>,
id: String,
) -> Result<T, LinearError> {
crate::generated::mutations::comment_resolve::<T>(self, resolving_comment_id, id).await
}
/// Unresolves a comment.
///
/// Full type: [`Comment`](super::types::Comment)
pub async fn comment_unresolve<
T: serde::de::DeserializeOwned
+ crate::field_selection::GraphQLFields<FullType = super::types::Comment>,
>(
&self,
id: String,
) -> Result<T, LinearError> {
crate::generated::mutations::comment_unresolve::<T>(self, id).await
}
/// Creates a new project.
///
/// Full type: [`Project`](super::types::Project)
Expand Down
62 changes: 62 additions & 0 deletions crates/lineark-sdk/src/generated/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,28 @@ pub async fn comment_create<
.execute_mutation::<T>(&query, variables, "commentCreate", "comment")
.await
}
/// Updates a comment.
///
/// Full type: [`Comment`](super::types::Comment)
pub async fn comment_update<
T: serde::de::DeserializeOwned
+ crate::field_selection::GraphQLFields<FullType = super::types::Comment>,
>(
client: &Client,
skip_edited_at: Option<bool>,
input: CommentUpdateInput,
id: String,
) -> Result<T, LinearError> {
let variables = serde_json::json!(
{ "skipEditedAt" : skip_edited_at, "input" : input, "id" : id }
);
let query = String::from(
"mutation CommentUpdate($skipEditedAt: Boolean, $input: CommentUpdateInput!, $id: String!) { commentUpdate(skipEditedAt: $skipEditedAt, input: $input, id: $id) { success comment { ",
) + &T::selection() + " } } }";
client
.execute_mutation::<T>(&query, variables, "commentUpdate", "comment")
.await
}
/// Deletes a comment.
pub async fn comment_delete(client: &Client, id: String) -> Result<serde_json::Value, LinearError> {
let variables = serde_json::json!({ "id" : id });
Expand All @@ -78,6 +100,46 @@ pub async fn comment_delete(client: &Client, id: String) -> Result<serde_json::V
.execute::<serde_json::Value>(&query, variables, "commentDelete")
.await
}
/// Resolves a comment.
///
/// Full type: [`Comment`](super::types::Comment)
pub async fn comment_resolve<
T: serde::de::DeserializeOwned
+ crate::field_selection::GraphQLFields<FullType = super::types::Comment>,
>(
client: &Client,
resolving_comment_id: Option<String>,
id: String,
) -> Result<T, LinearError> {
let variables = serde_json::json!(
{ "resolvingCommentId" : resolving_comment_id, "id" : id }
);
let query = String::from(
"mutation CommentResolve($resolvingCommentId: String, $id: String!) { commentResolve(resolvingCommentId: $resolvingCommentId, id: $id) { success comment { ",
) + &T::selection() + " } } }";
client
.execute_mutation::<T>(&query, variables, "commentResolve", "comment")
.await
}
/// Unresolves a comment.
///
/// Full type: [`Comment`](super::types::Comment)
pub async fn comment_unresolve<
T: serde::de::DeserializeOwned
+ crate::field_selection::GraphQLFields<FullType = super::types::Comment>,
>(
client: &Client,
id: String,
) -> Result<T, LinearError> {
let variables = serde_json::json!({ "id" : id });
let query = String::from(
"mutation CommentUnresolve($id: String!) { commentUnresolve(id: $id) { success comment { ",
) + &T::selection()
+ " } } }";
client
.execute_mutation::<T>(&query, variables, "commentUnresolve", "comment")
.await
}
/// Creates a new project.
///
/// Full type: [`Project`](super::types::Project)
Expand Down
130 changes: 127 additions & 3 deletions crates/lineark-sdk/tests/online.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1068,18 +1068,142 @@ mod online {
client.team_delete(team_id).await.unwrap();
}

// ── Comment Update ────────────────────────────────────────────────────

#[test_with::runtime_ignore_if(no_online_test_token)]
async fn comment_update_changes_body() {
use lineark_sdk::generated::inputs::{
CommentCreateInput, CommentUpdateInput, IssueCreateInput,
};

let client = test_client();
let (team_id, _team_guard) = create_test_team(&client).await;

// Create an issue to comment on.
let issue_input = IssueCreateInput {
title: Some(format!(
"[test] SDK comment_update_changes_body {}",
&uuid::Uuid::new_v4().to_string()[..8]
)),
team_id: Some(team_id),
priority: Some(4),
..Default::default()
};
let issue_entity = client.issue_create::<Issue>(issue_input).await.unwrap();
let issue_id = issue_entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};

// Create a comment with the original body.
let comment_input = CommentCreateInput {
body: Some("Original body".to_string()),
issue_id: Some(issue_id.clone()),
..Default::default()
};
let comment_entity = client
.comment_create::<Comment>(comment_input)
.await
.unwrap();
let comment_id = comment_entity.id.clone().unwrap();
assert_eq!(comment_entity.body.as_deref(), Some("Original body"));

// Update the comment body.
let update_input = CommentUpdateInput {
body: Some("Updated body".to_string()),
..Default::default()
};
let updated = client
.comment_update::<Comment>(None, update_input, comment_id)
.await
.unwrap();
assert_eq!(updated.body.as_deref(), Some("Updated body"));

// Clean up: permanently delete the issue (cascades the comment).
client
.issue_delete::<Issue>(Some(true), issue_id)
.await
.unwrap();
}

#[test_with::runtime_ignore_if(no_online_test_token)]
async fn comment_resolve_and_unresolve() {
use lineark_sdk::generated::inputs::{CommentCreateInput, IssueCreateInput};

let client = test_client();
let (team_id, _team_guard) = create_test_team(&client).await;

// Create an issue to comment on.
let issue_input = IssueCreateInput {
title: Some(format!(
"[test] SDK comment_resolve_and_unresolve {}",
&uuid::Uuid::new_v4().to_string()[..8]
)),
team_id: Some(team_id),
priority: Some(4),
..Default::default()
};
let issue_entity = client.issue_create::<Issue>(issue_input).await.unwrap();
let issue_id = issue_entity.id.clone().unwrap();
let _issue_guard = IssueGuard {
token: test_token(),
id: issue_id.clone(),
};

// Create a comment.
let comment_input = CommentCreateInput {
body: Some("Thread to resolve".to_string()),
issue_id: Some(issue_id.clone()),
..Default::default()
};
let comment_entity = client
.comment_create::<Comment>(comment_input)
.await
.unwrap();
let comment_id = comment_entity.id.clone().unwrap();
assert!(
comment_entity.resolved_at.is_none(),
"new comment should not be resolved"
);

// Resolve the comment thread.
let resolved = client
.comment_resolve::<Comment>(None, comment_id.clone())
.await
.unwrap();
assert!(
resolved.resolved_at.is_some(),
"comment should have resolvedAt after resolve"
);

// Unresolve the comment thread.
let unresolved = client
.comment_unresolve::<Comment>(comment_id)
.await
.unwrap();
assert!(
unresolved.resolved_at.is_none(),
"comment should not have resolvedAt after unresolve"
);

// Clean up: permanently delete the issue.
client
.issue_delete::<Issue>(Some(true), issue_id)
.await
.unwrap();
}

// ── Issue VCS Branch Search ─────────────────────────────────────────────

#[test_with::runtime_ignore_if(no_online_test_token)]
async fn issue_vcs_branch_search_found() {
use lineark_sdk::generated::inputs::IssueCreateInput;

let client = test_client();
let (team_id, _team_guard) = create_test_team(&client).await;

// Create an issue so we can look up its branchName.
let teams = client.teams::<Team>().first(1).send().await.unwrap();
let team_id = teams.nodes[0].id.clone().unwrap();

let uid = &uuid::Uuid::new_v4().to_string()[..8];
let input = IssueCreateInput {
title: Some(format!("[test] SDK branch search {uid}")),
Expand Down
70 changes: 69 additions & 1 deletion crates/lineark/src/commands/comments.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::Args;
use lineark_sdk::generated::inputs::CommentCreateInput;
use lineark_sdk::generated::inputs::{CommentCreateInput, CommentUpdateInput};
use lineark_sdk::generated::types::Comment;
use lineark_sdk::{Client, GraphQLFields};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -28,6 +28,17 @@ pub enum CommentsAction {
#[arg(long)]
body: String,
},
/// Update a comment's body.
///
/// Examples:
/// lineark comments update COMMENT-UUID --body "Updated text"
Update {
/// Comment UUID.
id: String,
/// New comment body in markdown format.
#[arg(long)]
body: Option<String>,
},
/// Delete a comment.
///
/// Examples:
Expand All @@ -36,6 +47,26 @@ pub enum CommentsAction {
/// Comment UUID.
id: String,
},
/// Resolve a comment thread.
///
/// Examples:
/// lineark comments resolve COMMENT-UUID
/// lineark comments resolve COMMENT-UUID --resolving-comment REPLY-UUID
Resolve {
/// Comment UUID (the thread root to resolve).
id: String,
/// Optional UUID of the reply comment that resolves this thread.
#[arg(long)]
resolving_comment: Option<String>,
},
/// Unresolve a previously resolved comment thread.
///
/// Examples:
/// lineark comments unresolve COMMENT-UUID
Unresolve {
/// Comment UUID.
id: String,
},
}

/// Lean result type for comment mutations.
Expand All @@ -45,6 +76,7 @@ pub enum CommentsAction {
struct CommentRef {
id: Option<String>,
body: Option<String>,
resolved_at: Option<String>,
}

pub async fn run(cmd: CommentsCmd, client: &Client, format: Format) -> anyhow::Result<()> {
Expand All @@ -66,6 +98,23 @@ pub async fn run(cmd: CommentsCmd, client: &Client, format: Format) -> anyhow::R

output::print_one(&comment, format);
}
CommentsAction::Update { id, body } => {
if body.is_none() {
anyhow::bail!("No update fields provided. Use --body to set the new comment body.");
}

let input = CommentUpdateInput {
body,
..Default::default()
};

let comment = client
.comment_update::<CommentRef>(None, input, id)
.await
.map_err(|e| anyhow::anyhow!("{}", e))?;

output::print_one(&comment, format);
}
CommentsAction::Delete { id } => {
client
.comment_delete(id)
Expand All @@ -74,6 +123,25 @@ pub async fn run(cmd: CommentsCmd, client: &Client, format: Format) -> anyhow::R

output::print_one(&serde_json::json!({ "success": true }), format);
}
CommentsAction::Resolve {
id,
resolving_comment,
} => {
let comment = client
.comment_resolve::<CommentRef>(resolving_comment, id)
.await
.map_err(|e| anyhow::anyhow!("{}", e))?;

output::print_one(&comment, format);
}
CommentsAction::Unresolve { id } => {
let comment = client
.comment_unresolve::<CommentRef>(id)
.await
.map_err(|e| anyhow::anyhow!("{}", e))?;

output::print_one(&comment, format);
}
}
Ok(())
}
5 changes: 5 additions & 0 deletions crates/lineark/src/commands/usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ COMMANDS:
lineark issues delete <IDENTIFIER> Delete (trash) an issue
[--permanently] Permanently delete instead of trashing
lineark comments create <ISSUE-ID> --body TEXT Comment on an issue
lineark comments update <COMMENT-UUID> Update a comment
--body TEXT New body in markdown
lineark comments resolve <COMMENT-UUID> Resolve a comment thread
[--resolving-comment UUID] Reply that resolves thread
lineark comments unresolve <COMMENT-UUID> Unresolve a comment thread
lineark comments delete <COMMENT-UUID> Delete a comment
lineark relations create <ISSUE> Create an issue relation
--blocks <ISSUE> Source blocks target
Expand Down
Loading