drawdown.rs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::collections::HashMap;
  19. use chrono::{Datelike, Duration, NaiveDate, Utc};
  20. use colored::Colorize;
  21. use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
  22. use darkfi::{util::time::DateTime, Error, Result};
  23. use crate::primitives::{TaskEvent, TaskInfo};
  24. // Red
  25. const NO_TASK_SCALE: u8 = 50;
  26. const MIN_SCALE: usize = 90;
  27. const MAX_SCALE: usize = 255;
  28. const INCREASE_FACTOR: usize = 25;
  29. // Green
  30. const GREEN: u8 = 40;
  31. // Blue
  32. const BLUE: u8 = 50;
  33. /// Log drawdown gets all assignees of tasks, stores a vec of stopped tasks for each
  34. /// assignee in a hashmap, draw a heatmap of how many stopped tasks in each day of the
  35. /// specified month and assignee.
  36. pub fn drawdown(date: String, tasks: Vec<TaskInfo>, assignee: Option<String>) -> Result<()> {
  37. let mut ret = HashMap::new();
  38. let assignees = assignees(tasks.clone());
  39. if assignee.is_none() {
  40. println!("Assignees of this month's tasks are: {}", assignees.join(", "));
  41. return Ok(())
  42. }
  43. let asgn = assignee.unwrap();
  44. if !assignees.contains(&asgn) {
  45. eprintln!("Assignee {} not found, run \"tau log -h\" for more information.", asgn);
  46. return Ok(())
  47. }
  48. for assignee in assignees {
  49. let stopped_tasks = tasks
  50. .clone()
  51. .into_iter()
  52. .filter(|task| {
  53. if task.assign.is_empty() {
  54. task.owner == asgn
  55. } else {
  56. task.assign.contains(&asgn)
  57. }
  58. })
  59. .collect::<Vec<TaskInfo>>();
  60. ret.insert(assignee, stopped_tasks);
  61. }
  62. let mut naivedate = to_naivedate(date.clone())?;
  63. println!("log drawdown for {} in {}", asgn, naivedate.format("%b %Y"));
  64. let fdow = if naivedate.month() == 2 && !is_leap_year(naivedate.year()) {
  65. [" ", "1 ", "8 ", "15", "22", " "]
  66. } else {
  67. [" ", "1 ", "8 ", "15", "22", "29"]
  68. };
  69. // Print first day of each week horizontally.
  70. let mut dow_grid =
  71. Grid::new(GridOptions { direction: Direction::LeftToRight, filling: Filling::Spaces(1) });
  72. if ret.contains_key(&asgn) {
  73. for i in fdow {
  74. let cell = Cell::from(i);
  75. dow_grid.add(cell)
  76. }
  77. let grid_display = dow_grid.fit_into_rows(1);
  78. print!("{}", grid_display);
  79. }
  80. let mut grid =
  81. Grid::new(GridOptions { direction: Direction::TopToBottom, filling: Filling::Spaces(1) });
  82. let days_in_month = get_days_from_month(date)? as u32;
  83. if ret.contains_key(&asgn) {
  84. for _ in 0..7 {
  85. let dow = naivedate.weekday().to_string();
  86. let wcell = Cell::from(dow);
  87. grid.add(wcell);
  88. naivedate += Duration::days(1);
  89. }
  90. for day in 1..=days_in_month {
  91. let owner_stopped_tasks = ret.get(&asgn).unwrap().to_owned();
  92. let date_tasks: Vec<TaskInfo> = owner_stopped_tasks
  93. .into_iter()
  94. .filter(|t| {
  95. // last event is always state stop
  96. let event_date = DateTime::from_timestamp(
  97. t.events.last().unwrap_or(&TaskEvent::default()).timestamp.0,
  98. 0,
  99. );
  100. // let event_date = Utc.timestamp_nanos(
  101. // t.events
  102. // .last()
  103. // .unwrap_or(&TaskEvent::default())
  104. // .timestamp
  105. // .0
  106. // .try_into()
  107. // .unwrap(),
  108. // );
  109. event_date.day == day
  110. })
  111. .collect();
  112. let red_scale = if date_tasks.is_empty() {
  113. NO_TASK_SCALE
  114. } else {
  115. ((date_tasks.len() * INCREASE_FACTOR) + MIN_SCALE).clamp(MIN_SCALE, MAX_SCALE) as u8
  116. };
  117. let cell = Cell::from("▀▀".truecolor(red_scale, GREEN, BLUE));
  118. grid.add(cell)
  119. }
  120. }
  121. let grid_display = grid.fit_into_rows(7);
  122. println!("{}", grid_display);
  123. Ok(())
  124. }
  125. fn is_leap_year(year: i32) -> bool {
  126. year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
  127. }
  128. fn helper_parse_func(date: String) -> Result<(u32, i32)> {
  129. if date.len() != 4 || date.parse::<u32>().is_err() {
  130. return Err(Error::MalformedPacket)
  131. }
  132. let (month, year) = (date[..2].parse::<u32>().unwrap(), date[2..].parse::<i32>().unwrap());
  133. let year = year + (Utc::now().year() / 100) * 100;
  134. Ok((month, year))
  135. }
  136. pub fn to_naivedate(date: String) -> Result<NaiveDate> {
  137. let (month, year) = helper_parse_func(date)?;
  138. Ok(NaiveDate::from_ymd_opt(year, month, 1).unwrap())
  139. }
  140. fn get_days_from_month(date: String) -> Result<i64> {
  141. let (month, year) = helper_parse_func(date)?;
  142. Ok(NaiveDate::from_ymd_opt(
  143. match month {
  144. 12 => year + 1,
  145. _ => year,
  146. },
  147. match month {
  148. 12 => 1,
  149. _ => month + 1,
  150. },
  151. 1,
  152. )
  153. .unwrap()
  154. .signed_duration_since(NaiveDate::from_ymd_opt(year, month, 1).unwrap())
  155. .num_days())
  156. }
  157. fn assignees(tasks: Vec<TaskInfo>) -> Vec<String> {
  158. let mut assignees = vec![];
  159. for task in tasks {
  160. // if task is stopped with no assignee specified we give credit to the owner
  161. if task.assign.is_empty() {
  162. if !assignees.contains(&task.owner) {
  163. assignees.push(task.owner)
  164. }
  165. } else {
  166. for assignee in task.assign {
  167. if !assignees.contains(&assignee) {
  168. assignees.push(assignee)
  169. }
  170. }
  171. }
  172. }
  173. assignees
  174. }