actions.rs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. use chrono::{DateTime, Duration, Utc};
  2. use std::collections::HashMap;
  3. use std::sync::Arc;
  4. use async_trait::async_trait;
  5. use reqwest::Client;
  6. use serde::{Deserialize, Serialize};
  7. use tera::{Context, Tera};
  8. use crate::{
  9. github::{self, GithubClient, Repository},
  10. http_client::{CompilerMeeting, HttpClient},
  11. };
  12. #[async_trait]
  13. pub trait Action {
  14. async fn call(&self) -> anyhow::Result<String>;
  15. }
  16. pub struct Step<'a> {
  17. pub name: &'a str,
  18. pub actions: Vec<Query<'a>>,
  19. }
  20. pub struct Query<'a> {
  21. /// Vec of (owner, name)
  22. pub repos: Vec<(&'a str, &'a str)>,
  23. pub queries: Vec<QueryMap<'a>>,
  24. }
  25. #[derive(Copy, Clone)]
  26. pub enum QueryKind {
  27. List,
  28. Count,
  29. }
  30. pub struct QueryMap<'a> {
  31. pub name: &'a str,
  32. pub kind: QueryKind,
  33. pub query: Arc<dyn github::IssuesQuery + Send + Sync>,
  34. }
  35. #[derive(Debug, serde::Serialize)]
  36. pub struct IssueDecorator {
  37. pub number: u64,
  38. pub title: String,
  39. pub html_url: String,
  40. pub repo_name: String,
  41. pub labels: String,
  42. pub assignees: String,
  43. // Human (readable) timestamp
  44. pub updated_at_hts: String,
  45. pub fcp_details: Option<FCPDetails>,
  46. }
  47. #[derive(Serialize, Deserialize, Debug)]
  48. pub struct FCPDetails {
  49. pub bot_tracking_comment_html_url: String,
  50. pub bot_tracking_comment_content: String,
  51. pub initiating_comment_html_url: String,
  52. pub initiating_comment_content: String,
  53. }
  54. lazy_static! {
  55. pub static ref TEMPLATES: Tera = {
  56. match Tera::new("templates/*") {
  57. Ok(t) => t,
  58. Err(e) => {
  59. println!("Parsing error(s): {}", e);
  60. ::std::process::exit(1);
  61. }
  62. }
  63. };
  64. }
  65. pub fn to_human(d: DateTime<Utc>) -> String {
  66. let d1 = chrono::Utc::now() - d;
  67. let days = d1.num_days();
  68. if days > 60 {
  69. format!("{} months ago", days / 30)
  70. } else {
  71. format!("about {} days ago", days)
  72. }
  73. }
  74. #[async_trait]
  75. impl<'a> Action for Step<'a> {
  76. async fn call(&self) -> anyhow::Result<String> {
  77. let gh = GithubClient::new_with_default_token(Client::new());
  78. // retrieve all Rust compiler meetings
  79. // from today for 7 days
  80. let today: chrono::DateTime<chrono::Local> = chrono::Local::now();
  81. let tcompiler_meetings: Vec<CompilerMeeting> =
  82. CompilerMeeting::get_meetings(today, today + Duration::days(7))
  83. .await
  84. .map_err(|e| format!("Meetings couldn't be retrieved: {:?}", e))
  85. .unwrap_or_default();
  86. let mut context = Context::new();
  87. let mut results = HashMap::new();
  88. let mut handles: Vec<tokio::task::JoinHandle<anyhow::Result<(String, QueryKind, Vec<_>)>>> =
  89. Vec::new();
  90. let semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(5));
  91. for Query { repos, queries } in &self.actions {
  92. for repo in repos {
  93. let repository = Repository {
  94. full_name: format!("{}/{}", repo.0, repo.1),
  95. // These are unused for query.
  96. default_branch: "master".to_string(),
  97. fork: false,
  98. parent: None,
  99. };
  100. for QueryMap { name, kind, query } in queries {
  101. let semaphore = semaphore.clone();
  102. let name = String::from(*name);
  103. let kind = *kind;
  104. let repository = repository.clone();
  105. let gh = gh.clone();
  106. let query = query.clone();
  107. handles.push(tokio::task::spawn(async move {
  108. let _permit = semaphore.acquire().await?;
  109. let issues = query
  110. .query(&repository, name == "proposed_fcp", &gh)
  111. .await?;
  112. Ok((name, kind, issues))
  113. }));
  114. }
  115. }
  116. }
  117. for handle in handles {
  118. let (name, kind, issues) = handle.await.unwrap()?;
  119. match kind {
  120. QueryKind::List => {
  121. results.entry(name).or_insert(Vec::new()).extend(issues);
  122. }
  123. QueryKind::Count => {
  124. let count = issues.len();
  125. let result = if let Some(value) = context.get(&name) {
  126. value.as_u64().unwrap() + count as u64
  127. } else {
  128. count as u64
  129. };
  130. context.insert(name, &result);
  131. }
  132. }
  133. }
  134. for (name, issues) in &results {
  135. context.insert(name, issues);
  136. }
  137. let date = chrono::Utc::today().format("%Y-%m-%d").to_string();
  138. context.insert("CURRENT_DATE", &date);
  139. // populate T-compiler meetings
  140. context.insert("meetings_tcompiler", &tcompiler_meetings);
  141. Ok(TEMPLATES
  142. .render(&format!("{}.tt", self.name), &context)
  143. .unwrap())
  144. }
  145. }