Skip to content

Commit

Permalink
Open thread a week prior to meeting - post update on issues without u…
Browse files Browse the repository at this point in the history
…pdates the next thursday
  • Loading branch information
jackh726 committed Nov 5, 2023
1 parent 40a23ae commit f18d933
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 16 deletions.
19 changes: 15 additions & 4 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,16 +187,27 @@ pub async fn schedule_jobs(db: &DbClient, jobs: Vec<JobSchedule>) -> anyhow::Res
let mut upcoming = job.schedule.upcoming(Utc).take(1);

if let Some(scheduled_at) = upcoming.next() {
if let Err(_) = get_job_by_name_and_scheduled_at(&db, job.name, &scheduled_at).await {
// mean there's no job already in the db with that name and scheduled_at
insert_job(&db, job.name, &scheduled_at, &job.metadata).await?;
}
schedule_job(db, job.name, job.metadata, scheduled_at).await?;
}
}

Ok(())
}

pub async fn schedule_job(db: &DbClient, job_name: &str, job_metadata: serde_json::Value, when: chrono::DateTime<Utc>) -> anyhow::Result<()> {
let all_jobs = jobs();
if !all_jobs.iter().any(|j| j.name() == job_name) {
anyhow::bail!("Job {} does not exist in the current job list.", job_name);
}

if let Err(_) = get_job_by_name_and_scheduled_at(&db, job_name, &when).await {
// mean there's no job already in the db with that name and scheduled_at
insert_job(&db, job_name, &when, &job_metadata).await?;
}

Ok(())
}

