archetype flows behave like the built-ins: switch off per archetype or per agent, board cards, duplicate
The document's disabled list takes archetype:<archetype>/<flow> (off for every member) and the same with @<agent> (off for one member); a key naming nothing is ignored. GET /api/notify/flows lists the archetype flows with their members, the board draws a card for each with a switch, a checkbox per agent and duplicate, and the dry run counts their hits. The flows keep following the archetype.
This commit is contained in:
parent
1efbb72fdd
commit
68c24643f6
1 changed files with 908 additions and 102 deletions
|
|
@ -27,6 +27,41 @@ pub const BUCKET: &str = "notify-flows";
|
|||
/// The one key in [`BUCKET`].
|
||||
pub const KEY: &str = "all";
|
||||
|
||||
/// The key an archetype's flow `flow` of `archetype` goes by: in the
|
||||
/// document's `disabled_builtins` it switches the flow off for every member,
|
||||
/// and it names the flow in the router's log, a todo's source and the hit
|
||||
/// counts. `flow` is the `id` the archetype declares.
|
||||
#[must_use]
|
||||
pub fn archetype_flow_key(archetype: &str, flow: &str) -> String {
|
||||
format!("archetype:{archetype}/{flow}")
|
||||
}
|
||||
|
||||
/// [`archetype_flow_key`] followed by `@agent`: in `disabled_builtins` it
|
||||
/// switches the flow off for that one member.
|
||||
#[must_use]
|
||||
pub fn archetype_member_key(archetype: &str, flow: &str, agent: &str) -> String {
|
||||
format!("{}@{agent}", archetype_flow_key(archetype, flow))
|
||||
}
|
||||
|
||||
/// Whether `key` has the shape of [`archetype_flow_key`] or
|
||||
/// [`archetype_member_key`]. Whether such a flow, or agent, exists is not
|
||||
/// asked: a key for one that is gone is ignored, so editing an archetype
|
||||
/// cannot make the stored document fail its check.
|
||||
#[must_use]
|
||||
pub fn is_archetype_key(key: &str) -> bool {
|
||||
let Some(rest) = key.strip_prefix("archetype:") else {
|
||||
return false;
|
||||
};
|
||||
let Some((archetype, rest)) = rest.split_once('/') else {
|
||||
return false;
|
||||
};
|
||||
let (flow, agent) = match rest.split_once('@') {
|
||||
Some((flow, agent)) => (flow, Some(agent)),
|
||||
None => (rest, None),
|
||||
};
|
||||
is_id(archetype) && is_id(flow) && agent.is_none_or(is_id)
|
||||
}
|
||||
|
||||
/// The document layout this build reads and writes. Version 1 wrote a todo's
|
||||
/// key and text with `{field}` placeholders; version 2 writes them as Jinja
|
||||
/// templates (`{{ field }}`). [`Document::decode`] converts a version 1
|
||||
|
|
@ -158,7 +193,8 @@ pub struct Document {
|
|||
/// leaves the document alone.
|
||||
#[serde(default = "version")]
|
||||
pub version: u32,
|
||||
/// The ids of the built-in flows that are switched off.
|
||||
/// The ids of the built-in flows that are switched off, and the keys of the
|
||||
/// archetype flows that are ([`archetype_flow_key`], [`archetype_member_key`]).
|
||||
#[serde(default)]
|
||||
pub disabled_builtins: Vec<String>,
|
||||
/// The flows the operator defined.
|
||||
|
|
@ -338,9 +374,9 @@ impl Document {
|
|||
}
|
||||
let builtin = builtins();
|
||||
for id in &self.disabled_builtins {
|
||||
if !builtin.iter().any(|b| b.id == *id) {
|
||||
if !builtin.iter().any(|b| b.id == *id) && !is_archetype_key(id) {
|
||||
return Err(format!(
|
||||
"disabled_builtins names {id}, which is no built-in flow"
|
||||
"disabled_builtins names {id}, which is no built-in flow and no archetype flow key"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -1802,4 +1838,35 @@ mod tests {
|
|||
assert!(f.validate_archetype().is_err(), "{key} {text}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The keys that switch an archetype's flow off join `disabled_builtins`;
|
||||
/// one that names nothing there is still accepted, one of no valid shape is not.
|
||||
#[test]
|
||||
fn archetype_flow_keys_are_accepted_in_the_disabled_list() {
|
||||
let flow = archetype_flow_key("triage", "new-issues");
|
||||
let member = archetype_member_key("triage", "new-issues", "alice");
|
||||
assert_eq!(flow, "archetype:triage/new-issues");
|
||||
assert_eq!(member, "archetype:triage/new-issues@alice");
|
||||
let with = |keys: &[&str]| Document {
|
||||
disabled_builtins: keys.iter().map(|k| (*k).to_owned()).collect(),
|
||||
..Document::default()
|
||||
};
|
||||
assert_eq!(with(&[&flow, &member, "closed"]).check(), Ok(()));
|
||||
assert_eq!(with(&["archetype:gone/flow"]).check(), Ok(()), "stale");
|
||||
for bad in [
|
||||
"archetype:triage",
|
||||
"archetype:/x",
|
||||
"archetype:a/",
|
||||
"archetype:a/b@",
|
||||
"archetype:a b/c",
|
||||
"archetype:a/b/c",
|
||||
"triage/new-issues",
|
||||
"no-such-builtin",
|
||||
] {
|
||||
assert!(with(&[bad]).check().is_err(), "{bad}");
|
||||
assert!(!is_archetype_key(bad), "{bad}");
|
||||
}
|
||||
// A stale key leaves the built-ins as they were.
|
||||
assert_eq!(with(&[&flow]).active().0.len(), builtins().len());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue