actions.rs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. use chrono::{DateTime, Utc};
  2. use std::collections::HashMap;
  3. use async_trait::async_trait;
  4. use reqwest::Client;
  5. use tera::{Context, Tera};
  6. use crate::github::{self, GithubClient, Repository};
  7. #[async_trait]
  8. pub trait Action {
  9. async fn call(&self) -> String;
  10. }
  11. pub struct Step<'a> {
  12. pub name: &'a str,
  13. pub actions: Vec<Query<'a>>,
  14. }
  15. pub struct Query<'a> {
  16. pub repos: Vec<&'a str>,
  17. pub queries: Vec<QueryMap<'a>>,
  18. }
  19. pub struct QueryMap<'a> {
  20. pub name: &'a str,
  21. pub query: github::Query<'a>,
  22. }
  23. #[derive(serde::Serialize)]
  24. pub struct IssueDecorator {
  25. pub number: u64,
  26. pub title: String,
  27. pub html_url: String,
  28. pub repo_name: String,
  29. pub labels: String,
  30. pub assignees: String,
  31. pub updated_at: String,
  32. }
  33. lazy_static! {
  34. pub static ref TEMPLATES: Tera = {
  35. match Tera::new("templates/*") {
  36. Ok(t) => t,
  37. Err(e) => {
  38. println!("Parsing error(s): {}", e);
  39. ::std::process::exit(1);
  40. }
  41. }
  42. };
  43. }
  44. fn to_human(d: DateTime<Utc>) -> String {
  45. let d1 = chrono::Utc::now() - d;
  46. let days = d1.num_days();
  47. if days > 60 {
  48. format!("{} months ago", days / 30)
  49. } else {
  50. format!("about {} days ago", days)
  51. }
  52. }
  53. #[async_trait]
  54. impl<'a> Action for Step<'a> {
  55. async fn call(&self) -> String {
  56. let gh = GithubClient::new_with_default_token(Client::new());
  57. let mut context = Context::new();
  58. let mut results = HashMap::new();
  59. for Query { repos, queries } in &self.actions {
  60. for repo in repos {
  61. let repository = Repository {
  62. full_name: repo.to_string(),
  63. };
  64. for QueryMap { name, query } in queries {
  65. match query.kind {
  66. github::QueryKind::List => {
  67. let issues_search_result = repository.get_issues(&gh, &query).await;
  68. match issues_search_result {
  69. Ok(issues) => {
  70. let issues_decorator: Vec<_> = issues
  71. .iter()
  72. .map(|issue| IssueDecorator {
  73. title: issue.title.clone(),
  74. number: issue.number,
  75. html_url: issue.html_url.clone(),
  76. repo_name: repository
  77. .full_name
  78. .split("/")
  79. .last()
  80. .expect("Failed to split repository name")
  81. .to_string(),
  82. labels: issue
  83. .labels
  84. .iter()
  85. .map(|l| l.name.as_ref())
  86. .collect::<Vec<_>>()
  87. .join(", "),
  88. assignees: issue
  89. .assignees
  90. .iter()
  91. .map(|u| u.login.as_ref())
  92. .collect::<Vec<_>>()
  93. .join(", "),
  94. updated_at: to_human(issue.updated_at),
  95. })
  96. .collect();
  97. results
  98. .entry(*name)
  99. .or_insert(Vec::new())
  100. .extend(issues_decorator);
  101. }
  102. Err(err) => {
  103. eprintln!("ERROR: {}", err);
  104. err.chain()
  105. .skip(1)
  106. .for_each(|cause| eprintln!("because: {}", cause));
  107. std::process::exit(1);
  108. }
  109. }
  110. }
  111. github::QueryKind::Count => {
  112. let count = repository.get_issues_count(&gh, &query).await;
  113. match count {
  114. Ok(count) => {
  115. let result = if let Some(value) = context.get(*name) {
  116. value.as_u64().unwrap() + count as u64
  117. } else {
  118. count as u64
  119. };
  120. context.insert(*name, &result);
  121. }
  122. Err(err) => {
  123. eprintln!("ERROR: {}", err);
  124. err.chain()
  125. .skip(1)
  126. .for_each(|cause| eprintln!("because: {}", cause));
  127. std::process::exit(1);
  128. }
  129. }
  130. }
  131. };
  132. }
  133. }
  134. }
  135. for (name, issues) in &results {
  136. context.insert(*name, issues);
  137. }
  138. TEMPLATES
  139. .render(&format!("{}.tt", self.name), &context)
  140. .unwrap()
  141. }
  142. }