use pinto::backlog::{BacklogItem, ItemId};
use pinto::service::{Board, Burndown, CycleTimeReport, DurationSummary, ItemDetail};
use pinto::sprint::{Sprint, SprintCapacity};
use serde::Serialize;
#[derive(Debug, Serialize)]
struct ItemJson {
id: String,
title: String,
status: String,
rank: String,
points: Option<u32>,
labels: Vec<String>,
assignee: Option<String>,
sprint: Option<String>,
parent: Option<String>,
depends_on: Vec<String>,
start_at: Option<String>,
done_at: Option<String>,
commits: Vec<String>,
created: String,
updated: String,
body: String,
}
impl ItemJson {
fn from_item(item: &BacklogItem) -> Self {
Self {
id: item.id.to_string(),
title: item.title.clone(),
status: item.status.to_string(),
rank: item.rank.to_string(),
points: item.points,
labels: item.labels.clone(),
assignee: item.assignee.clone(),
sprint: item.sprint.clone(),
parent: item.parent.as_ref().map(ItemId::to_string),
depends_on: item.depends_on.iter().map(ItemId::to_string).collect(),
start_at: item.start_at.map(|d| d.to_rfc3339()),
done_at: item.done_at.map(|d| d.to_rfc3339()),
commits: item.commits.clone(),
created: item.created.to_rfc3339(),
updated: item.updated.to_rfc3339(),
body: item.body.clone(),
}
}
}
#[derive(Debug, Serialize)]
struct DetailJson {
#[serde(flatten)]
item: ItemJson,
rank_ordinal: usize,
children: Vec<String>,
dependents: Vec<String>,
}
#[derive(Debug, Serialize)]
struct ColumnJson {
status: String,
items: Vec<ItemJson>,
}
#[derive(Debug, Serialize)]
struct BoardJson {
columns: Vec<ColumnJson>,
orphaned: Vec<ItemJson>,
}
#[derive(Debug, Serialize)]
struct SprintJson {
id: String,
title: String,
state: String,
goal: String,
start: Option<String>,
end: Option<String>,
created: String,
updated: String,
}
impl SprintJson {
fn from_sprint(sprint: &Sprint) -> Self {
Self {
id: sprint.id.to_string(),
title: sprint.title.clone(),
state: sprint.state.to_string(),
goal: sprint.goal.clone(),
start: sprint.start.map(|d| d.to_rfc3339()),
end: sprint.end.map(|d| d.to_rfc3339()),
created: sprint.created.to_rfc3339(),
updated: sprint.updated.to_rfc3339(),
}
}
}
#[derive(Debug, Serialize)]
struct SprintCapacityJson {
working_days: u32,
hours: f64,
}
pub(super) fn sprint_capacity_json(capacity: &SprintCapacity) -> serde_json::Result<String> {
serde_json::to_string_pretty(&SprintCapacityJson {
working_days: capacity.working_days,
hours: capacity.hours,
})
}
pub(super) fn list_json(items: &[BacklogItem]) -> serde_json::Result<String> {
let dto: Vec<ItemJson> = items.iter().map(ItemJson::from_item).collect();
serde_json::to_string_pretty(&dto)
}
pub(super) fn detail_json(details: &[ItemDetail]) -> serde_json::Result<String> {
let dto: Vec<DetailJson> = details
.iter()
.map(|detail| DetailJson {
item: ItemJson::from_item(&detail.item),
rank_ordinal: detail.rank_ordinal,
children: detail.children.iter().map(ItemId::to_string).collect(),
dependents: detail.dependents.iter().map(ItemId::to_string).collect(),
})
.collect();
serde_json::to_string_pretty(&dto)
}
pub(super) fn board_json(board: &Board) -> serde_json::Result<String> {
let dto = BoardJson {
columns: board
.columns
.iter()
.map(|col| ColumnJson {
status: col.status.to_string(),
items: col.items.iter().map(ItemJson::from_item).collect(),
})
.collect(),
orphaned: board.orphaned.iter().map(ItemJson::from_item).collect(),
};
serde_json::to_string_pretty(&dto)
}
pub(super) fn sprints_json(sprints: &[Sprint]) -> serde_json::Result<String> {
let dto: Vec<SprintJson> = sprints.iter().map(SprintJson::from_sprint).collect();
serde_json::to_string_pretty(&dto)
}
#[derive(Debug, Serialize)]
struct BurndownDayJson {
date: String,
remaining: u32,
ideal: f64,
}
#[derive(Debug, Serialize)]
struct BurndownJson {
sprint_id: String,
sprint_title: String,
metric: String,
total: u32,
days: Vec<BurndownDayJson>,
}
#[derive(Debug, Serialize)]
struct DurationSummaryJson {
count: usize,
mean_seconds: i64,
median_seconds: i64,
min_seconds: i64,
max_seconds: i64,
}
impl DurationSummaryJson {
fn from_summary(s: &DurationSummary) -> Self {
Self {
count: s.count,
mean_seconds: s.mean.num_seconds(),
median_seconds: s.median.num_seconds(),
min_seconds: s.min.num_seconds(),
max_seconds: s.max.num_seconds(),
}
}
}
#[derive(Debug, Serialize)]
struct CycleTimeJson {
completed: usize,
cycle: Option<DurationSummaryJson>,
lead: Option<DurationSummaryJson>,
missing_start: Vec<String>,
}
pub(super) fn cycletime_json(report: &CycleTimeReport) -> serde_json::Result<String> {
let dto = CycleTimeJson {
completed: report.completed,
cycle: report.cycle.as_ref().map(DurationSummaryJson::from_summary),
lead: report.lead.as_ref().map(DurationSummaryJson::from_summary),
missing_start: report.missing_start.iter().map(ItemId::to_string).collect(),
};
serde_json::to_string_pretty(&dto)
}
pub(super) fn burndown_json(chart: &Burndown) -> serde_json::Result<String> {
let dto = BurndownJson {
sprint_id: chart.sprint_id.to_string(),
sprint_title: chart.sprint_title.clone(),
metric: chart.metric.as_str().to_string(),
total: chart.total,
days: chart
.days
.iter()
.map(|d| BurndownDayJson {
date: d.date.to_string(),
remaining: d.remaining,
ideal: d.ideal,
})
.collect(),
};
serde_json::to_string_pretty(&dto)
}