|
| 1 | +use crate::github; |
| 2 | +use crate::jobs::Job; |
| 3 | +use crate::zulip::BOT_EMAIL; |
| 4 | +use crate::zulip::{to_zulip_id, MembersApiResponse}; |
| 5 | +use anyhow::{format_err, Context as _}; |
| 6 | +use async_trait::async_trait; |
| 7 | +use chrono::{Duration, Utc}; |
| 8 | + |
| 9 | +pub struct TypesPlanningUpdatesJob; |
| 10 | + |
| 11 | +#[async_trait] |
| 12 | +impl Job for TypesPlanningUpdatesJob { |
| 13 | + fn name(&self) -> &'static str { |
| 14 | + "types_planning_updates" |
| 15 | + } |
| 16 | + |
| 17 | + async fn run(&self, ctx: &super::Context, _metadata: &serde_json::Value) -> anyhow::Result<()> { |
| 18 | + request_updates(ctx).await?; |
| 19 | + Ok(()) |
| 20 | + } |
| 21 | +} |
| 22 | + |
| 23 | +const TYPES_REPO: &'static str = "rust-lang/types-team"; |
| 24 | + |
| 25 | +pub async fn request_updates(ctx: &super::Context) -> anyhow::Result<()> { |
| 26 | + let gh = &ctx.github; |
| 27 | + let types_repo = gh.repository(TYPES_REPO).await?; |
| 28 | + |
| 29 | + let tracking_issues_query = github::Query { |
| 30 | + filters: vec![("state", "open"), ("is", "issue")], |
| 31 | + include_labels: vec!["roadmap-tracking-issue"], |
| 32 | + exclude_labels: vec![], |
| 33 | + }; |
| 34 | + let issues = types_repo |
| 35 | + .get_issues(&gh, &tracking_issues_query) |
| 36 | + .await |
| 37 | + .with_context(|| "Unable to get issues.")?; |
| 38 | + |
| 39 | + for issue in issues { |
| 40 | + let comments = issue.get_first100_comments(gh).await?; |
| 41 | + if comments.len() >= 100 { |
| 42 | + anyhow::bail!( |
| 43 | + "Encountered types tracking issue with 100 or more comments; needs implementation." |
| 44 | + ); |
| 45 | + } |
| 46 | + let older_than_28_days = comments |
| 47 | + .last() |
| 48 | + .map_or(true, |c| c.updated_at < (Utc::now() - Duration::days(28))); |
| 49 | + if !older_than_28_days { |
| 50 | + continue; |
| 51 | + } |
| 52 | + let mut dmed_assignee = false; |
| 53 | + for assignee in issue.assignees { |
| 54 | + let zulip_id_and_email = zulip_id_and_email(ctx, assignee.id.unwrap()).await?; |
| 55 | + let (zulip_id, email) = match zulip_id_and_email { |
| 56 | + Some(id) => id, |
| 57 | + None => continue, |
| 58 | + }; |
| 59 | + let message = format!( |
| 60 | + "Type team tracking issue needs an update. [Issue #{}]({})", |
| 61 | + issue.number, issue.html_url |
| 62 | + ); |
| 63 | + let zulip_req = crate::zulip::MessageApiRequest { |
| 64 | + recipient: crate::zulip::Recipient::Private { |
| 65 | + id: zulip_id, |
| 66 | + email: &email, |
| 67 | + }, |
| 68 | + content: &message, |
| 69 | + }; |
| 70 | + zulip_req.send(&ctx.github.raw()).await?; |
| 71 | + dmed_assignee = true; |
| 72 | + } |
| 73 | + if !dmed_assignee { |
| 74 | + let message = format!( |
| 75 | + "Type team tracking issue needs an update, and was unable to reach an assignee. \ |
| 76 | + [Issue #{}]({})", |
| 77 | + issue.number, issue.html_url |
| 78 | + ); |
| 79 | + let zulip_req = crate::zulip::MessageApiRequest { |
| 80 | + recipient: crate::zulip::Recipient::Stream { |
| 81 | + id: 144729, |
| 82 | + topic: "tracking issue updates", |
| 83 | + }, |
| 84 | + content: &message, |
| 85 | + }; |
| 86 | + zulip_req.send(&ctx.github.raw()).await?; |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + Ok(()) |
| 91 | +} |
| 92 | + |
| 93 | +async fn zulip_id_and_email( |
| 94 | + ctx: &super::Context, |
| 95 | + github_id: i64, |
| 96 | +) -> anyhow::Result<Option<(u64, String)>> { |
| 97 | + let bot_api_token = std::env::var("ZULIP_API_TOKEN").expect("ZULIP_API_TOKEN"); |
| 98 | + |
| 99 | + let members = ctx |
| 100 | + .github |
| 101 | + .raw() |
| 102 | + .get("https://rust-lang.zulipchat.com/api/v1/users") |
| 103 | + .basic_auth(BOT_EMAIL, Some(&bot_api_token)) |
| 104 | + .send() |
| 105 | + .await |
| 106 | + .map_err(|e| format_err!("Failed to get list of zulip users: {e:?}."))?; |
| 107 | + let members = members |
| 108 | + .json::<MembersApiResponse>() |
| 109 | + .await |
| 110 | + .map_err(|e| format_err!("Failed to get list of zulip users: {e:?}."))?; |
| 111 | + |
| 112 | + let zulip_id = match to_zulip_id(&ctx.github, github_id).await { |
| 113 | + Ok(Some(id)) => id as u64, |
| 114 | + Ok(None) => return Ok(None), |
| 115 | + Err(e) => anyhow::bail!("Could not find Zulip ID for GitHub id {github_id}: {e:?}"), |
| 116 | + }; |
| 117 | + |
| 118 | + let user = members.members.iter().find(|m| m.user_id == zulip_id); |
| 119 | + |
| 120 | + Ok(user.map(|m| (m.user_id, m.email.clone()))) |
| 121 | +} |
0 commit comments