drawdown.rs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, Utc};
  2. use colored::Colorize;
  3. use fxhash::FxHashMap;
  4. use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
  5. use darkfi::{Error, Result};
  6. use crate::primitives::{TaskEvent, TaskInfo};
  7. // Red
  8. const NO_TASK_SCALE: u8 = 50;
  9. const MIN_SCALE: usize = 90;
  10. const MAX_SCALE: usize = 255;
  11. const INCREASE_FACTOR: usize = 25;
  12. // Green
  13. const GREEN: u8 = 40;
  14. // Blue
  15. const BLUE: u8 = 50;
  16. /// Log drawdown gets all assignees of tasks, stores a vec of stopped tasks for each
  17. /// assignee in a hashmap, draw a heatmap of how many stopped tasks in each day of the
  18. /// specified month and assignee.
  19. pub fn drawdown(date: String, tasks: Vec<TaskInfo>, assignee: Option<String>) -> Result<()> {
  20. let mut ret = FxHashMap::default();
  21. let assignees = assignees(tasks.clone());
  22. if assignee.is_none() {
  23. println!("Assignees of this month's tasks are: {}", assignees.join(", "));
  24. return Ok(())
  25. }
  26. let asgn = assignee.unwrap();
  27. if !assignees.contains(&asgn) {
  28. eprintln!("Assignee {} not found, run \"tau log -h\" for more information.", asgn);
  29. return Ok(())
  30. }
  31. for assignee in assignees {
  32. let stopped_tasks = tasks
  33. .clone()
  34. .into_iter()
  35. .filter(|task| {
  36. if task.assign.is_empty() {
  37. task.owner == asgn
  38. } else {
  39. task.assign.contains(&asgn)
  40. }
  41. })
  42. .collect::<Vec<TaskInfo>>();
  43. ret.insert(assignee, stopped_tasks);
  44. }
  45. let mut naivedate = to_naivedate(date.clone())?;
  46. println!("log drawdown for {} in {}", asgn, naivedate.format("%b %Y"));
  47. let fdow = if naivedate.month() == 2 && !is_leap_year(naivedate.year()) {
  48. [" ", "1 ", "8 ", "15", "22", " "]
  49. } else {
  50. [" ", "1 ", "8 ", "15", "22", "29"]
  51. };
  52. // Print first day of each week horizontally.
  53. let mut dow_grid =
  54. Grid::new(GridOptions { direction: Direction::LeftToRight, filling: Filling::Spaces(1) });
  55. if ret.contains_key(&asgn) {
  56. for i in fdow {
  57. let cell = Cell::from(i);
  58. dow_grid.add(cell)
  59. }
  60. let grid_display = dow_grid.fit_into_rows(1);
  61. print!("{}", grid_display);
  62. }
  63. let mut grid =
  64. Grid::new(GridOptions { direction: Direction::TopToBottom, filling: Filling::Spaces(1) });
  65. let days_in_month = get_days_from_month(date)? as u32;
  66. if ret.contains_key(&asgn) {
  67. for _ in 0..7 {
  68. let dow = naivedate.weekday().to_string();
  69. let wcell = Cell::from(dow);
  70. grid.add(wcell);
  71. naivedate += Duration::days(1);
  72. }
  73. for day in 1..=days_in_month {
  74. let owner_stopped_tasks = ret.get(&asgn).unwrap().to_owned();
  75. let date_tasks: Vec<TaskInfo> = owner_stopped_tasks
  76. .into_iter()
  77. .filter(|t| {
  78. // last event is always state stop
  79. let event_date = NaiveDateTime::from_timestamp(
  80. t.events.last().unwrap_or(&TaskEvent::default()).timestamp.0,
  81. 0,
  82. );
  83. event_date.day() == day
  84. })
  85. .collect();
  86. let red_scale = if date_tasks.is_empty() {
  87. NO_TASK_SCALE
  88. } else {
  89. ((date_tasks.len() * INCREASE_FACTOR) + MIN_SCALE).clamp(MIN_SCALE, MAX_SCALE) as u8
  90. };
  91. let cell = Cell::from("▀▀".truecolor(red_scale, GREEN, BLUE));
  92. grid.add(cell)
  93. }
  94. }
  95. let grid_display = grid.fit_into_rows(7);
  96. println!("{}", grid_display);
  97. Ok(())
  98. }
  99. fn is_leap_year(year: i32) -> bool {
  100. year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
  101. }
  102. fn helper_parse_func(date: String) -> Result<(u32, i32)> {
  103. if date.len() != 4 || date.parse::<u32>().is_err() {
  104. return Err(Error::MalformedPacket)
  105. }
  106. let (month, year) = (date[..2].parse::<u32>().unwrap(), date[2..].parse::<i32>().unwrap());
  107. let year = year + (Utc::today().year() / 100) * 100;
  108. Ok((month, year))
  109. }
  110. pub fn to_naivedate(date: String) -> Result<NaiveDate> {
  111. let (month, year) = helper_parse_func(date)?;
  112. Ok(NaiveDate::from_ymd(year, month, 1))
  113. }
  114. fn get_days_from_month(date: String) -> Result<i64> {
  115. let (month, year) = helper_parse_func(date)?;
  116. Ok(NaiveDate::from_ymd(
  117. match month {
  118. 12 => year + 1,
  119. _ => year,
  120. },
  121. match month {
  122. 12 => 1,
  123. _ => month + 1,
  124. },
  125. 1,
  126. )
  127. .signed_duration_since(NaiveDate::from_ymd(year, month, 1))
  128. .num_days())
  129. }
  130. fn assignees(tasks: Vec<TaskInfo>) -> Vec<String> {
  131. let mut assignees = vec![];
  132. for task in tasks {
  133. // if task is stopped with no assignee specified we give credit to the owner
  134. if task.assign.is_empty() {
  135. if !assignees.contains(&task.owner) {
  136. assignees.push(task.owner)
  137. }
  138. } else {
  139. for assignee in task.assign {
  140. if !assignees.contains(&assignee) {
  141. assignees.push(assignee)
  142. }
  143. }
  144. }
  145. }
  146. assignees
  147. }