actions.rs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. use chrono::{DateTime, Utc};
  2. use std::collections::HashMap;
  3. use async_trait::async_trait;
  4. use reqwest::Client;
  5. use serde::{Deserialize, Serialize};
  6. use tera::{Context, Tera};
  7. use crate::github::{self, GithubClient, Repository};
  8. #[async_trait]
  9. pub trait Action {
  10. async fn call(&self) -> String;
  11. }
  12. pub struct Step<'a> {
  13. pub name: &'a str,
  14. pub actions: Vec<Query<'a>>,
  15. }
  16. pub struct Query<'a> {
  17. /// Vec of (owner, name)
  18. pub repos: Vec<(&'a str, &'a str)>,
  19. pub queries: Vec<QueryMap<'a>>,
  20. }
  21. pub enum QueryKind {
  22. List,
  23. Count,
  24. }
  25. pub struct QueryMap<'a> {
  26. pub name: &'a str,
  27. pub kind: QueryKind,
  28. pub query: Box<dyn github::IssuesQuery + Send + Sync>,
  29. }
  30. #[derive(Debug, serde::Serialize)]
  31. pub struct IssueDecorator {
  32. pub number: u64,
  33. pub title: String,
  34. pub html_url: String,
  35. pub repo_name: String,
  36. pub labels: String,
  37. pub assignees: String,
  38. // Human (readable) timestamp
  39. pub updated_at_hts: String,
  40. pub fcp_details: Option<FCPDetails>,
  41. }
  42. #[derive(Serialize, Deserialize, Debug)]
  43. pub struct FCPDetails {
  44. pub bot_tracking_comment_html_url: String,
  45. pub bot_tracking_comment_content: String,
  46. pub initiating_comment_html_url: String,
  47. pub initiating_comment_content: String,
  48. }
  49. lazy_static! {
  50. pub static ref TEMPLATES: Tera = {
  51. match Tera::new("templates/*") {
  52. Ok(t) => t,
  53. Err(e) => {
  54. println!("Parsing error(s): {}", e);
  55. ::std::process::exit(1);
  56. }
  57. }
  58. };
  59. }
  60. pub fn to_human(d: DateTime<Utc>) -> String {
  61. let d1 = chrono::Utc::now() - d;
  62. let days = d1.num_days();
  63. if days > 60 {
  64. format!("{} months ago", days / 30)
  65. } else {
  66. format!("about {} days ago", days)
  67. }
  68. }
  69. #[async_trait]
  70. impl<'a> Action for Step<'a> {
  71. async fn call(&self) -> String {
  72. let gh = GithubClient::new_with_default_token(Client::new());
  73. let mut context = Context::new();
  74. let mut results = HashMap::new();
  75. for Query { repos, queries } in &self.actions {
  76. for repo in repos {
  77. let repository = Repository {
  78. full_name: format!("{}/{}", repo.0, repo.1),
  79. };
  80. for QueryMap { name, kind, query } in queries {
  81. let issues = query.query(&repository, name == &"proposed_fcp", &gh).await;
  82. match issues {
  83. Ok(issues_decorator) => match kind {
  84. QueryKind::List => {
  85. results
  86. .entry(*name)
  87. .or_insert(Vec::new())
  88. .extend(issues_decorator);
  89. }
  90. QueryKind::Count => {
  91. let count = issues_decorator.len();
  92. let result = if let Some(value) = context.get(*name) {
  93. value.as_u64().unwrap() + count as u64
  94. } else {
  95. count as u64
  96. };
  97. context.insert(*name, &result);
  98. }
  99. },
  100. Err(err) => {
  101. eprintln!("ERROR: {}", err);
  102. err.chain()
  103. .skip(1)
  104. .for_each(|cause| eprintln!("because: {}", cause));
  105. std::process::exit(1);
  106. }
  107. }
  108. }
  109. }
  110. }
  111. for (name, issues) in &results {
  112. context.insert(*name, issues);
  113. }
  114. TEMPLATES
  115. .render(&format!("{}.tt", self.name), &context)
  116. .unwrap()
  117. }
  118. }