mirror of
https://github.com/block/buzz.git
synced 2026-11-01 06:51:17 +00:00
fix(cli): keep Git activity cards in source threads
Carry the original thread root separately from the triggering message so generated repository activity remains visible in the conversation that requested it.
This commit is contained in:
@@ -17,6 +17,7 @@ pub(crate) struct GitConversationContext {
|
||||
struct ActivityContext {
|
||||
channel_id: Uuid,
|
||||
source_message: EventId,
|
||||
thread_root: EventId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -41,15 +42,22 @@ impl GitEntityType {
|
||||
pub(crate) fn parse_git_conversation_context(
|
||||
channel: Option<&str>,
|
||||
source_message: Option<&str>,
|
||||
thread_root: Option<&str>,
|
||||
) -> Result<GitConversationContext, CliError> {
|
||||
if source_message.is_some() && channel.is_none() {
|
||||
return Err(CliError::Usage(
|
||||
"--source-message requires --channel".into(),
|
||||
));
|
||||
}
|
||||
if thread_root.is_some() && source_message.is_none() {
|
||||
return Err(CliError::Usage(
|
||||
"--thread-root requires --source-message".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let channel_id = channel.map(parse_uuid).transpose()?;
|
||||
let source_event = source_message.map(parse_event_id).transpose()?;
|
||||
let thread_root = thread_root.map(parse_event_id).transpose()?;
|
||||
let source_message = match (channel_id, source_event) {
|
||||
(Some(channel_id), Some(source_message)) => Some(GitSourceMessage {
|
||||
channel_id: channel_id.to_string(),
|
||||
@@ -61,6 +69,7 @@ pub(crate) fn parse_git_conversation_context(
|
||||
(Some(channel_id), Some(source_message)) => Some(ActivityContext {
|
||||
channel_id,
|
||||
source_message,
|
||||
thread_root: thread_root.unwrap_or(source_message),
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
@@ -116,7 +125,7 @@ fn build_activity_message(
|
||||
git_url: &str,
|
||||
) -> Result<EventBuilder, CliError> {
|
||||
let thread_ref = ThreadRef {
|
||||
root_event_id: context.source_message,
|
||||
root_event_id: context.thread_root,
|
||||
parent_event_id: context.source_message,
|
||||
};
|
||||
let content = format!("Created [{}]({git_url})", markdown_link_label(title.trim()));
|
||||
@@ -252,11 +261,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn source_message_requires_channel_and_valid_values() {
|
||||
assert!(parse_git_conversation_context(None, Some(&"a".repeat(64))).is_err());
|
||||
assert!(parse_git_conversation_context(Some("not-a-uuid"), None).is_err());
|
||||
assert!(parse_git_conversation_context(None, Some(&"a".repeat(64)), None).is_err());
|
||||
assert!(parse_git_conversation_context(Some("not-a-uuid"), None, None).is_err());
|
||||
assert!(parse_git_conversation_context(
|
||||
Some("11111111-1111-4111-8111-111111111111"),
|
||||
Some("not-an-event")
|
||||
None,
|
||||
Some(&"c".repeat(64)),
|
||||
)
|
||||
.is_err());
|
||||
assert!(parse_git_conversation_context(
|
||||
Some("11111111-1111-4111-8111-111111111111"),
|
||||
Some("not-an-event"),
|
||||
None,
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
@@ -267,6 +283,7 @@ mod tests {
|
||||
let context = ActivityContext {
|
||||
channel_id: Uuid::parse_str("11111111-1111-4111-8111-111111111111").expect("channel"),
|
||||
source_message: source,
|
||||
thread_root: source,
|
||||
};
|
||||
let event = build_activity_message(
|
||||
&context,
|
||||
@@ -291,4 +308,32 @@ mod tests {
|
||||
.iter()
|
||||
.any(|tag| { tag.as_slice() == ["e", &"b".repeat(64), "", "reply"] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn activity_message_preserves_existing_thread_root() {
|
||||
let source = parse_event_id(&"b".repeat(64)).expect("source event");
|
||||
let root = parse_event_id(&"c".repeat(64)).expect("root event");
|
||||
let context = ActivityContext {
|
||||
channel_id: Uuid::parse_str("11111111-1111-4111-8111-111111111111").expect("channel"),
|
||||
source_message: source,
|
||||
thread_root: root,
|
||||
};
|
||||
let event = build_activity_message(
|
||||
&context,
|
||||
"Repository",
|
||||
"buzz://git?repo=owner%3Arepo&type=repository",
|
||||
)
|
||||
.expect("activity builder")
|
||||
.sign_with_keys(&Keys::generate())
|
||||
.expect("signed activity");
|
||||
|
||||
assert!(event
|
||||
.tags
|
||||
.iter()
|
||||
.any(|tag| tag.as_slice() == ["e", &"c".repeat(64), "", "root"]));
|
||||
assert!(event
|
||||
.tags
|
||||
.iter()
|
||||
.any(|tag| tag.as_slice() == ["e", &"b".repeat(64), "", "reply"]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,12 @@ pub async fn cmd_create_issue(
|
||||
to: &[String],
|
||||
channel: Option<&str>,
|
||||
source_message: Option<&str>,
|
||||
thread_root: Option<&str>,
|
||||
) -> Result<(), CliError> {
|
||||
validate_hex64(repo_owner)?;
|
||||
validate_repo_id(repo_id)?;
|
||||
let body = read_or_stdin(content)?;
|
||||
let context = parse_git_conversation_context(channel, source_message)?;
|
||||
let context = parse_git_conversation_context(channel, source_message, thread_root)?;
|
||||
|
||||
let meta = GitIssueMeta {
|
||||
labels: labels.to_vec(),
|
||||
@@ -170,6 +171,7 @@ pub async fn dispatch(cmd: crate::IssuesCmd, client: &BuzzClient) -> Result<(),
|
||||
to,
|
||||
channel,
|
||||
source_message,
|
||||
thread_root,
|
||||
} => {
|
||||
cmd_create_issue(
|
||||
client,
|
||||
@@ -181,6 +183,7 @@ pub async fn dispatch(cmd: crate::IssuesCmd, client: &BuzzClient) -> Result<(),
|
||||
&to,
|
||||
channel.as_deref(),
|
||||
source_message.as_deref(),
|
||||
thread_root.as_deref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -25,11 +25,12 @@ pub async fn cmd_send_patch(
|
||||
committer: Option<&str>,
|
||||
channel: Option<&str>,
|
||||
source_message: Option<&str>,
|
||||
thread_root: Option<&str>,
|
||||
) -> Result<(), CliError> {
|
||||
validate_hex64(repo_owner)?;
|
||||
validate_repo_id(repo_id)?;
|
||||
let content = read_file_or_stdin(patch)?;
|
||||
let context = parse_git_conversation_context(channel, source_message)?;
|
||||
let context = parse_git_conversation_context(channel, source_message, thread_root)?;
|
||||
|
||||
let committer = match committer {
|
||||
Some(spec) => Some(parse_committer(spec)?),
|
||||
@@ -244,6 +245,7 @@ pub async fn dispatch(cmd: crate::PatchesCmd, client: &BuzzClient) -> Result<(),
|
||||
committer,
|
||||
channel,
|
||||
source_message,
|
||||
thread_root,
|
||||
} => {
|
||||
cmd_send_patch(
|
||||
client,
|
||||
@@ -261,6 +263,7 @@ pub async fn dispatch(cmd: crate::PatchesCmd, client: &BuzzClient) -> Result<(),
|
||||
committer.as_deref(),
|
||||
channel.as_deref(),
|
||||
source_message.as_deref(),
|
||||
thread_root.as_deref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -36,12 +36,13 @@ pub async fn cmd_open_pr(
|
||||
to: &[String],
|
||||
channel: Option<&str>,
|
||||
source_message: Option<&str>,
|
||||
thread_root: Option<&str>,
|
||||
revision_of: Option<&str>,
|
||||
) -> Result<(), CliError> {
|
||||
validate_hex64(repo_owner)?;
|
||||
validate_repo_id(repo_id)?;
|
||||
let content = read_optional_body(body, body_file)?;
|
||||
let context = parse_git_conversation_context(channel, source_message)?;
|
||||
let context = parse_git_conversation_context(channel, source_message, thread_root)?;
|
||||
|
||||
let repo = GitRepoCoord {
|
||||
owner: repo_owner.to_string(),
|
||||
@@ -243,6 +244,7 @@ pub async fn dispatch(cmd: crate::PrCmd, client: &BuzzClient) -> Result<(), CliE
|
||||
to,
|
||||
channel,
|
||||
source_message,
|
||||
thread_root,
|
||||
revision_of,
|
||||
} => {
|
||||
cmd_open_pr(
|
||||
@@ -261,6 +263,7 @@ pub async fn dispatch(cmd: crate::PrCmd, client: &BuzzClient) -> Result<(), CliE
|
||||
&to,
|
||||
channel.as_deref(),
|
||||
source_message.as_deref(),
|
||||
thread_root.as_deref(),
|
||||
revision_of.as_deref(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -212,9 +212,10 @@ pub async fn cmd_create_repo(
|
||||
relays: &[String],
|
||||
channel: Option<&str>,
|
||||
source_message: Option<&str>,
|
||||
thread_root: Option<&str>,
|
||||
) -> Result<(), CliError> {
|
||||
validate_repo_id(repo_id)?;
|
||||
let context = parse_git_conversation_context(channel, source_message)?;
|
||||
let context = parse_git_conversation_context(channel, source_message, thread_root)?;
|
||||
|
||||
let clone_refs: Vec<&str> = clone_urls.iter().map(|s| s.as_str()).collect();
|
||||
let relay_refs: Vec<&str> = relays.iter().map(|s| s.as_str()).collect();
|
||||
@@ -378,6 +379,7 @@ pub async fn dispatch(cmd: crate::ReposCmd, client: &BuzzClient) -> Result<(), C
|
||||
relays,
|
||||
channel,
|
||||
source_message,
|
||||
thread_root,
|
||||
} => {
|
||||
cmd_create_repo(
|
||||
client,
|
||||
@@ -389,6 +391,7 @@ pub async fn dispatch(cmd: crate::ReposCmd, client: &BuzzClient) -> Result<(), C
|
||||
&relays,
|
||||
channel.as_deref(),
|
||||
source_message.as_deref(),
|
||||
thread_root.as_deref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1119,6 +1119,9 @@ pub enum ReposCmd {
|
||||
/// Triggering Buzz message event ID; requires --channel
|
||||
#[arg(long, requires = "channel")]
|
||||
source_message: Option<String>,
|
||||
/// Original thread root when the triggering message is already a reply
|
||||
#[arg(long, requires = "source_message")]
|
||||
thread_root: Option<String>,
|
||||
},
|
||||
/// Get a repository announcement
|
||||
Get {
|
||||
@@ -1244,6 +1247,9 @@ pub enum PatchesCmd {
|
||||
/// Triggering Buzz message event ID; requires --channel
|
||||
#[arg(long, requires = "channel")]
|
||||
source_message: Option<String>,
|
||||
/// Original thread root when the triggering message is already a reply
|
||||
#[arg(long, requires = "source_message")]
|
||||
thread_root: Option<String>,
|
||||
},
|
||||
/// Get a patch by event id
|
||||
Get {
|
||||
@@ -1356,6 +1362,9 @@ pub enum PrCmd {
|
||||
/// Triggering Buzz message event ID; requires --channel
|
||||
#[arg(long, requires = "channel")]
|
||||
source_message: Option<String>,
|
||||
/// Original thread root when the triggering message is already a reply
|
||||
#[arg(long, requires = "source_message")]
|
||||
thread_root: Option<String>,
|
||||
/// Root patch event id this PR revises
|
||||
#[arg(long)]
|
||||
revision_of: Option<String>,
|
||||
@@ -1482,6 +1491,9 @@ pub enum IssuesCmd {
|
||||
/// Triggering Buzz message event ID; requires --channel
|
||||
#[arg(long, requires = "channel")]
|
||||
source_message: Option<String>,
|
||||
/// Original thread root when the triggering message is already a reply
|
||||
#[arg(long, requires = "source_message")]
|
||||
thread_root: Option<String>,
|
||||
},
|
||||
/// Get an issue by event id
|
||||
Get {
|
||||
@@ -1894,6 +1906,22 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_thread_root_requires_source_message() {
|
||||
let error = Cli::try_parse_from([
|
||||
"buzz",
|
||||
"repos",
|
||||
"create",
|
||||
"--id",
|
||||
"repo",
|
||||
"--thread-root",
|
||||
&"a".repeat(64),
|
||||
])
|
||||
.err()
|
||||
.expect("--source-message must be required");
|
||||
assert!(error.to_string().contains("--source-message"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_inventory_is_stable() {
|
||||
let expected_groups: Vec<&str> = vec![
|
||||
|
||||
Reference in New Issue
Block a user