195 lines
9.4 KiB
Rust
195 lines
9.4 KiB
Rust
// file: crates/ksp-app-backfill-desk/src/backfill_status.rs
|
|
// version: 1
|
|
|
|
//! Safe latest-value monitoring projection for one Backfill Desk run.
|
|
|
|
use ts_rs::TS; // rust-rules: trait-import
|
|
|
|
/// Stable Tauri event carrying the latest complete safe Backfill run status.
|
|
pub(crate) const BACKFILL_STATUS_EVENT_NAME: &str = "ksp-backfill-status";
|
|
|
|
/// Frontend-safe latest-value projection of one concrete Backfill runtime snapshot.
|
|
#[derive(Clone, Debug, serde::Serialize, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_backfill_desk/backfill_status/BackfillRunStatusDto.ts")]
|
|
pub(crate) struct BackfillRunStatusDto {
|
|
/// Whether the represented Job is currently non-terminal.
|
|
pub(crate) active: bool,
|
|
/// Number of candidates admitted into hydration.
|
|
pub(crate) candidates_admitted: u32,
|
|
/// Number of admitted candidates that reached a known coordinator result.
|
|
pub(crate) candidates_finished: u32,
|
|
/// Number of candidates selected by discovery.
|
|
pub(crate) candidates_selected: u32,
|
|
/// Number of candidates cancelled before Store submission.
|
|
pub(crate) cancelled_candidates: u32,
|
|
/// Whether a safe caller-owned checkpoint currently exists.
|
|
pub(crate) checkpoint_present: bool,
|
|
/// Normal terminal completion classification, when applicable.
|
|
pub(crate) completion: std::option::Option<String>,
|
|
/// Safe contiguous completion prefix retained by the current checkpoint frontier.
|
|
pub(crate) contiguous_completed: u32,
|
|
/// Number of Store content conflicts observed by the run.
|
|
pub(crate) conflicts: u32,
|
|
/// Safe discovery boundary code after discovery completes.
|
|
pub(crate) discovery_boundary: std::option::Option<String>,
|
|
/// Number of identical canonical RAW transactions already present.
|
|
pub(crate) entities_existing: u32,
|
|
/// Number of newly inserted canonical RAW transactions.
|
|
pub(crate) entities_inserted: u32,
|
|
/// Number of durable purge tombstones respected by normal Backfill persistence.
|
|
pub(crate) entities_purged: u32,
|
|
/// Stable fatal error code retained by a failed Job, when applicable.
|
|
pub(crate) failure_code: std::option::Option<String>,
|
|
/// Stable fatal error domain retained by a failed Job, when applicable.
|
|
pub(crate) failure_domain: std::option::Option<String>,
|
|
/// Number of known candidate outcomes blocking the contiguous frontier.
|
|
pub(crate) holes: u32,
|
|
/// Backend-generated logical Job identity.
|
|
pub(crate) job_id: String,
|
|
/// Stable concrete Job family code.
|
|
pub(crate) job_kind: String,
|
|
/// Greatest observed candidate concurrency.
|
|
pub(crate) maximum_in_flight: u32,
|
|
/// Number of `getTransaction = null` candidates.
|
|
pub(crate) missing: u32,
|
|
/// Number of acquisition observations already durable.
|
|
pub(crate) observations_existing: u32,
|
|
/// Number of newly inserted acquisition observations.
|
|
pub(crate) observations_inserted: u32,
|
|
/// Stable concrete Backfill runtime phase.
|
|
pub(crate) phase: String,
|
|
/// Monotone latest-value notification sequence encoded as decimal text.
|
|
pub(crate) sequence: String,
|
|
/// Stable Backfill scope category without address/signature payloads.
|
|
pub(crate) scope_kind: String,
|
|
/// Stable generic Job lifecycle state.
|
|
pub(crate) state: String,
|
|
/// Whether this latest value is terminal.
|
|
pub(crate) terminal: bool,
|
|
}
|
|
|
|
/// Projects one complete Backfill latest-value notification to the safe desktop monitoring contract.
|
|
pub(crate) fn project_backfill_status(
|
|
notification: &ksp_job_api::JobNotification<ksp_job_backfill_lib::BackfillJobSnapshot>,
|
|
) -> ksp_core_lib::Result<BackfillRunStatusDto> {
|
|
let snapshot = notification.snapshot();
|
|
let state = notification.state();
|
|
let failure = snapshot.failure_code();
|
|
let candidates_admitted = usize_to_u32(snapshot.candidates_admitted(), "candidates_admitted");
|
|
let candidates_admitted = match candidates_admitted {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let candidates_finished = usize_to_u32(snapshot.candidates_finished(), "candidates_finished");
|
|
let candidates_finished = match candidates_finished {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let candidates_selected = usize_to_u32(snapshot.candidates_selected(), "candidates_selected");
|
|
let candidates_selected = match candidates_selected {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let cancelled_candidates = usize_to_u32(snapshot.cancelled_candidates(), "cancelled_candidates");
|
|
let cancelled_candidates = match cancelled_candidates {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let contiguous_completed = usize_to_u32(snapshot.contiguous_completed(), "contiguous_completed");
|
|
let contiguous_completed = match contiguous_completed {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let conflicts = usize_to_u32(snapshot.conflicts(), "conflicts");
|
|
let conflicts = match conflicts {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let entities_existing = usize_to_u32(snapshot.entities_existing(), "entities_existing");
|
|
let entities_existing = match entities_existing {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let entities_inserted = usize_to_u32(snapshot.entities_inserted(), "entities_inserted");
|
|
let entities_inserted = match entities_inserted {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let entities_purged = usize_to_u32(snapshot.entities_purged(), "entities_purged");
|
|
let entities_purged = match entities_purged {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let holes = usize_to_u32(snapshot.holes(), "holes");
|
|
let holes = match holes {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let maximum_in_flight = usize_to_u32(snapshot.maximum_in_flight(), "maximum_in_flight");
|
|
let maximum_in_flight = match maximum_in_flight {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let missing = usize_to_u32(snapshot.missing(), "missing");
|
|
let missing = match missing {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let observations_existing = usize_to_u32(snapshot.observations_existing(), "observations_existing");
|
|
let observations_existing = match observations_existing {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let observations_inserted = usize_to_u32(snapshot.observations_inserted(), "observations_inserted");
|
|
let observations_inserted = match observations_inserted {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(BackfillRunStatusDto {
|
|
active: !state.is_terminal(),
|
|
candidates_admitted,
|
|
candidates_finished,
|
|
candidates_selected,
|
|
cancelled_candidates,
|
|
checkpoint_present: snapshot.checkpoint().is_some(),
|
|
completion: state.completion().map(|completion| return completion.code().to_owned()),
|
|
contiguous_completed,
|
|
conflicts,
|
|
discovery_boundary: snapshot.discovery_boundary().map(|boundary| return boundary.code().to_owned()),
|
|
entities_existing,
|
|
entities_inserted,
|
|
entities_purged,
|
|
failure_code: failure.map(|code| return code.code().to_owned()),
|
|
failure_domain: failure.map(|code| return code.domain().to_owned()),
|
|
holes,
|
|
job_id: notification.id().as_str().to_owned(),
|
|
job_kind: notification.kind().as_str().to_owned(),
|
|
maximum_in_flight,
|
|
missing,
|
|
observations_existing,
|
|
observations_inserted,
|
|
phase: snapshot.phase().code().to_owned(),
|
|
sequence: notification.sequence().value().to_string(),
|
|
scope_kind: snapshot.scope_kind().code().to_owned(),
|
|
state: state.code().to_owned(),
|
|
terminal: state.is_terminal(),
|
|
});
|
|
}
|
|
|
|
fn usize_to_u32(value: usize, field: &'static str) -> ksp_core_lib::Result<u32> {
|
|
let converted = u32::try_from(value);
|
|
return match converted {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "Backfill Desk cannot project a bounded Job counter to the frontend")
|
|
.with_context("field", field)
|
|
.with_source(error),
|
|
),
|
|
};
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "../unit_tests/backfill_status.rs"]
|
|
mod tests;
|