@@ -22,6 +22,7 @@ use anyhow::{Context as _, format_err};
22
22
use rust_team_data:: v1:: { TeamKind , TeamMember } ;
23
23
use std:: cmp:: Reverse ;
24
24
use std:: fmt:: Write as _;
25
+ use std:: sync:: Arc ;
25
26
use subtle:: ConstantTimeEq ;
26
27
use tracing as log;
27
28
@@ -88,7 +89,7 @@ struct Response {
88
89
/// Top-level handler for Zulip webhooks.
89
90
///
90
91
/// Returns a JSON response.
91
- pub async fn respond ( ctx : & Context , req : Request ) -> String {
92
+ pub async fn respond ( ctx : Arc < Context > , req : Request ) -> String {
92
93
let content = match process_zulip_request ( ctx, req) . await {
93
94
Ok ( None ) => {
94
95
return serde_json:: to_string ( & ResponseNotRequired {
@@ -123,7 +124,7 @@ pub fn get_token_from_env() -> Result<String, anyhow::Error> {
123
124
/// Processes a Zulip webhook.
124
125
///
125
126
/// Returns a string of the response, or None if no response is needed.
126
- async fn process_zulip_request ( ctx : & Context , req : Request ) -> anyhow:: Result < Option < String > > {
127
+ async fn process_zulip_request ( ctx : Arc < Context > , req : Request ) -> anyhow:: Result < Option < String > > {
127
128
let expected_token = get_token_from_env ( ) ?;
128
129
if !bool:: from ( req. token . as_bytes ( ) . ct_eq ( expected_token. as_bytes ( ) ) ) {
129
130
anyhow:: bail!( "Invalid authorization." ) ;
@@ -148,7 +149,7 @@ async fn process_zulip_request(ctx: &Context, req: Request) -> anyhow::Result<Op
148
149
}
149
150
150
151
async fn handle_command < ' a > (
151
- ctx : & ' a Context ,
152
+ ctx : Arc < Context > ,
152
153
mut gh_id : u64 ,
153
154
command : & ' a str ,
154
155
message_data : & ' a Message ,
@@ -203,10 +204,12 @@ async fn handle_command<'a>(
203
204
}
204
205
ChatCommand :: Whoami => whoami_cmd ( & ctx, gh_id) . await ,
205
206
ChatCommand :: Lookup ( cmd) => lookup_cmd ( & ctx, cmd) . await ,
206
- ChatCommand :: Work ( cmd) => workqueue_commands ( ctx, gh_id, cmd) . await ,
207
- ChatCommand :: PingGoals ( args) => ping_goals_cmd ( ctx, gh_id, & args) . await ,
207
+ ChatCommand :: Work ( cmd) => workqueue_commands ( & ctx, gh_id, cmd) . await ,
208
+ ChatCommand :: PingGoals ( args) => {
209
+ ping_goals_cmd ( ctx. clone ( ) , gh_id, message_data, & args) . await
210
+ }
208
211
ChatCommand :: DocsUpdate => trigger_docs_update ( message_data, & ctx. zulip ) ,
209
- ChatCommand :: TeamStats { name } => team_status_cmd ( ctx, & name) . await ,
212
+ ChatCommand :: TeamStats { name } => team_status_cmd ( & ctx, & name) . await ,
210
213
} ;
211
214
212
215
let output = output?;
@@ -267,29 +270,58 @@ async fn handle_command<'a>(
267
270
StreamCommand :: Read => post_waiter ( & ctx, message_data, WaitingMessage :: start_reading ( ) )
268
271
. await
269
272
. map_err ( |e| format_err ! ( "Failed to await at this time: {e:?}" ) ) ,
270
- StreamCommand :: PingGoals ( args) => ping_goals_cmd ( ctx, gh_id, & args) . await ,
273
+ StreamCommand :: PingGoals ( args) => ping_goals_cmd ( ctx, gh_id, message_data , & args) . await ,
271
274
StreamCommand :: DocsUpdate => trigger_docs_update ( message_data, & ctx. zulip ) ,
272
275
}
273
276
}
274
277
}
275
278
276
279
async fn ping_goals_cmd (
277
- ctx : & Context ,
280
+ ctx : Arc < Context > ,
278
281
gh_id : u64 ,
282
+ message : & Message ,
279
283
args : & PingGoalsArgs ,
280
284
) -> anyhow:: Result < Option < String > > {
281
285
if project_goals:: check_project_goal_acl ( & ctx. team , gh_id) . await ? {
282
- ping_project_goals_owners (
283
- & ctx. github ,
284
- & ctx. zulip ,
285
- & ctx. team ,
286
- false ,
287
- args. threshold as i64 ,
288
- & format ! ( "on {}" , args. next_update) ,
289
- )
290
- . await
291
- . map_err ( |e| format_err ! ( "Failed to await at this time: {e:?}" ) ) ?;
292
- Ok ( None )
286
+ let args = args. clone ( ) ;
287
+ let message = message. clone ( ) ;
288
+ tokio:: spawn ( async move {
289
+ let res = ping_project_goals_owners (
290
+ & ctx. github ,
291
+ & ctx. zulip ,
292
+ & ctx. team ,
293
+ false ,
294
+ args. threshold as i64 ,
295
+ & format ! ( "on {}" , args. next_update) ,
296
+ )
297
+ . await ;
298
+
299
+ let status = match res {
300
+ Ok ( _res) => "OK" . to_string ( ) ,
301
+ Err ( err) => {
302
+ tracing:: error!( "ping_project_goals_owners: {err:?}" ) ;
303
+ format ! ( "ERROR\n \n ```\n {err:#?}\n ```\n " )
304
+ }
305
+ } ;
306
+
307
+ let res = MessageApiRequest {
308
+ recipient : Recipient :: Private {
309
+ id : message. sender_id ,
310
+ email : & message. sender_email ,
311
+ } ,
312
+ content : & format ! ( "End pinging project groups owners: {status}" ) ,
313
+ }
314
+ . send ( & ctx. zulip )
315
+ . await ;
316
+
317
+ if let Err ( err) = res {
318
+ tracing:: error!(
319
+ "error sending project goals ping reply: {err:?} for status: {status}"
320
+ ) ;
321
+ }
322
+ } ) ;
323
+
324
+ Ok ( Some ( "Started pinging project groups owners..." . to_string ( ) ) )
293
325
} else {
294
326
Err ( format_err ! (
295
327
"That command is only permitted for those running the project-goal program." ,
0 commit comments