use chrono::{DateTime, Duration, Local, NaiveDate};
use serde::Deserialize;
use serde_with::{DisplayFromStr, DurationSeconds, NoneAsEmptyString, serde_as};
#[derive(Debug, PartialEq, Default)]
pub struct Project {
pub name: String,
pub time_logs: Vec<TimeLog>,
pub total_spent_time: Duration,
}
impl Project {
pub fn merge(&mut self, other: Project) {
self.name = format!("{}, {}", self.name, other.name);
self.total_spent_time += other.total_spent_time;
self.time_logs.extend(other.time_logs);
}
}
#[serde_as]
#[derive(Debug, PartialEq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(rename = "Nodes")]
pub struct TimeLog {
pub spent_at: DateTime<Local>,
#[serde_as(as = "DurationSeconds<i64>")]
pub time_spent: Duration,
#[serde_as(as = "NoneAsEmptyString")]
pub summary: Option<String>,
pub user: User,
#[serde(flatten)]
pub trackable_item: TrackableItem,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, Deserialize)]
pub struct UserNodes {
#[serde(rename = "nodes")]
pub users: Vec<User>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Deserialize)]
pub struct User {
pub name: String,
pub username: String,
}
impl std::fmt::Display for User {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
#[derive(Debug, PartialEq, Default, Eq, Hash, PartialOrd, Ord, Clone)]
pub struct TrackableItem {
pub common: TrackableItemFields,
pub kind: TrackableItemKind,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub enum TrackableItemKind {
Issue(Issue),
MergeRequest(MergeRequest),
}
impl Default for TrackableItemKind {
fn default() -> Self {
Self::Issue(Issue::default())
}
}
impl std::fmt::Display for TrackableItemKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TrackableItemKind::Issue(_) => write!(f, "Issue"),
TrackableItemKind::MergeRequest(_) => write!(f, "Merge Request"),
}
}
}
#[serde_as]
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TrackableItemFields {
#[serde(rename = "iid")]
#[serde_as(as = "DisplayFromStr")]
pub id: u32,
pub title: String,
#[serde_as(as = "DurationSeconds<i64>")]
pub time_estimate: Duration,
#[serde_as(as = "DurationSeconds<i64>")]
pub total_time_spent: Duration,
pub assignees: UserNodes,
pub milestone: Option<Milestone>,
pub labels: Labels,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, Deserialize)]
pub struct Issue {}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, Deserialize)]
pub struct MergeRequest {
pub reviewers: UserNodes,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Milestone {
pub title: String,
pub due_date: Option<NaiveDate>,
}
impl std::fmt::Display for Milestone {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.title)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, Deserialize)]
pub struct Labels {
#[serde(rename = "nodes")]
pub labels: Vec<Label>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, Deserialize)]
pub struct Label {
pub title: String,
}
impl std::fmt::Display for Label {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.title)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merge_projects() {
let mut project1 = Project {
name: "Frontend".to_string(),
total_spent_time: Duration::hours(2),
time_logs: vec![],
};
project1.time_logs.push(TimeLog {
spent_at: Local::now() - Duration::days(1),
time_spent: Duration::hours(2),
..Default::default()
});
let mut project2 = Project {
name: "Backend".to_string(),
total_spent_time: Duration::hours(1),
time_logs: vec![],
};
project2.time_logs.push(TimeLog {
spent_at: Local::now() - Duration::weeks(1),
time_spent: Duration::hours(1),
..Default::default()
});
project1.merge(project2);
assert_eq!(project1.name, "Frontend, Backend");
assert_eq!(project1.total_spent_time, Duration::hours(3));
assert_eq!(project1.time_logs.len(), 2);
}
}