diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index ade446bf67..c1f755b53c 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -6054,6 +6054,39 @@ paths: required: - database_length + /w/{workspace}/jobs/completed/count_jobs: + get: + summary: count number of completed jobs with filter + operationId: countCompletedJobs + tags: + - job + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: completed_after_s_ago + in: query + schema: + type: integer + - name: success + in: query + schema: + type: boolean + - name: tags + in: query + schema: + type: string + - name: all_workspaces + in: query + schema: + type: boolean + responses: + "200": + description: Count of completed jobs + content: + application/json: + schema: + type: integer + + /w/{workspace}/jobs/queue/list_filtered_uuids: get: summary: get the ids of all jobs matching the given filters diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 680bfd6c76..3c7cea1584 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -206,6 +206,7 @@ pub fn workspaced_service() -> Router { .route("/queue/list_filtered_uuids", get(list_filtered_uuids)) .route("/queue/cancel_selection", post(cancel_selection)) .route("/completed/count", get(count_completed_jobs)) + .route("/completed/count_jobs", get(count_completed_jobs_detail)) .route( "/completed/list", get(list_completed_jobs).layer(cors.clone()), @@ -1592,6 +1593,50 @@ async fn count_queue_jobs( )) } +#[derive(Deserialize)] +pub struct CountCompletedJobsQuery { + completed_after_s_ago: Option, + success: Option, + tags: Option, + all_workspaces: Option, +} + +async fn count_completed_jobs_detail( + Extension(db): Extension, + Path(w_id): Path, + Query(query): Query, +) -> error::JsonResult { + let mut sqlb = SqlBuilder::select_from("completed_job"); + sqlb + .field("COUNT(*) as count"); + + if !query.all_workspaces.unwrap_or(false) { + sqlb.and_where_eq("workspace_id", "?".bind(&w_id)); + } + + if let Some(after_s_ago) = query.completed_after_s_ago { + let after = Utc::now() - chrono::Duration::seconds(after_s_ago); + sqlb.and_where_gt("started_at + duration_ms / 1000 * interval '1 second'", "?".bind(&after.to_rfc3339())); + } + + if let Some(success) = query.success { + sqlb.and_where_eq("success", "?".bind(&success)); + } + + if let Some(tags) = query.tags { + sqlb.and_where_in("tag", &tags.split(",").map(|t| format!("'{}'", t)).collect::>()); + } + + let sql = sqlb.sql()?; + let stats = sqlx::query_scalar::<_, i64>(&sql) + .fetch_one(&db) + .await?; + + Ok(Json(stats)) +} + + + async fn count_completed_jobs( Extension(db): Extension, Path(w_id): Path, diff --git a/python-client/wmill/wmill/client.py b/python-client/wmill/wmill/client.py index 08338a273f..c57e181a0f 100644 --- a/python-client/wmill/wmill/client.py +++ b/python-client/wmill/wmill/client.py @@ -26,7 +26,7 @@ JobStatus = Literal["RUNNING", "WAITING", "COMPLETED"] class Windmill: def __init__(self, base_url=None, token=None, workspace=None, verify=True): - base = base_url or os.environ.get("BASE_INTERNAL_URL") + base = base_url or os.environ.get("BASE_INTERNAL_URL") or os.environ.get("WM_BASE_URL") self.base_url = f"{base}/api" self.token = token or os.environ.get("WM_TOKEN")