|
@@ -1,4 +1,11 @@
|
|
-//! SCHEDULED JOBS
|
|
|
|
|
|
+//! # Scheduled Jobs
|
|
|
|
+//!
|
|
|
|
+//! Scheduled jobs essentially come in two flavors: automatically repeating
|
|
|
|
+//! (cron) jobs and one-off jobs.
|
|
|
|
+//!
|
|
|
|
+//! The core trait here is the `Job` trait, which *must* define the name of the
|
|
|
|
+//! job (to be used as an identifier in the database) and the function to run
|
|
|
|
+//! when the job runs.
|
|
//!
|
|
//!
|
|
//! The metadata is a serde_json::Value
|
|
//! The metadata is a serde_json::Value
|
|
//! Please refer to https://docs.rs/serde_json/latest/serde_json/value/fn.from_value.html
|
|
//! Please refer to https://docs.rs/serde_json/latest/serde_json/value/fn.from_value.html
|
|
@@ -7,33 +14,42 @@
|
|
//! The schedule is a cron::Schedule
|
|
//! The schedule is a cron::Schedule
|
|
//! Please refer to https://docs.rs/cron/latest/cron/struct.Schedule.html for further info
|
|
//! Please refer to https://docs.rs/cron/latest/cron/struct.Schedule.html for further info
|
|
//!
|
|
//!
|
|
-//! For example, if we want to sends a Zulip message every Friday at 11:30am ET into #t-release
|
|
|
|
-//! with a @T-release meeting! content, we should create some JobSchedule like:
|
|
|
|
|
|
+//! ## Example, sending a zulip message once a week
|
|
//!
|
|
//!
|
|
|
|
+//! To give an example, let's imagine we want to sends a Zulip message every
|
|
|
|
+//! Friday at 11:30am ET into #t-release with a "@T-release meeting!"" content.
|
|
|
|
+//!
|
|
|
|
+//! To begin, let's create a generic zulip message Job:
|
|
//! #[derive(Serialize, Deserialize)]
|
|
//! #[derive(Serialize, Deserialize)]
|
|
//! struct ZulipMetadata {
|
|
//! struct ZulipMetadata {
|
|
//! pub message: String
|
|
//! pub message: String
|
|
|
|
+//! pub channel: String,
|
|
//! }
|
|
//! }
|
|
|
|
+//! struct ZulipMessageJob;
|
|
|
|
+//! impl Job for ZulipMessageJob { ... }
|
|
//!
|
|
//!
|
|
-//! let metadata = serde_json::value::to_value(ZulipMetadata {
|
|
|
|
-//! message: "@T-release meeting!".to_string()
|
|
|
|
-//! }).unwrap();
|
|
|
|
-//!
|
|
|
|
-//! let schedule = Schedule::from_str("0 30 11 * * FRI *").unwrap();
|
|
|
|
-//!
|
|
|
|
-//! let new_job = JobSchedule {
|
|
|
|
-//! name: "send_zulip_message".to_owned(),
|
|
|
|
-//! schedule: schedule,
|
|
|
|
-//! metadata: metadata
|
|
|
|
-//! }
|
|
|
|
-//!
|
|
|
|
-//! and include it in the below vector in jobs():
|
|
|
|
|
|
+//! (Imagine that this job requires a channel and a message in the metadata.)
|
|
//!
|
|
//!
|
|
-//! jobs.push(new_job);
|
|
|
|
-//!
|
|
|
|
-//! ... fianlly, add the corresponding "send_zulip_message" handler in src/handlers/jobs.rs
|
|
|
|
|
|
+//! If we wanted to have a default scheduled message, we could add the following to
|
|
|
|
+//! `default_jobs`:
|
|
|
|
+//! JobSchedule {
|
|
|
|
+//! name: ZulipMessageJob.name(),
|
|
|
|
+//! schedule: Schedule::from_str("0 30 11 * * FRI *").unwrap(),
|
|
|
|
+//! metadata: serde_json::value::to_value(ZulipMetadata {
|
|
|
|
+//! message: "@T-release meeting!".to_string()
|
|
|
|
+//! channel: "T-release".to_string(),
|
|
|
|
+//! }).unwrap(),
|
|
|
|
+//! }
|
|
|
|
+
|
|
|
|
+use std::str::FromStr;
|
|
|
|
|
|
-use crate::db::jobs::JobSchedule;
|
|
|
|
|
|
+use async_trait::async_trait;
|
|
|
|
+use cron::Schedule;
|
|
|
|
+
|
|
|
|
+use crate::{
|
|
|
|
+ db::jobs::JobSchedule,
|
|
|
|
+ handlers::{docs_update::DocsUpdateJob, rustc_commits::RustcCommitsJob, Context},
|
|
|
|
+};
|
|
|
|
|
|
// How often new cron-based jobs will be placed in the queue.
|
|
// How often new cron-based jobs will be placed in the queue.
|
|
// This is the minimum period *between* a single cron task's executions.
|
|
// This is the minimum period *between* a single cron task's executions.
|
|
@@ -43,16 +59,50 @@ pub const JOB_SCHEDULING_CADENCE_IN_SECS: u64 = 1800;
|
|
// This is the granularity at which events will occur.
|
|
// This is the granularity at which events will occur.
|
|
pub const JOB_PROCESSING_CADENCE_IN_SECS: u64 = 60;
|
|
pub const JOB_PROCESSING_CADENCE_IN_SECS: u64 = 60;
|
|
|
|
|
|
-pub fn jobs() -> Vec<JobSchedule> {
|
|
|
|
- // Add to this vector any new cron task you want (as explained above)
|
|
|
|
|
|
+// The default jobs to schedule, repeatedly.
|
|
|
|
+pub fn jobs() -> Vec<Box<dyn Job + Send + Sync>> {
|
|
|
|
+ vec![Box::new(DocsUpdateJob), Box::new(RustcCommitsJob)]
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+pub fn default_jobs() -> Vec<JobSchedule> {
|
|
vec![
|
|
vec![
|
|
- crate::handlers::docs_update::job(),
|
|
|
|
- crate::handlers::rustc_commits::job(),
|
|
|
|
|
|
+ JobSchedule {
|
|
|
|
+ name: DocsUpdateJob.name(),
|
|
|
|
+ // Around 9am Pacific time on every Monday.
|
|
|
|
+ schedule: Schedule::from_str("0 00 17 * * Mon *").unwrap(),
|
|
|
|
+ metadata: serde_json::Value::Null,
|
|
|
|
+ },
|
|
|
|
+ JobSchedule {
|
|
|
|
+ name: RustcCommitsJob.name(),
|
|
|
|
+ // Every 30 minutes...
|
|
|
|
+ schedule: Schedule::from_str("* 0,30 * * * * *").unwrap(),
|
|
|
|
+ metadata: serde_json::Value::Null,
|
|
|
|
+ },
|
|
]
|
|
]
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+#[async_trait]
|
|
|
|
+pub trait Job {
|
|
|
|
+ fn name(&self) -> &str;
|
|
|
|
+
|
|
|
|
+ async fn run(&self, ctx: &Context, metadata: &serde_json::Value) -> anyhow::Result<()>;
|
|
|
|
+}
|
|
|
|
+
|
|
#[test]
|
|
#[test]
|
|
fn jobs_defined() {
|
|
fn jobs_defined() {
|
|
|
|
+ // This checks that we don't panic (during schedule parsing) and that all names are unique
|
|
// Checks we don't panic here, mostly for the schedule parsing.
|
|
// Checks we don't panic here, mostly for the schedule parsing.
|
|
- drop(jobs());
|
|
|
|
|
|
+ let all_jobs = jobs();
|
|
|
|
+ let mut all_job_names: Vec<_> = all_jobs.into_iter().map(|j| j.name().to_string()).collect();
|
|
|
|
+ all_job_names.sort();
|
|
|
|
+ let mut unique_all_job_names = all_job_names.clone();
|
|
|
|
+ unique_all_job_names.sort();
|
|
|
|
+ unique_all_job_names.dedup();
|
|
|
|
+ assert_eq!(all_job_names, unique_all_job_names);
|
|
|
|
+
|
|
|
|
+ // Also ensure that our defalt jobs are release jobs
|
|
|
|
+ let default_jobs = default_jobs();
|
|
|
|
+ default_jobs
|
|
|
|
+ .iter()
|
|
|
|
+ .for_each(|j| assert!(all_job_names.contains(&j.name.to_string())));
|
|
}
|
|
}
|