Skip to content

Commit 330e790

Browse files
authored
Merge pull request #1836 from nikomatsakis/project-goals-3
add more parameters to the project goals message
2 parents 49024fc + 18167d0 commit 330e790

File tree

5 files changed

+184
-24
lines changed

5 files changed

+184
-24
lines changed

Cargo.lock

Lines changed: 90 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ ignore = "0.4.18"
4646
postgres-types = { version = "0.2.4", features = ["derive"] }
4747
cron = { version = "0.12.0" }
4848
bytes = "1.1.0"
49+
structopt = "0.3.26"
4950

5051
[dependencies.serde]
5152
version = "1"

src/bin/project_goals.rs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
1+
use structopt::StructOpt;
12
use triagebot::{github::GithubClient, handlers::project_goals};
23

4+
/// A basic example
5+
#[derive(StructOpt, Debug)]
6+
struct Opt {
7+
/// If specified, no messages are sent.
8+
#[structopt(long)]
9+
dry_run: bool,
10+
11+
/// Goals with an updated within this threshold will not be pinged.
12+
days_threshold: i64,
13+
14+
/// A string like "on Sep-5" when the update blog post will be written.
15+
next_meeting_date: String,
16+
}
17+
318
#[tokio::main(flavor = "current_thread")]
419
async fn main() -> anyhow::Result<()> {
520
dotenv::dotenv().ok();
621
tracing_subscriber::fmt::init();
722

8-
let mut dry_run = false;
9-
10-
for arg in std::env::args().skip(1) {
11-
match arg.as_str() {
12-
"--dry-run" => dry_run = true,
13-
_ => {
14-
eprintln!("Usage: project_goals [--dry-run]");
15-
std::process::exit(1);
16-
}
17-
}
18-
}
19-
23+
let opt = Opt::from_args();
2024
let gh = GithubClient::new_from_env();
21-
project_goals::ping_project_goals_owners(&gh, dry_run).await?;
25+
project_goals::ping_project_goals_owners(
26+
&gh,
27+
opt.dry_run,
28+
opt.days_threshold,
29+
&opt.next_meeting_date,
30+
)
31+
.await?;
2232

2333
Ok(())
2434
}

src/handlers/project_goals.rs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::jobs::Job;
66
use crate::zulip::to_zulip_id;
77
use anyhow::Context as _;
88
use async_trait::async_trait;
9-
use chrono::Utc;
9+
use chrono::{Datelike, NaiveDate, Utc};
1010
use tracing::{self as log};
1111

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

1919
const MESSAGE: &str = r#"
20-
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
20+
Dear $OWNERS, it's been $DAYS days since the last update to your goal *$GOAL*.
21+
22+
We will begin drafting the next blog post collecting goal updates $NEXT_UPDATE.
23+
24+
Please comment on the github tracking issue goals#$GOALNUM before then. Thanks! <3
2125
2226
Here is a suggested template for updates (feel free to drop the items that don't apply):
2327
@@ -35,7 +39,7 @@ impl Job for ProjectGoalsUpdateJob {
3539
}
3640

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

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

54-
pub async fn ping_project_goals_owners(gh: &GithubClient, dry_run: bool) -> anyhow::Result<()> {
58+
async fn ping_project_goals_owners_automatically(gh: &GithubClient) -> anyhow::Result<()> {
59+
// Predicted schedule is to author a blog post on the 3rd week of the month.
60+
// We start pinging when the month starts until we see an update in this month
61+
// or the last 7 days of previous month.
62+
//
63+
// Therefore, we compute:
64+
// * Days since start of this month -- threshold will be this number of days + 7.
65+
// * Date of the 3rd Monday in the month -- this will be the next update (e.g., `on Sep-5`).
66+
let now = Utc::now();
67+
68+
// We want to ping people unless they've written an update since the last week of the previous month.
69+
let days_threshold = now.day() + 7;
70+
71+
// Format the 3rd Monday of the month, e.g. "on Sep-5", for inclusion.
72+
let third_monday =
73+
NaiveDate::from_weekday_of_month_opt(now.year(), now.month(), chrono::Weekday::Mon, 3)
74+
.unwrap()
75+
.format("on %b-%d")
76+
.to_string();
77+
78+
ping_project_goals_owners(gh, false, days_threshold as i64, &third_monday).await
79+
}
80+
81+
/// Sends a ping message to all project goal owners if
82+
/// they have not posted an update in the last `days_threshold` days.
83+
///
84+
/// `next_update` is a human readable description of when the next update
85+
/// will be drafted (e.g., `"on Sep 5"`).
86+
pub async fn ping_project_goals_owners(
87+
gh: &GithubClient,
88+
dry_run: bool,
89+
days_threshold: i64,
90+
next_update: &str,
91+
) -> anyhow::Result<()> {
5592
let goals_repo = gh.repository(&RUST_PROJECT_GOALS_REPO).await?;
5693

5794
let tracking_issues_query = github::Query {
@@ -78,7 +115,7 @@ pub async fn ping_project_goals_owners(gh: &GithubClient, dry_run: bool) -> anyh
78115
days_since_last_comment,
79116
comments,
80117
);
81-
if days_since_last_comment < 21 && comments > 1 {
118+
if days_since_last_comment < days_threshold && comments > 1 {
82119
continue;
83120
}
84121

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

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

src/zulip.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::handlers::Context;
88
use anyhow::{format_err, Context as _};
99
use std::env;
1010
use std::fmt::Write as _;
11+
use std::str::FromStr;
1112
use tracing as log;
1213

1314
#[derive(Debug, serde::Deserialize)]
@@ -190,8 +191,29 @@ fn handle_command<'a>(
190191
.map_err(|e| format_err!("Failed to await at this time: {e:?}"))
191192
}
192193
Some("ping-goals") => {
194+
let usage_err = |description: &str| Err(format_err!(
195+
"Error: {description}\n\
196+
\n\
197+
Usage: triagebot ping-goals D N, where:\n\
198+
\n\
199+
* D is the number of days before an update is considered stale\n\
200+
* N is the date of next update, like \"Sep-5\"\n",
201+
));
202+
203+
let Some(threshold) = words.next() else {
204+
return usage_err("expected number of days");
205+
};
206+
let threshold = match i64::from_str(threshold) {
207+
Ok(v) => v,
208+
Err(e) => return usage_err(&format!("ill-formed number of days, {e}")),
209+
};
210+
211+
let Some(next_update) = words.next() else {
212+
return usage_err("expected date of next update");
213+
};
214+
193215
if project_goals::check_project_goal_acl(&ctx.github, gh_id).await? {
194-
ping_project_goals_owners(&ctx.github, false)
216+
ping_project_goals_owners(&ctx.github, false, threshold, &format!("on {next_update}"))
195217
.await
196218
.map_err(|e| format_err!("Failed to await at this time: {e:?}"))?;
197219
return Ok(None);

0 commit comments

Comments
 (0)