docs: notification flows, their API and the notifications page
This commit is contained in:
parent
0512862b3d
commit
7795da44f8
9 changed files with 140 additions and 59 deletions
|
|
@ -201,8 +201,7 @@ pub(crate) async fn put_flows(
|
|||
let (current, revision) = stored(&store).await.map_err(|e| queue_problem(&e))?;
|
||||
// An unreadable document is replaced only when it is not a version
|
||||
// this build does not know: a newer build's document is kept for it.
|
||||
if let Stored::Unreadable(why @ DecodeError::UnknownVersion(_)) = ¤t
|
||||
{
|
||||
if let Stored::Unreadable(why @ DecodeError::UnknownVersion(_)) = ¤t {
|
||||
return Err(unreadable(why));
|
||||
}
|
||||
if put.revision.is_some() && put.revision != revision {
|
||||
|
|
@ -269,16 +268,12 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn body(
|
||||
revision: Option<u64>,
|
||||
disabled: &[&str],
|
||||
flows: Vec<Flow>,
|
||||
) -> Result<Json<PutFlows>, JsonRejection> {
|
||||
Ok(Json(PutFlows {
|
||||
fn body(revision: Option<u64>, disabled: &[&str], flows: Vec<Flow>) -> Json<PutFlows> {
|
||||
Json(PutFlows {
|
||||
revision,
|
||||
disabled_builtins: disabled.iter().map(|s| (*s).to_owned()).collect(),
|
||||
flows,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -295,7 +290,7 @@ mod tests {
|
|||
|
||||
let Json(saved) = put_flows(
|
||||
State(state.clone()),
|
||||
body(None, &["unassigned"], vec![custom("triage")]),
|
||||
Ok(body(None, &["unassigned"], vec![custom("triage")])),
|
||||
)
|
||||
.await
|
||||
.expect("put");
|
||||
|
|
@ -307,18 +302,22 @@ mod tests {
|
|||
|
||||
// The stored bytes are the document the router decodes.
|
||||
let store = open(&client).await.expect("bucket");
|
||||
let bytes = store.get(notify_flows::KEY).await.expect("read").expect("stored");
|
||||
let bytes = store
|
||||
.get(notify_flows::KEY)
|
||||
.await
|
||||
.expect("read")
|
||||
.expect("stored");
|
||||
let doc = Document::decode(&bytes).expect("the router reads it");
|
||||
assert_eq!(doc.active().0.len(), 3, "2 built-ins and the custom flow");
|
||||
|
||||
// A refused save changes nothing: a flow with a cycle-free orphan.
|
||||
// A refused save changes nothing: an orphan node, and a built-in that does not exist.
|
||||
let mut orphan = custom("orphan");
|
||||
orphan.edges.clear();
|
||||
let refused = put_flows(State(state.clone()), body(None, &[], vec![orphan]))
|
||||
let refused = put_flows(State(state.clone()), Ok(body(None, &[], vec![orphan])))
|
||||
.await
|
||||
.expect_err("an orphan node");
|
||||
assert_eq!(refused.status, Some(StatusCode::BAD_REQUEST), "{refused:?}");
|
||||
let unknown = put_flows(State(state.clone()), body(None, &["nope"], vec![]))
|
||||
let unknown = put_flows(State(state.clone()), Ok(body(None, &["nope"], vec![])))
|
||||
.await
|
||||
.expect_err("no such built-in");
|
||||
assert_eq!(unknown.status, Some(StatusCode::BAD_REQUEST), "{unknown:?}");
|
||||
|
|
@ -326,14 +325,14 @@ mod tests {
|
|||
assert_eq!(after, saved);
|
||||
|
||||
// A stale revision is refused; the current one lands.
|
||||
let stale = put_flows(
|
||||
let outdated = put_flows(
|
||||
State(state.clone()),
|
||||
body(Some(revision + 100), &[], vec![]),
|
||||
Ok(body(Some(revision + 100), &[], vec![])),
|
||||
)
|
||||
.await
|
||||
.expect_err("stale");
|
||||
assert_eq!(stale.status, Some(StatusCode::CONFLICT), "{stale:?}");
|
||||
let Json(next) = put_flows(State(state.clone()), body(Some(revision), &[], vec![]))
|
||||
assert_eq!(outdated.status, Some(StatusCode::CONFLICT), "{outdated:?}");
|
||||
let Json(next) = put_flows(State(state.clone()), Ok(body(Some(revision), &[], vec![])))
|
||||
.await
|
||||
.expect("current revision");
|
||||
assert!(next.revision > Some(revision));
|
||||
|
|
@ -350,16 +349,25 @@ mod tests {
|
|||
let store = open(&client).await.expect("bucket");
|
||||
|
||||
store
|
||||
.put(notify_flows::KEY, br#"{"version":2,"flows":[]}"#.to_vec().into())
|
||||
.put(
|
||||
notify_flows::KEY,
|
||||
br#"{"version":2,"flows":[]}"#.to_vec().into(),
|
||||
)
|
||||
.await
|
||||
.expect("seed");
|
||||
let got = get_flows(State(state.clone())).await.expect_err("version 2");
|
||||
let got = get_flows(State(state.clone()))
|
||||
.await
|
||||
.expect_err("version 2");
|
||||
assert_eq!(got.status, Some(StatusCode::CONFLICT), "{got:?}");
|
||||
let refused = put_flows(State(state.clone()), body(None, &[], vec![]))
|
||||
let refused = put_flows(State(state.clone()), Ok(body(None, &[], vec![])))
|
||||
.await
|
||||
.expect_err("version 2 is kept");
|
||||
assert_eq!(refused.status, Some(StatusCode::CONFLICT), "{refused:?}");
|
||||
let kept = store.get(notify_flows::KEY).await.expect("read").expect("stored");
|
||||
let kept = store
|
||||
.get(notify_flows::KEY)
|
||||
.await
|
||||
.expect("read")
|
||||
.expect("stored");
|
||||
assert_eq!(&kept[..], br#"{"version":2,"flows":[]}"#);
|
||||
|
||||
// Control: garbage that names no version is replaced by a save.
|
||||
|
|
@ -367,7 +375,7 @@ mod tests {
|
|||
.put(notify_flows::KEY, b"garbage".to_vec().into())
|
||||
.await
|
||||
.expect("seed");
|
||||
let _ = put_flows(State(state.clone()), body(None, &["closed"], vec![]))
|
||||
let _ = put_flows(State(state.clone()), Ok(body(None, &["closed"], vec![])))
|
||||
.await
|
||||
.expect("replaces garbage");
|
||||
let Json(view) = get_flows(State(state)).await.expect("readable again");
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ use crate::{
|
|||
agent_control, agent_icon, agent_input, agent_limits, agent_login, agent_permissions,
|
||||
agent_renewal, agent_state_stream, agent_status, agents_md, alerts, archetypes, auth, builder,
|
||||
claude_login, config_pr, forge, forge_account, github_account, inference, issue_report,
|
||||
linked_accounts, matrix_account, message_stream, notify_flows, pulls, queue_identity, review_classifier,
|
||||
schedules, status, subagent_term, term_stream, wanted, webhook,
|
||||
linked_accounts, matrix_account, message_stream, notify_flows, pulls, queue_identity,
|
||||
review_classifier, schedules, status, subagent_term, term_stream, wanted, webhook,
|
||||
};
|
||||
|
||||
/// Where the daemon binds, overridable via `SWARM_CONTROLLER_SOCKET`.
|
||||
|
|
|
|||
Loading…
Reference in a new issue