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 sea_orm_migration::MigratorTrait;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
use uuid::Uuid;
|
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)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(name = "solstice-logs", version, about = "Solstice CI — Logs Service")]
|
#[command(name = "solstice-logs", version, about = "Solstice CI — Logs Service")]
|
||||||
struct Opts {
|
struct Opts {
|
||||||
|
|
@ -39,6 +63,7 @@ async fn main() -> Result<()> {
|
||||||
let router = Router::new()
|
let router = Router::new()
|
||||||
.route("/jobs/{request_id}/logs", get(list_logs))
|
.route("/jobs/{request_id}/logs", get(list_logs))
|
||||||
.route("/jobs/{request_id}/logs/{category}", get(get_logs_by_category))
|
.route("/jobs/{request_id}/logs/{category}", get(get_logs_by_category))
|
||||||
|
.route("/jobs", get(list_jobs_grouped))
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
|
|
||||||
let addr: SocketAddr = opts.http_addr.parse().expect("invalid HTTP_ADDR");
|
let addr: SocketAddr = opts.http_addr.parse().expect("invalid HTTP_ADDR");
|
||||||
|
|
@ -73,6 +98,25 @@ mod job_logs {
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
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)]
|
#[derive(Serialize, sea_orm::FromQueryResult)]
|
||||||
struct LogCategorySummary {
|
struct LogCategorySummary {
|
||||||
category: String,
|
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() }
|
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