Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add more parameters to the project goals message #1836

Merged
merged 4 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 90 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ ignore = "0.4.18"
postgres-types = { version = "0.2.4", features = ["derive"] }
cron = { version = "0.12.0" }
bytes = "1.1.0"
structopt = "0.3.26"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's surprising to me or not that we don't already have a CLI parser.


[dependencies.serde]
version = "1"
Expand Down
36 changes: 23 additions & 13 deletions src/bin/project_goals.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
use structopt::StructOpt;
use triagebot::{github::GithubClient, handlers::project_goals};

/// A basic example
#[derive(StructOpt, Debug)]
struct Opt {
/// If specified, no messages are sent.
#[structopt(long)]
dry_run: bool,

/// Goals with an updated within this threshold will not be pinged.
days_threshold: i64,

/// A string like "on Sep-5" when the update blog post will be written.
next_meeting_date: String,
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
dotenv::dotenv().ok();
tracing_subscriber::fmt::init();

let mut dry_run = false;

for arg in std::env::args().skip(1) {
match arg.as_str() {
"--dry-run" => dry_run = true,
_ => {
eprintln!("Usage: project_goals [--dry-run]");
std::process::exit(1);
}
}
}

let opt = Opt::from_args();
let gh = GithubClient::new_from_env();
project_goals::ping_project_goals_owners(&gh, dry_run).await?;
project_goals::ping_project_goals_owners(
&gh,
opt.dry_run,
opt.days_threshold,
&opt.next_meeting_date,
)
.await?;

Ok(())
}
54 changes: 47 additions & 7 deletions src/handlers/project_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::jobs::Job;
use crate::zulip::to_zulip_id;
use anyhow::Context as _;
use async_trait::async_trait;
use chrono::Utc;
use chrono::{Datelike, NaiveDate, Utc};
use tracing::{self as log};

use super::Context;
Expand All @@ -17,7 +17,11 @@ const GOALS_STREAM: u64 = 435869; // #project-goals
const C_TRACKING_ISSUE: &str = "C-tracking-issue";

const MESSAGE: &str = r#"
Dear $OWNERS, it's been $DAYS days since the last update to your goal *$GOAL*. Please comment on the github tracking issue goals#$GOALNUM with an update at your earliest convenience. Thanks! <3
Dear $OWNERS, it's been $DAYS days since the last update to your goal *$GOAL*.

We will begin drafting the next blog post collecting goal updates $NEXT_UPDATE.

Please comment on the github tracking issue goals#$GOALNUM before then. Thanks! <3

Here is a suggested template for updates (feel free to drop the items that don't apply):

Expand All @@ -35,7 +39,7 @@ impl Job for ProjectGoalsUpdateJob {
}

async fn run(&self, ctx: &super::Context, _metadata: &serde_json::Value) -> anyhow::Result<()> {
ping_project_goals_owners(&ctx.github, false).await
ping_project_goals_owners_automatically(&ctx.github).await
}
}

Expand All @@ -51,7 +55,40 @@ pub async fn check_project_goal_acl(_gh: &GithubClient, gh_id: u64) -> anyhow::R
Ok(gh_id == GOAL_OWNER_GH_ID)
}

pub async fn ping_project_goals_owners(gh: &GithubClient, dry_run: bool) -> anyhow::Result<()> {
async fn ping_project_goals_owners_automatically(gh: &GithubClient) -> anyhow::Result<()> {
// Predicted schedule is to author a blog post on the 3rd week of the month.
// We start pinging when the month starts until we see an update in this month
// or the last 7 days of previous month.
//
// Therefore, we compute:
// * Days since start of this month -- threshold will be this number of days + 7.
// * Date of the 3rd Monday in the month -- this will be the next update (e.g., `on Sep-5`).
let now = Utc::now();

// We want to ping people unless they've written an update since the last week of the previous month.
let days_threshold = now.day() + 7;

// Format the 3rd Monday of the month, e.g. "on Sep-5", for inclusion.
let third_monday =
NaiveDate::from_weekday_of_month_opt(now.year(), now.month(), chrono::Weekday::Mon, 3)
.unwrap()
.format("on %b-%d")
.to_string();

ping_project_goals_owners(gh, false, days_threshold as i64, &third_monday).await
}

/// Sends a ping message to all project goal owners if
/// they have not posted an update in the last `days_threshold` days.
///
/// `next_update` is a human readable description of when the next update
/// will be drafted (e.g., `"on Sep 5"`).
pub async fn ping_project_goals_owners(
gh: &GithubClient,
dry_run: bool,
days_threshold: i64,
next_update: &str,
) -> anyhow::Result<()> {
let goals_repo = gh.repository(&RUST_PROJECT_GOALS_REPO).await?;

let tracking_issues_query = github::Query {
Expand All @@ -78,7 +115,7 @@ pub async fn ping_project_goals_owners(gh: &GithubClient, dry_run: bool) -> anyh
days_since_last_comment,
comments,
);
if days_since_last_comment < 21 && comments > 1 {
if days_since_last_comment < days_threshold && comments > 1 {
continue;
}

Expand All @@ -99,7 +136,8 @@ pub async fn ping_project_goals_owners(gh: &GithubClient, dry_run: bool) -> anyh
},
)
.replace("$GOALNUM", &issue.number.to_string())
.replace("$GOAL", &issue.title);
.replace("$GOAL", &issue.title)
.replace("$NEXT_UPDATE", next_update);

let zulip_req = crate::zulip::MessageApiRequest {
recipient: crate::zulip::Recipient::Stream {
Expand All @@ -115,7 +153,9 @@ pub async fn ping_project_goals_owners(gh: &GithubClient, dry_run: bool) -> anyh
if !dry_run {
zulip_req.send(&gh.raw()).await?;
} else {
log::debug!("skipping zulip send because dry run");
eprintln!();
eprintln!("-- Dry Run ------------------------------------");
eprintln!("Would send to {zulip_topic_name}: {}", zulip_req.content);
}
}

Expand Down
24 changes: 23 additions & 1 deletion src/zulip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::handlers::Context;
use anyhow::{format_err, Context as _};
use std::env;
use std::fmt::Write as _;
use std::str::FromStr;
use tracing as log;

#[derive(Debug, serde::Deserialize)]
Expand Down Expand Up @@ -190,8 +191,29 @@ fn handle_command<'a>(
.map_err(|e| format_err!("Failed to await at this time: {e:?}"))
}
Some("ping-goals") => {
let usage_err = |description: &str| Err(format_err!(
"Error: {description}\n\
\n\
Usage: triagebot ping-goals D N, where:\n\
\n\
* D is the number of days before an update is considered stale\n\
* N is the date of next update, like \"Sep-5\"\n",
));

let Some(threshold) = words.next() else {
return usage_err("expected number of days");
};
let threshold = match i64::from_str(threshold) {
Ok(v) => v,
Err(e) => return usage_err(&format!("ill-formed number of days, {e}")),
};

let Some(next_update) = words.next() else {
return usage_err("expected date of next update");
};

if project_goals::check_project_goal_acl(&ctx.github, gh_id).await? {
ping_project_goals_owners(&ctx.github, false)
ping_project_goals_owners(&ctx.github, false, threshold, &format!("on {next_update}"))
.await
.map_err(|e| format_err!("Failed to await at this time: {e:?}"))?;
return Ok(None);
Expand Down
Loading