#[allow(clippy::wildcard_imports)]
use super::*;
pub(super) fn project_refusal(error: crate::projects::ProjectError) -> Value {
error_result(crate::error::render_error(&crate::error::TixError::from(
error,
)))
}
pub(super) fn epic_refusal(error: crate::epics::EpicError) -> Value {
error_result(crate::error::render_error(&crate::error::TixError::from(
error,
)))
}
pub(super) fn epic_edit_requires_a_field() -> String {
crate::error::render_error(&crate::error::TixError::InvalidInput(
"no fields to change; pass at least one of --name, --description, --status, \
--owner, --start-date, --due-date, --weight, --parent, --projects, \
--milestone or --cycle"
.to_string(),
))
}
pub(super) fn epic_weight_parse_error(text: &str) -> String {
crate::error::render_error(&crate::error::TixError::InvalidInput(format!(
"{}: epic weight must be an integer; got `{text}`",
crate::epics::EpicDiagnosticKind::InvalidWeight
)))
}
pub(super) fn epic_create_weight(args: &Value, key: &str) -> ToolOutcome2<Option<i64>> {
match args.get(key) {
None | Some(Value::Null) => Ok(None),
Some(Value::Number(number)) => match number.as_i64() {
Some(value) => Ok(Some(value)),
None => Err(refuse(epic_weight_parse_error(&number.to_string()))),
},
Some(Value::String(text)) if text.trim().is_empty() => Ok(None),
Some(Value::String(text)) => match text.trim().parse::<i64>() {
Ok(value) => Ok(Some(value)),
Err(_) => Err(refuse(epic_weight_parse_error(text))),
},
Some(_) => Err(proto()),
}
}
pub(super) fn epic_weight_patch(args: &Value, key: &str) -> ToolOutcome2<crate::epics::Patch<i64>> {
use crate::epics::Patch;
match args.get(key) {
None | Some(Value::Null) => Ok(Patch::Unchanged),
Some(Value::Number(number)) => number
.as_i64()
.map(Patch::Set)
.ok_or_else(|| refuse(epic_weight_parse_error(&number.to_string()))),
Some(Value::String(text)) if text.trim().is_empty() => Ok(Patch::Clear),
Some(Value::String(text)) => text
.trim()
.parse::<i64>()
.map(Patch::Set)
.map_err(|_| refuse(epic_weight_parse_error(text))),
Some(_) => Err(proto()),
}
}
pub(super) fn epic_member_value(member: &crate::epics::MemberRef) -> Value {
json!({ "issue": member.issue.number(), "display": member.display })
}
pub(super) fn epic_ref_value(reference: &crate::epics::EpicRef) -> Value {
json!({
"id": reference.id.as_str(),
"name": reference.name,
"display": reference.display,
})
}
pub(super) fn epic_missing_value(missing: &crate::epics::EpicReferenceResolution) -> Value {
json!({
"holder": missing.holder.to_string(),
"kind": missing.target_kind.as_str(),
"diagnostic_kind": missing.target_kind.diagnostic_kind(),
"target": missing.target,
"reason": missing.reason,
})
}
pub(super) fn epic_record_value(epic: &crate::epics::Epic) -> Value {
json!({
"id": epic.id.as_str(),
"name": epic.name,
"description": epic.description,
"status": epic.status,
"owner": epic.owner,
"start_date": epic.start_date,
"due_date": epic.due_date,
"weight": epic.weight,
"parent": epic.parent,
"projects": epic.projects,
"milestone": epic.milestone,
"cycle": epic.cycle,
"member_count": epic.member_count,
"child_count": epic.child_count,
"children": epic.children.iter().map(epic_ref_value).collect::<Vec<Value>>(),
"members": epic.members.iter().map(epic_member_value).collect::<Vec<Value>>(),
})
}
pub(super) fn weighted_progress_value(weighted: &crate::epics::WeightedProgress) -> Value {
json!({
"own_weight": weighted.own_weight,
"weighted_children": weighted.weighted_children,
"unweighted_children": weighted.unweighted_children,
"done_weight": weighted.done_weight,
"total_weight": weighted.total_weight,
"percent": weighted.percent,
})
}
pub(super) fn epic_rollup_value(rollup: &crate::epics::EpicRollup) -> Value {
json!({
"epic_id": rollup.epic_id.as_str(),
"name": rollup.name,
"done": rollup.progress.done,
"total": rollup.progress.total,
"progress": { "done": rollup.progress.done, "total": rollup.progress.total },
"child_count": rollup.child_count,
"weighted_progress": weighted_progress_value(&rollup.weighted_progress),
"blocked": rollup.blocked,
"blocked_members": rollup
.blocked_members
.iter()
.map(|member| json!({
"issue": member.issue.number(),
"display": member.display,
"reason": member.reason,
}))
.collect::<Vec<Value>>(),
"missing_references": rollup
.missing_references
.iter()
.map(epic_missing_value)
.collect::<Vec<Value>>(),
})
}
pub(super) fn epic_hierarchy_value(hierarchy: &crate::epics::EpicHierarchy) -> Value {
json!({
"epic": hierarchy.epic.as_str(),
"recorded_parent": hierarchy.recorded_parent,
"parent": hierarchy.parent.as_ref().map(epic_ref_value),
"children": hierarchy.children.iter().map(epic_ref_value).collect::<Vec<Value>>(),
"child_count": hierarchy.child_count,
"ancestors": hierarchy.ancestors.iter().map(epic_ref_value).collect::<Vec<Value>>(),
"depth": hierarchy.depth,
"unresolved": hierarchy.unresolved.as_ref().map(|unresolved| json!({
"epic": unresolved.epic.as_str(),
"recorded_parent": unresolved.recorded_parent,
"reason": unresolved.reason,
})),
})
}
pub(super) fn epic_health_text(rollup: &crate::epics::EpicRollup) -> String {
format!(
"{} {}/{} done, blocked {}, missing-references {}",
crate::epics::epic_display(&rollup.epic_id, &rollup.name),
rollup.progress.done,
rollup.progress.total,
rollup.blocked,
rollup.missing_references.len(),
)
}
pub(super) fn epic_reference_targets(
config: &Config,
store: &Store,
) -> TixResult<crate::epics::ReferenceTargets> {
let root = Config::project_root(&store.issues_dir).unwrap_or_else(|| store.issues_dir.clone());
let planner = crate::planning::Planner::from_config(config.clone(), root);
crate::epics::ReferenceTargets::from_planner(&planner)
}
pub(super) fn epic_reference_outcome_value(
outcome: &crate::storage::BulkReferenceOutcome,
) -> Value {
match &outcome.outcome {
Ok(applied) => json!({
"selector": outcome.selector,
"changed": applied.changed,
"epic_ids": applied.epic_ids,
"written": applied.written.as_ref().map(|issue| issue.filename.clone()),
}),
Err(error) => json!({
"selector": outcome.selector,
"error": crate::error::render_error(error),
}),
}
}
pub(super) fn wip_value(decision: &crate::wip::WipDecision) -> Value {
json!({
"kind": decision.as_str(),
"from": decision.from,
"to": decision.to,
"before": decision.state_before.as_str(),
"after": decision.state_after.as_str(),
"limit": decision.limit.as_ref().map(|limit| json!({
"column": decision.to,
"max_items": limit.max_items,
"max_estimate": limit.max_estimate,
"unit": limit.unit,
"policy": limit.policy.map(|policy| policy.as_str()),
"enabled": limit.enabled,
})),
"usage": decision.usage.as_ref().map(|usage| json!({
"cell": usage.cell(),
"items": usage.items,
"estimate": usage.estimate,
})),
"diagnostics": decision
.diagnostics
.iter()
.map(|diagnostic| json!({
"kind": diagnostic.kind.as_str(),
"message": diagnostic.message,
}))
.collect::<Vec<_>>(),
"history": decision.history.as_ref().map(|intent| json!({
"kind": intent.kind.as_str(),
"actor": intent.actor,
"timestamp": intent.timestamp,
"detail": intent.detail,
})),
})
}
pub(super) fn wip_result(
text: String,
decision: &crate::wip::WipDecision,
is_error: bool,
) -> Value {
json!({
"content": [{ "type": "text", "text": text }],
"structuredContent": { "wip": wip_value(decision) },
"isError": is_error,
})
}