feat: add edit schedule functionality (#2700)
This commit is contained in:
@@ -41,6 +41,7 @@ use utoipa::OpenApi;
|
||||
super::routes::schedule::create_schedule,
|
||||
super::routes::schedule::list_schedules,
|
||||
super::routes::schedule::delete_schedule,
|
||||
super::routes::schedule::update_schedule,
|
||||
super::routes::schedule::run_now_handler,
|
||||
super::routes::schedule::pause_schedule,
|
||||
super::routes::schedule::unpause_schedule,
|
||||
@@ -93,6 +94,7 @@ use utoipa::OpenApi;
|
||||
SessionInfo,
|
||||
SessionMetadata,
|
||||
super::routes::schedule::CreateScheduleRequest,
|
||||
super::routes::schedule::UpdateScheduleRequest,
|
||||
goose::scheduler::ScheduledJob,
|
||||
super::routes::schedule::RunNowResponse,
|
||||
super::routes::schedule::ListSchedulesResponse,
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::{HeaderMap, StatusCode},
|
||||
routing::{delete, get, post},
|
||||
routing::{delete, get, post, put},
|
||||
Json, Router,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -21,6 +21,11 @@ pub struct CreateScheduleRequest {
|
||||
cron: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||
pub struct UpdateScheduleRequest {
|
||||
cron: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
pub struct ListSchedulesResponse {
|
||||
jobs: Vec<ScheduledJob>,
|
||||
@@ -333,11 +338,63 @@ async fn unpause_schedule(
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/schedule/{id}",
|
||||
params(
|
||||
("id" = String, Path, description = "ID of the schedule to update")
|
||||
),
|
||||
request_body = UpdateScheduleRequest,
|
||||
responses(
|
||||
(status = 200, description = "Scheduled job updated successfully", body = ScheduledJob),
|
||||
(status = 404, description = "Scheduled job not found"),
|
||||
(status = 400, description = "Cannot update a currently running job or invalid request"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
tag = "schedule"
|
||||
)]
|
||||
#[axum::debug_handler]
|
||||
async fn update_schedule(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
Json(req): Json<UpdateScheduleRequest>,
|
||||
) -> Result<Json<ScheduledJob>, StatusCode> {
|
||||
verify_secret_key(&headers, &state)?;
|
||||
let scheduler = state
|
||||
.scheduler()
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
scheduler
|
||||
.update_schedule(&id, req.cron)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Error updating schedule '{}': {:?}", id, e);
|
||||
match e {
|
||||
goose::scheduler::SchedulerError::JobNotFound(_) => StatusCode::NOT_FOUND,
|
||||
goose::scheduler::SchedulerError::AnyhowError(_) => StatusCode::BAD_REQUEST,
|
||||
goose::scheduler::SchedulerError::CronParseError(_) => StatusCode::BAD_REQUEST,
|
||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
})?;
|
||||
|
||||
// Return the updated schedule
|
||||
let jobs = scheduler.list_scheduled_jobs().await;
|
||||
let updated_job = jobs
|
||||
.into_iter()
|
||||
.find(|job| job.id == id)
|
||||
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(Json(updated_job))
|
||||
}
|
||||
|
||||
pub fn routes(state: Arc<AppState>) -> Router {
|
||||
Router::new()
|
||||
.route("/schedule/create", post(create_schedule))
|
||||
.route("/schedule/list", get(list_schedules))
|
||||
.route("/schedule/delete/{id}", delete(delete_schedule)) // Corrected
|
||||
.route("/schedule/{id}", put(update_schedule))
|
||||
.route("/schedule/{id}/run_now", post(run_now_handler)) // Corrected
|
||||
.route("/schedule/{id}/pause", post(pause_schedule))
|
||||
.route("/schedule/{id}/unpause", post(unpause_schedule))
|
||||
|
||||
@@ -577,6 +577,138 @@ impl Scheduler {
|
||||
None => Err(SchedulerError::JobNotFound(sched_id.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_schedule(
|
||||
&self,
|
||||
sched_id: &str,
|
||||
new_cron: String,
|
||||
) -> Result<(), SchedulerError> {
|
||||
let mut jobs_guard = self.jobs.lock().await;
|
||||
match jobs_guard.get_mut(sched_id) {
|
||||
Some((job_uuid, job_def)) => {
|
||||
if job_def.currently_running {
|
||||
return Err(SchedulerError::AnyhowError(anyhow!(
|
||||
"Cannot edit schedule '{}' while it's currently running",
|
||||
sched_id
|
||||
)));
|
||||
}
|
||||
|
||||
if new_cron == job_def.cron {
|
||||
// No change needed
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Remove the old job from the scheduler
|
||||
self.internal_scheduler
|
||||
.remove(job_uuid)
|
||||
.await
|
||||
.map_err(|e| SchedulerError::SchedulerInternalError(e.to_string()))?;
|
||||
|
||||
// Create new job with updated cron
|
||||
let job_for_task = job_def.clone();
|
||||
let jobs_arc_for_task = self.jobs.clone();
|
||||
let storage_path_for_task = self.storage_path.clone();
|
||||
|
||||
let cron_task = Job::new_async(&new_cron, move |_uuid, _l| {
|
||||
let task_job_id = job_for_task.id.clone();
|
||||
let current_jobs_arc = jobs_arc_for_task.clone();
|
||||
let local_storage_path = storage_path_for_task.clone();
|
||||
let job_to_execute = job_for_task.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
// Check if the job is paused before executing
|
||||
let should_execute = {
|
||||
let jobs_map_guard = current_jobs_arc.lock().await;
|
||||
if let Some((_, current_job_in_map)) = jobs_map_guard.get(&task_job_id)
|
||||
{
|
||||
!current_job_in_map.paused
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !should_execute {
|
||||
tracing::info!("Skipping execution of paused job '{}'", &task_job_id);
|
||||
return;
|
||||
}
|
||||
|
||||
let current_time = Utc::now();
|
||||
let mut needs_persist = false;
|
||||
{
|
||||
let mut jobs_map_guard = current_jobs_arc.lock().await;
|
||||
if let Some((_, current_job_in_map)) =
|
||||
jobs_map_guard.get_mut(&task_job_id)
|
||||
{
|
||||
current_job_in_map.last_run = Some(current_time);
|
||||
current_job_in_map.currently_running = true;
|
||||
needs_persist = true;
|
||||
}
|
||||
}
|
||||
|
||||
if needs_persist {
|
||||
if let Err(e) =
|
||||
persist_jobs_from_arc(&local_storage_path, ¤t_jobs_arc).await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to persist last_run update for job {}: {}",
|
||||
&task_job_id,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let result = run_scheduled_job_internal(job_to_execute, None).await;
|
||||
|
||||
// Update the job status after execution
|
||||
{
|
||||
let mut jobs_map_guard = current_jobs_arc.lock().await;
|
||||
if let Some((_, current_job_in_map)) =
|
||||
jobs_map_guard.get_mut(&task_job_id)
|
||||
{
|
||||
current_job_in_map.currently_running = false;
|
||||
needs_persist = true;
|
||||
}
|
||||
}
|
||||
|
||||
if needs_persist {
|
||||
if let Err(e) =
|
||||
persist_jobs_from_arc(&local_storage_path, ¤t_jobs_arc).await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to persist running status update for job {}: {}",
|
||||
&task_job_id,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = result {
|
||||
tracing::error!(
|
||||
"Scheduled job '{}' execution failed: {}",
|
||||
&e.job_id,
|
||||
e.error
|
||||
);
|
||||
}
|
||||
})
|
||||
})
|
||||
.map_err(|e| SchedulerError::CronParseError(e.to_string()))?;
|
||||
|
||||
let new_job_uuid = self
|
||||
.internal_scheduler
|
||||
.add(cron_task)
|
||||
.await
|
||||
.map_err(|e| SchedulerError::SchedulerInternalError(e.to_string()))?;
|
||||
|
||||
// Update the job UUID and cron expression
|
||||
*job_uuid = new_job_uuid;
|
||||
job_def.cron = new_cron;
|
||||
|
||||
self.persist_jobs_to_storage_with_guard(&jobs_guard).await?;
|
||||
Ok(())
|
||||
}
|
||||
None => Err(SchedulerError::JobNotFound(sched_id.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
Reference in New Issue
Block a user