use crate::{
monitor::{duration, plain},
store::{Request, RequestView},
};
use serde_json::Value;
use time::OffsetDateTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stage {
Claim,
Review,
Completed,
}
impl Stage {
pub fn label(self) -> &'static str {
match self {
Stage::Claim => "CLAIM",
Stage::Review => "REVIEW (yours)",
Stage::Completed => "completed",
}
}
}
pub fn meta(view: &RequestView, reviewer: &str, now: OffsetDateTime) -> String {
let request = &view.request;
let waiting = request.status().is_human_wait();
let mut parts = vec![
format!("Request {}", request.id),
request.status().to_string(),
];
if let Some(error) = &request.error() {
let first = error.lines().next().unwrap_or_default();
parts.push(match &request.error_code() {
Some(code) => format!("[{code}] {first}"),
None => first.to_owned(),
});
}
match &view.claim {
None if waiting => parts.push("unclaimed".into()),
None => {}
Some(claim) if claim.reviewer == reviewer => {
parts.push(format!("claimed by you ({reviewer})"))
}
Some(claim) => parts.push(format!("claimed by {} · read-only", claim.reviewer)),
}
if waiting {
parts.push(format!(
"waiting {}",
duration(now - request.created_at.time())
));
}
plain(&parts.join(" · ")).into_owned()
}
pub fn shared(view: &RequestView) -> Option<String> {
let source = view
.execution
.as_ref()
.map(|execution| &execution.provenance)?;
(source.request_id != view.request.id).then(|| {
let text = format!(
"actions go to {} in {}",
source.request_id,
crate::platform::path_text(&source.repo_path)
);
plain(&text).into_owned()
})
}
fn schema(schema: Option<&Value>) -> String {
schema.map_or("none (no owner fields)".into(), |schema| {
serde_json::to_string_pretty(schema).unwrap_or_default()
})
}
pub fn technical(view: &RequestView, reviewer: &str) -> Vec<(&'static str, String)> {
let request = &view.request;
let definition = request.human_definition.as_ref();
let declaration = definition
.and_then(|definition| definition.eval.declaration.value())
.map(|declaration| serde_json::to_value(declaration).expect("saved declaration is JSON"));
let status = match (&request.error(), &request.error_code()) {
(Some(error), Some(code)) => format!("{} [{code}] {error}", request.status()),
(Some(error), None) => format!("{} {error}", request.status()),
_ => request.status().to_string(),
};
let claim = match &view.claim {
None if request.status().is_human_wait() => {
format!("unclaimed; c claims it for {reviewer}")
}
None => "none".into(),
Some(claim) if claim.reviewer == reviewer => {
format!("claimed by you ({reviewer}) at {}", claim.claimed_at)
}
Some(claim) => format!(
"claimed by {} at {}; read-only",
claim.reviewer, claim.claimed_at
),
};
let mut fields = vec![
("Request", request.id.to_string()),
("Run", request.run_id.to_string()),
(
"Repository",
definition
.and_then(|definition| definition.repo.value())
.map(|repo| crate::platform::path_text(repo))
.unwrap_or_else(|| "-".into()),
),
("Status", status),
("Claim", claim),
];
if let Some(shared) = shared(view) {
fields.push(("Shared", shared));
}
fields.push(("Created", request.created_at.to_string()));
let owner = |key| {
schema(
declaration
.as_ref()
.and_then(|declaration| declaration.get(key)),
)
};
fields.push(("GREEN fields", owner("passSchema")));
fields.push(("RED fields", owner("failSchema")));
fields
}
pub fn stage(settled: bool, mine: bool) -> Stage {
if settled {
Stage::Completed
} else if mine {
Stage::Review
} else {
Stage::Claim
}
}
pub fn outcome_text(request: &Request) -> String {
request.result().map_or_else(
|| request.error().map(str::to_owned).unwrap_or_default(),
|result| serde_json::to_string_pretty(result).unwrap_or_default(),
)
}
#[cfg(test)]
mod tests {
use super::meta;
use crate::test_request::request;
use serde_json::json;
#[test]
fn human_detail_metadata_identifies_waiting_and_completed_requests() {
for (status, expected) in [
(
"WAITING_HUMAN",
"Request run-1-app-signoff · WAITING_HUMAN · unclaimed · waiting 0s",
),
("GREEN", "Request run-1-app-signoff · GREEN"),
] {
let view = request(
"app/signoff",
status,
json!({
"profile":{"kind":"human"},
"requestedProfile":{"kind":"human"}
}),
);
assert_eq!(
meta(&view, "alice", view.request.created_at.time()),
expected
);
}
}
}