pub async fn run_scheduled_jobs(ctx: &Context, db: &DbClient) -> anyhow::Result<()> {
let jobs = get_jobs_to_execute(&db).await.unwrap();
tracing::trace!("jobs to execute: {:#?}", jobs);
Expand Down
2 changes: 1 addition & 1 deletion src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ mod review_submitted;
mod rfc_helper;
pub mod rustc_commits;
mod shortcut;
mod types_planning_updates;
pub mod types_planning_updates;

pub async fn handle(ctx: &Context, event: &Event) -> Vec<HandlerError> {
let config = config::get(&ctx.github, event.repo()).await;
Expand Down
91 changes: 81 additions & 10 deletions src/handlers/types_planning_updates.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,76 @@
use crate::db::schedule_job;
use crate::github;
use crate::zulip::{MembersApiResponse, to_zulip_id};
use crate::zulip::BOT_EMAIL;
use crate::jobs::Job;
use anyhow::{format_err, Context as _};
use async_trait::async_trait;
use chrono::{Utc, Duration};
use chrono::{Utc, Duration, Datelike, NaiveTime, TimeZone};
use serde::{Deserialize, Serialize};

pub struct TypesPlanningUpdatesJob;

const TYPES_REPO: &'static str = "rust-lang/types-team";

pub struct TypesPlanningMeetingThreadOpenJob;

#[async_trait]
impl Job for TypesPlanningUpdatesJob {
impl Job for TypesPlanningMeetingThreadOpenJob {
fn name(&self) -> &'static str {
"types_planning_updates"
"types_planning_meeting_thread_open"
}

async fn run(&self, ctx: &super::Context, _metadata: &serde_json::Value) -> anyhow::Result<()> {
request_updates(ctx).await?;
// On the last week of the month, we open a thread on zulip for the next Monday
let today = chrono::Utc::now().date().naive_utc();
let first_monday = today + chrono::Duration::days(7);
let meeting_date_string = first_monday.format("%Y-%m-%d").to_string();
let message = format!("\
Hello @*T-types/meetings*. Monthly planning meeting in one week.\n\
This is a reminder to update the current [roadmap tracking issues](https://github.com/rust-lang/types-team/issues?q=is%3Aissue+is%3Aopen+label%3Aroadmap-tracking-issue).\n\
Extra reminders will be sent later this week.");
let zulip_req = crate::zulip::MessageApiRequest {
recipient: crate::zulip::Recipient::Stream { id: 326132, topic: &format!("{meeting_date_string} planning meeting") },
content: &message,
};
zulip_req.send(&ctx.github.raw()).await?;

// Then, we want to schedule the next Thursday after this
let mut thursday = today;
while thursday.weekday().num_days_from_monday() != 3 {
thursday = thursday.succ();
}
let thursday_at_noon = Utc.from_utc_datetime(&thursday.and_time(NaiveTime::from_hms(12, 0, 0)));
let metadata = serde_json::value::to_value(PlanningMeetingUpdatesPingMetadata {
date_string: meeting_date_string,
}).unwrap();
schedule_job(&*ctx.db.get().await, TypesPlanningMeetingUpdatesPing.name(), metadata, thursday_at_noon).await?;

Ok(())
}
}

const TYPES_REPO: &'static str = "rust-lang/types-team";
#[derive(Serialize, Deserialize)]
pub struct PlanningMeetingUpdatesPingMetadata {
pub date_string: String,
}

pub struct TypesPlanningMeetingUpdatesPing;

#[async_trait]
impl Job for TypesPlanningMeetingUpdatesPing {
fn name(&self) -> &'static str {
"types_planning_meeting_updates_ping"
}

async fn run(&self, ctx: &super::Context, metadata: &serde_json::Value) -> anyhow::Result<()> {
let metadata = serde_json::from_value(metadata.clone())?;
// On the thursday before the first monday, we want to ping for updates
request_updates(ctx, metadata).await?;
Ok(())
}
}

pub async fn request_updates(ctx: &super::Context) -> anyhow::Result<()> {
pub async fn request_updates(ctx: &super::Context, metadata: PlanningMeetingUpdatesPingMetadata) -> anyhow::Result<()> {
let gh = &ctx.github;
let types_repo = gh.repository(TYPES_REPO).await?;

Expand All @@ -36,15 +84,24 @@ pub async fn request_updates(ctx: &super::Context) -> anyhow::Result<()> {
.await
.with_context(|| "Unable to get issues.")?;

let mut issues_needs_updates = vec![];
for issue in issues {
// Github doesn't have a nice way to get the *last* comment; we would have to paginate all comments to get it.
// For now, just bail out if there are more than 100 comments (if this ever becomes a problem, we will have to fix).
let comments = issue.get_first100_comments(gh).await?;
if comments.len() >= 100 {
anyhow::bail!("Encountered types tracking issue with 100 or more comments; needs implementation.");
}
let older_than_28_days = comments.last().map_or(true, |c| c.updated_at < (Utc::now() - Duration::days(28)));
if !older_than_28_days {

// If there are any comments in the past 7 days, we consider this "updated". We *could* be more clever, but
// this is fine under the assumption that tracking issues should only contain updates.
let older_than_7_days = comments.last().map_or(true, |c| c.updated_at < (Utc::now() - Duration::days(7)));
if !older_than_7_days {
continue;
}
// In the future, we should reach out to specific people in charge of specific issues. For now, because our tracking
// method is crude and will over-estimate the issues that need updates.
/*
let mut dmed_assignee = false;
for assignee in issue.assignees {
let zulip_id_and_email = zulip_id_and_email(ctx, assignee.id.unwrap()).await?;
Expand All @@ -69,11 +126,25 @@ pub async fn request_updates(ctx: &super::Context) -> anyhow::Result<()> {
};
zulip_req.send(&ctx.github.raw()).await?;
}
*/
issues_needs_updates.push(format!("- [Issue #{}]({})", issue.number, issue.html_url));
}

let issue_list = issues_needs_updates.join("\n");

let message = format!("The following issues still need updates:\n\n{issue_list}");

let meeting_date_string = metadata.date_string;
let zulip_req = crate::zulip::MessageApiRequest {
recipient: crate::zulip::Recipient::Stream { id: 326132, topic: &format!("{meeting_date_string} planning meeting") },
content: &message,
};
zulip_req.send(&ctx.github.raw()).await?;

Ok(())
}

#[allow(unused)] // Needed for commented out bit above
async fn zulip_id_and_email(ctx: &super::Context, github_id: i64) -> anyhow::Result<Option<(u64, String)>> {
let bot_api_token = std::env::var("ZULIP_API_TOKEN").expect("ZULIP_API_TOKEN");

Expand Down Expand Up @@ -102,4 +173,4 @@ async fn zulip_id_and_email(ctx: &super::Context, github_id: i64) -> anyhow::Res
.find(|m| m.user_id == zulip_id);

Ok(user.map(|m| (m.user_id, m.email.clone())))
}
}
10 changes: 9 additions & 1 deletion src/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ use std::str::FromStr;
use async_trait::async_trait;
use cron::Schedule;

use crate::{handlers::{Context, docs_update::DocsUpdateJob, rustc_commits::RustcCommitsJob}, db::jobs::JobSchedule};
use crate::{handlers::{Context, docs_update::DocsUpdateJob, rustc_commits::RustcCommitsJob, types_planning_updates::{TypesPlanningMeetingThreadOpenJob, TypesPlanningMeetingUpdatesPing}}, db::jobs::JobSchedule};

// How often new cron-based jobs will be placed in the queue.
// This is the minimum period *between* a single cron task's executions.
Expand All @@ -61,6 +61,8 @@ pub fn jobs() -> Vec<Box<dyn Job + Send + Sync>> {
vec![
Box::new(DocsUpdateJob),
Box::new(RustcCommitsJob),
Box::new(TypesPlanningMeetingThreadOpenJob),
Box::new(TypesPlanningMeetingUpdatesPing),
]
}

Expand All @@ -78,6 +80,12 @@ pub fn default_jobs() -> Vec<JobSchedule> {
schedule: Schedule::from_str("* 0,30 * * * * *").unwrap(),
metadata: serde_json::Value::Null,
},
JobSchedule {
name: TypesPlanningMeetingThreadOpenJob.name(),
// Last Monday of every month
schedule: Schedule::from_str("0 0 12 ? * 2L *").unwrap(),
metadata: serde_json::Value::Null,
},
]
}

Expand Down

0 comments on commit f18d933

Please sign in to comment.