mirror of
https://codeberg.org/Toasterson/solstice-ci.git
synced 2026-04-10 13:20:41 +00:00
Add grouped job listing endpoint to logs-service and define related models
- Introduce new `/jobs` endpoint for listing jobs grouped by `(repo_url, commit_sha)`, ordered by update timestamp. - Add models `JobGroup`, `JobSummary`, and `JobLinks` to structure grouped job details. - Implement grouping logic using `BTreeMap` for structured output. - Extend router with the new endpoint and integrate ORM-backed query for fetching job data. Signed-off-by: Till Wegmueller <toasterson@gmail.com>
This commit is contained in:
parent
08eb82d7f7
commit
0a9d46a455
1 changed files with 98 additions and 0 deletions
|
|
@ -6,9 +6,33 @@ use sea_orm::sea_query::Expr;
|
|||
use sea_orm_migration::MigratorTrait;
|
||||
use serde::Serialize;
|
||||
use std::net::SocketAddr;
|
||||
use std::collections::BTreeMap;
|
||||
use tracing::{info, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JobLinks {
|
||||
logs: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JobSummary {
|
||||
request_id: Uuid,
|
||||
runs_on: Option<String>,
|
||||
state: String,
|
||||
updated_at: chrono::DateTime<chrono::Utc>,
|
||||
links: JobLinks,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JobGroup {
|
||||
repo_url: String,
|
||||
commit_sha: String,
|
||||
last_updated: chrono::DateTime<chrono::Utc>,
|
||||
total_jobs: usize,
|
||||
jobs: Vec<JobSummary>,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "solstice-logs", version, about = "Solstice CI — Logs Service")]
|
||||
struct Opts {
|
||||
|
|
@ -39,6 +63,7 @@ async fn main() -> Result<()> {
|
|||
let router = Router::new()
|
||||
.route("/jobs/{request_id}/logs", get(list_logs))
|
||||
.route("/jobs/{request_id}/logs/{category}", get(get_logs_by_category))
|
||||
.route("/jobs", get(list_jobs_grouped))
|
||||
.with_state(state);
|
||||
|
||||
let addr: SocketAddr = opts.http_addr.parse().expect("invalid HTTP_ADDR");
|
||||
|
|
@ -73,6 +98,25 @@ mod job_logs {
|
|||
impl ActiveModelBehavior for ActiveModel {}
|
||||
}
|
||||
|
||||
mod jobs {
|
||||
use super::*;
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "jobs")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub request_id: Uuid,
|
||||
pub repo_url: String,
|
||||
pub commit_sha: String,
|
||||
pub runs_on: Option<String>,
|
||||
pub state: String,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
}
|
||||
|
||||
#[derive(Serialize, sea_orm::FromQueryResult)]
|
||||
struct LogCategorySummary {
|
||||
category: String,
|
||||
|
|
@ -162,3 +206,57 @@ async fn get_logs_by_category(Path((request_id, category)): Path<(String, String
|
|||
Err(e) => { warn!(error = %e, request_id = %id, "failed to read logs"); StatusCode::INTERNAL_SERVER_ERROR.into_response() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async fn list_jobs_grouped(axum::extract::State(state): axum::extract::State<AppState>) -> Response {
|
||||
// Fetch all jobs ordered by most recently updated first
|
||||
let rows_res: miette::Result<Vec<jobs::Model>> = jobs::Entity::find()
|
||||
.order_by_desc(jobs::Column::UpdatedAt)
|
||||
.all(&state.db)
|
||||
.await
|
||||
.into_diagnostic();
|
||||
|
||||
match rows_res {
|
||||
Ok(rows) => {
|
||||
// Group by (repo_url, commit_sha)
|
||||
let mut groups: BTreeMap<(String, String), Vec<jobs::Model>> = BTreeMap::new();
|
||||
for j in rows {
|
||||
groups
|
||||
.entry((j.repo_url.clone(), j.commit_sha.clone()))
|
||||
.or_default()
|
||||
.push(j);
|
||||
}
|
||||
// Build output
|
||||
let mut out: Vec<JobGroup> = Vec::with_capacity(groups.len());
|
||||
for ((repo_url, commit_sha), mut items) in groups.into_iter() {
|
||||
// Ensure items are sorted by updated_at desc
|
||||
items.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
|
||||
let last_updated = items.first().map(|j| j.updated_at).unwrap_or_else(|| chrono::Utc::now());
|
||||
let jobs: Vec<JobSummary> = items
|
||||
.into_iter()
|
||||
.map(|j| JobSummary {
|
||||
request_id: j.request_id,
|
||||
runs_on: j.runs_on,
|
||||
state: j.state,
|
||||
updated_at: j.updated_at,
|
||||
links: JobLinks { logs: format!("/jobs/{}/logs", j.request_id) },
|
||||
})
|
||||
.collect();
|
||||
out.push(JobGroup {
|
||||
repo_url,
|
||||
commit_sha,
|
||||
last_updated,
|
||||
total_jobs: jobs.len(),
|
||||
jobs,
|
||||
});
|
||||
}
|
||||
// Sort groups by last_updated desc for convenience
|
||||
out.sort_by(|a, b| b.last_updated.cmp(&a.last_updated));
|
||||
Json(out).into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(error = %e, "failed to list jobs");
|
||||
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue