actions.rs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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. // This is unused for query.
  96. default_branch: "master".to_string(),
  97. };
  98. for QueryMap { name, kind, query } in queries {
  99. let semaphore = semaphore.clone();
  100. let name = String::from(*name);
  101. let kind = *kind;
  102. let repository = repository.clone();
  103. let gh = gh.clone();
  104. let query = query.clone();
  105. handles.push(tokio::task::spawn(async move {
  106. let _permit = semaphore.acquire().await?;
  107. let issues = query
  108. .query(&repository, name == "proposed_fcp", &gh)
  109. .await?;
  110. Ok((name, kind, issues))
  111. }));
  112. }
  113. }
  114. }
  115. for handle in handles {
  116. let (name, kind, issues) = handle.await.unwrap()?;
  117. match kind {
  118. QueryKind::List => {
  119. results.entry(name).or_insert(Vec::new()).extend(issues);
  120. }
  121. QueryKind::Count => {
  122. let count = issues.len();
  123. let result = if let Some(value) = context.get(&name) {
  124. value.as_u64().unwrap() + count as u64
  125. } else {
  126. count as u64
  127. };
  128. context.insert(name, &result);
  129. }
  130. }
  131. }
  132. for (name, issues) in &results {
  133. context.insert(name, issues);
  134. }
  135. let date = chrono::Utc::today().format("%Y-%m-%d").to_string();
  136. context.insert("CURRENT_DATE", &date);
  137. // populate T-compiler meetings
  138. context.insert("meetings_tcompiler", &tcompiler_meetings);
  139. Ok(TEMPLATES
  140. .render(&format!("{}.tt", self.name), &context)
  141. .unwrap())
  142. }
  143. }