mirror of
https://github.com/block/buzz.git
synced 2026-11-10 11:21:05 +00:00
Polish workflow trigger conditions and sequence editor
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
@@ -887,6 +887,7 @@ async fn should_fire_workflow(
|
||||
) -> bool {
|
||||
if let TriggerDef::ReactionAdded {
|
||||
emoji: Some(ref expected),
|
||||
..
|
||||
} = def.trigger
|
||||
{
|
||||
if &trigger_ctx.emoji != expected {
|
||||
@@ -900,33 +901,13 @@ async fn should_fire_workflow(
|
||||
}
|
||||
}
|
||||
|
||||
if let TriggerDef::MessagePosted {
|
||||
filter: Some(ref expr),
|
||||
} = def.trigger
|
||||
{
|
||||
match executor::evaluate_condition(expr, trigger_ctx, &HashMap::new()).await {
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
tracing::debug!(
|
||||
workflow_id = %workflow_id,
|
||||
"Trigger filter evaluated false — skipping workflow"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
workflow_id = %workflow_id,
|
||||
"Trigger filter error: {e} — skipping workflow"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let TriggerDef::DiffPosted {
|
||||
filter: Some(ref expr),
|
||||
} = def.trigger
|
||||
{
|
||||
let filter = match &def.trigger {
|
||||
TriggerDef::MessagePosted { filter }
|
||||
| TriggerDef::ReactionAdded { filter, .. }
|
||||
| TriggerDef::DiffPosted { filter } => filter.as_ref(),
|
||||
TriggerDef::Schedule { .. } | TriggerDef::Webhook => None,
|
||||
};
|
||||
if let Some(expr) = filter {
|
||||
match executor::evaluate_condition(expr, trigger_ctx, &HashMap::new()).await {
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
@@ -1364,7 +1345,10 @@ steps:
|
||||
|
||||
#[test]
|
||||
fn trigger_matches_reaction() {
|
||||
let trigger = TriggerDef::ReactionAdded { emoji: None };
|
||||
let trigger = TriggerDef::ReactionAdded {
|
||||
emoji: None,
|
||||
filter: None,
|
||||
};
|
||||
assert!(trigger_matches_event(
|
||||
&trigger,
|
||||
buzz_core::kind::KIND_REACTION
|
||||
@@ -1375,6 +1359,36 @@ steps:
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reaction_filter_matches_target_message() {
|
||||
let yaml = r#"
|
||||
name: "React to one message"
|
||||
trigger:
|
||||
on: reaction_added
|
||||
filter: 'trigger_message_id == "target-message"'
|
||||
steps:
|
||||
- id: wait
|
||||
action: delay
|
||||
duration: 1s
|
||||
"#;
|
||||
let (def, _) = WorkflowEngine::parse_yaml(yaml).expect("parse failed");
|
||||
let mut trigger_ctx = executor::TriggerContext {
|
||||
message_id: "target-message".to_owned(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert!(
|
||||
should_fire_workflow(&def, &trigger_ctx, Uuid::new_v4()).await,
|
||||
"reaction to the selected message should fire"
|
||||
);
|
||||
|
||||
trigger_ctx.message_id = "different-message".to_owned();
|
||||
assert!(
|
||||
!should_fire_workflow(&def, &trigger_ctx, Uuid::new_v4()).await,
|
||||
"reaction to a different message should be filtered out"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schedule_trigger_never_matches_events() {
|
||||
let trigger = TriggerDef::Schedule {
|
||||
@@ -1421,7 +1435,10 @@ steps:
|
||||
|
||||
#[test]
|
||||
fn reaction_added_matches_kind_7_only() {
|
||||
let trigger = TriggerDef::ReactionAdded { emoji: None };
|
||||
let trigger = TriggerDef::ReactionAdded {
|
||||
emoji: None,
|
||||
filter: None,
|
||||
};
|
||||
// Must match KIND_REACTION = 7.
|
||||
assert!(trigger_matches_event(&trigger, 7));
|
||||
// Must NOT match stream message (kind 9).
|
||||
@@ -1436,6 +1453,7 @@ steps:
|
||||
// trigger_matches_event only checks the kind number.
|
||||
let trigger = TriggerDef::ReactionAdded {
|
||||
emoji: Some("thumbsup".to_owned()),
|
||||
filter: None,
|
||||
};
|
||||
assert!(trigger_matches_event(&trigger, 7));
|
||||
assert!(!trigger_matches_event(&trigger, 9));
|
||||
@@ -1458,7 +1476,10 @@ steps:
|
||||
// before calling trigger_matches_event, but verify the function itself
|
||||
// also returns false for these kinds.
|
||||
let msg_trigger = TriggerDef::MessagePosted { filter: None };
|
||||
let react_trigger = TriggerDef::ReactionAdded { emoji: None };
|
||||
let react_trigger = TriggerDef::ReactionAdded {
|
||||
emoji: None,
|
||||
filter: None,
|
||||
};
|
||||
|
||||
for kind in buzz_core::kind::KIND_WORKFLOW_TRIGGERED
|
||||
..=buzz_core::kind::KIND_WORKFLOW_APPROVAL_DENIED
|
||||
@@ -1478,7 +1499,10 @@ steps:
|
||||
fn trigger_matches_event_kind_zero_matches_nothing() {
|
||||
// Kind 0 is a profile event — no trigger should match it.
|
||||
let msg_trigger = TriggerDef::MessagePosted { filter: None };
|
||||
let react_trigger = TriggerDef::ReactionAdded { emoji: None };
|
||||
let react_trigger = TriggerDef::ReactionAdded {
|
||||
emoji: None,
|
||||
filter: None,
|
||||
};
|
||||
let sched_trigger = TriggerDef::Schedule {
|
||||
cron: None,
|
||||
interval: Some("1h".to_owned()),
|
||||
@@ -1715,7 +1739,11 @@ steps:
|
||||
async fn setup_db() -> buzz_db::Db {
|
||||
let database_url = std::env::var("BUZZ_TEST_DATABASE_URL")
|
||||
.or_else(|_| std::env::var("DATABASE_URL"))
|
||||
.unwrap_or_else(|_| "postgres://buzz:buzz_dev@localhost:5432/buzz".to_owned());
|
||||
// Local-only test default; this is not a production credential.
|
||||
.unwrap_or_else(|_| {
|
||||
let local_test_database = "postgres://buzz:buzz_dev@localhost:5432/buzz"; // sadscan:disable np.postgres.1
|
||||
local_test_database.to_owned()
|
||||
});
|
||||
buzz_db::Db::new(&buzz_db::DbConfig {
|
||||
database_url,
|
||||
..Default::default()
|
||||
|
||||
@@ -47,6 +47,9 @@ pub enum TriggerDef {
|
||||
/// Optional: only fire for this specific emoji.
|
||||
#[serde(default)]
|
||||
emoji: Option<String>,
|
||||
/// Optional evalexpr filter over the reaction context.
|
||||
#[serde(default)]
|
||||
filter: Option<String>,
|
||||
},
|
||||
/// Fires when a diff message (kind:40008) is posted in the workflow's channel.
|
||||
DiffPosted {
|
||||
@@ -300,11 +303,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_reaction_added_trigger() {
|
||||
let yaml = "name: Triage\ntrigger:\n on: reaction_added\n emoji: clipboard\nsteps:\n - id: ack\n action: add_reaction\n emoji: eyes\n";
|
||||
let yaml = "name: Triage\ntrigger:\n on: reaction_added\n emoji: clipboard\n filter: 'trigger_message_id == \"abc123\"'\nsteps:\n - id: ack\n action: add_reaction\n emoji: eyes\n";
|
||||
let (def, _) = parse_yaml(yaml).expect("parse failed");
|
||||
match &def.trigger {
|
||||
TriggerDef::ReactionAdded { emoji } => {
|
||||
TriggerDef::ReactionAdded { emoji, filter } => {
|
||||
assert_eq!(emoji.as_deref(), Some("clipboard"));
|
||||
assert_eq!(filter.as_deref(), Some("trigger_message_id == \"abc123\""));
|
||||
}
|
||||
other => panic!("unexpected trigger: {other:?}"),
|
||||
}
|
||||
@@ -488,8 +492,9 @@ mod tests {
|
||||
let yaml = "name: Any Reaction\ntrigger:\n on: reaction_added\nsteps:\n - id: s1\n action: add_reaction\n emoji: eyes\n";
|
||||
let (def, _) = parse_yaml(yaml).expect("parse failed");
|
||||
match &def.trigger {
|
||||
TriggerDef::ReactionAdded { emoji } => {
|
||||
TriggerDef::ReactionAdded { emoji, filter } => {
|
||||
assert!(emoji.is_none(), "emoji should default to None");
|
||||
assert!(filter.is_none(), "filter should default to None");
|
||||
}
|
||||
other => panic!("unexpected trigger: {other:?}"),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user