388 lines
16 KiB
Rust
388 lines
16 KiB
Rust
use super::*;
|
|
use super::storage::BELStorage;
|
|
use std::sync::Arc;
|
|
use std::collections::HashMap;
|
|
|
|
/// App-layer aggregation that scans storage events
|
|
pub struct BELQueryEngine {
|
|
storage: Arc<dyn BELStorage>,
|
|
}
|
|
|
|
impl BELQueryEngine {
|
|
pub fn new(storage: Arc<dyn BELStorage>) -> Self {
|
|
Self { storage }
|
|
}
|
|
|
|
/// Get latest status for a partition by scanning recent events
|
|
pub async fn get_latest_partition_status(&self, partition_ref: &str) -> Result<Option<(PartitionStatus, i64)>> {
|
|
let filter = EventFilter {
|
|
partition_refs: vec![partition_ref.to_string()],
|
|
partition_patterns: vec![],
|
|
job_labels: vec![],
|
|
job_run_ids: vec![],
|
|
build_request_ids: vec![],
|
|
};
|
|
|
|
let events = self.storage.list_events(0, filter).await?;
|
|
self.aggregate_partition_status(&events.events)
|
|
}
|
|
|
|
/// Get all build requests that are currently building a partition
|
|
pub async fn get_active_builds_for_partition(&self, partition_ref: &str) -> Result<Vec<String>> {
|
|
let filter = EventFilter {
|
|
partition_refs: vec![partition_ref.to_string()],
|
|
partition_patterns: vec![],
|
|
job_labels: vec![],
|
|
job_run_ids: vec![],
|
|
build_request_ids: vec![],
|
|
};
|
|
|
|
let events = self.storage.list_events(0, filter).await?;
|
|
let mut active_builds = Vec::new();
|
|
let mut build_states: HashMap<String, BuildRequestStatus> = HashMap::new();
|
|
|
|
// Process events chronologically to track build states
|
|
for event in events.events {
|
|
match &event.event_type {
|
|
Some(crate::build_event::EventType::BuildRequestEvent(br_event)) => {
|
|
if let Ok(status) = BuildRequestStatus::try_from(br_event.status_code) {
|
|
build_states.insert(event.build_request_id.clone(), status);
|
|
}
|
|
}
|
|
Some(crate::build_event::EventType::PartitionEvent(p_event)) => {
|
|
if let Some(partition_event_ref) = &p_event.partition_ref {
|
|
if partition_event_ref.str == partition_ref {
|
|
// Check if this partition is actively being built
|
|
if let Ok(status) = PartitionStatus::try_from(p_event.status_code) {
|
|
if matches!(status, PartitionStatus::PartitionBuilding | PartitionStatus::PartitionAnalyzed) {
|
|
// Check if the build request is still active
|
|
if let Some(build_status) = build_states.get(&event.build_request_id) {
|
|
if matches!(build_status,
|
|
BuildRequestStatus::BuildRequestReceived |
|
|
BuildRequestStatus::BuildRequestPlanning |
|
|
BuildRequestStatus::BuildRequestExecuting |
|
|
BuildRequestStatus::BuildRequestAnalysisCompleted
|
|
) {
|
|
if !active_builds.contains(&event.build_request_id) {
|
|
active_builds.push(event.build_request_id.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
Ok(active_builds)
|
|
}
|
|
|
|
/// Get summary of a build request by aggregating its events
|
|
pub async fn get_build_request_summary(&self, build_id: &str) -> Result<BuildRequestSummary> {
|
|
let filter = EventFilter {
|
|
partition_refs: vec![],
|
|
partition_patterns: vec![],
|
|
job_labels: vec![],
|
|
job_run_ids: vec![],
|
|
build_request_ids: vec![build_id.to_string()],
|
|
};
|
|
|
|
let events = self.storage.list_events(0, filter).await?;
|
|
|
|
// If no events found, build doesn't exist
|
|
if events.events.is_empty() {
|
|
return Err(BuildEventLogError::QueryError(format!("Build request '{}' not found", build_id)));
|
|
}
|
|
|
|
let mut status = BuildRequestStatus::BuildRequestUnknown;
|
|
let mut requested_partitions = Vec::new();
|
|
let mut created_at = 0i64;
|
|
let mut updated_at = 0i64;
|
|
|
|
for event in events.events {
|
|
if event.timestamp > 0 {
|
|
if created_at == 0 || event.timestamp < created_at {
|
|
created_at = event.timestamp;
|
|
}
|
|
if event.timestamp > updated_at {
|
|
updated_at = event.timestamp;
|
|
}
|
|
}
|
|
|
|
if let Some(crate::build_event::EventType::BuildRequestEvent(br_event)) = &event.event_type {
|
|
if let Ok(event_status) = BuildRequestStatus::try_from(br_event.status_code) {
|
|
status = event_status;
|
|
}
|
|
if !br_event.requested_partitions.is_empty() {
|
|
requested_partitions = br_event.requested_partitions.iter()
|
|
.map(|p| p.str.clone())
|
|
.collect();
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(BuildRequestSummary {
|
|
build_request_id: build_id.to_string(),
|
|
status,
|
|
requested_partitions,
|
|
created_at,
|
|
updated_at,
|
|
})
|
|
}
|
|
|
|
/// List build requests with pagination and filtering
|
|
pub async fn list_build_requests(&self, request: BuildsListRequest) -> Result<BuildsListResponse> {
|
|
// For now, scan all events and aggregate
|
|
let filter = EventFilter {
|
|
partition_refs: vec![],
|
|
partition_patterns: vec![],
|
|
job_labels: vec![],
|
|
job_run_ids: vec![],
|
|
build_request_ids: vec![],
|
|
};
|
|
|
|
let events = self.storage.list_events(0, filter).await?;
|
|
let mut build_summaries: HashMap<String, BuildRequestSummary> = HashMap::new();
|
|
|
|
// Aggregate by build request ID
|
|
for event in events.events {
|
|
if let Some(crate::build_event::EventType::BuildRequestEvent(br_event)) = &event.event_type {
|
|
let build_id = &event.build_request_id;
|
|
let entry = build_summaries.entry(build_id.clone()).or_insert_with(|| {
|
|
BuildRequestSummary {
|
|
build_request_id: build_id.clone(),
|
|
status: BuildRequestStatus::BuildRequestUnknown,
|
|
requested_partitions: Vec::new(),
|
|
created_at: event.timestamp,
|
|
updated_at: event.timestamp,
|
|
}
|
|
});
|
|
|
|
if let Ok(status) = BuildRequestStatus::try_from(br_event.status_code) {
|
|
entry.status = status;
|
|
}
|
|
entry.updated_at = event.timestamp.max(entry.updated_at);
|
|
if !br_event.requested_partitions.is_empty() {
|
|
entry.requested_partitions = br_event.requested_partitions.iter()
|
|
.map(|p| p.str.clone())
|
|
.collect();
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut builds: Vec<_> = build_summaries.into_values().collect();
|
|
builds.sort_by(|a, b| b.created_at.cmp(&a.created_at)); // Most recent first
|
|
|
|
// Apply status filter if provided
|
|
if let Some(status_filter) = &request.status_filter {
|
|
if let Ok(filter_status) = status_filter.parse::<i32>() {
|
|
if let Ok(status) = BuildRequestStatus::try_from(filter_status) {
|
|
builds.retain(|b| b.status == status);
|
|
}
|
|
}
|
|
}
|
|
|
|
let total_count = builds.len() as u32;
|
|
let offset = request.offset.unwrap_or(0) as usize;
|
|
let limit = request.limit.unwrap_or(50) as usize;
|
|
|
|
let paginated_builds = builds.into_iter()
|
|
.skip(offset)
|
|
.take(limit)
|
|
.map(|summary| BuildSummary {
|
|
build_request_id: summary.build_request_id,
|
|
status_code: summary.status as i32,
|
|
status_name: summary.status.to_display_string(),
|
|
requested_partitions: summary.requested_partitions.into_iter()
|
|
.map(|s| PartitionRef { str: s })
|
|
.collect(),
|
|
total_jobs: 0, // TODO: Implement
|
|
completed_jobs: 0, // TODO: Implement
|
|
failed_jobs: 0, // TODO: Implement
|
|
cancelled_jobs: 0, // TODO: Implement
|
|
requested_at: summary.created_at,
|
|
started_at: None, // TODO: Implement
|
|
completed_at: None, // TODO: Implement
|
|
duration_ms: None, // TODO: Implement
|
|
cancelled: false, // TODO: Implement
|
|
})
|
|
.collect();
|
|
|
|
Ok(BuildsListResponse {
|
|
builds: paginated_builds,
|
|
total_count,
|
|
has_more: (offset + limit) < total_count as usize,
|
|
})
|
|
}
|
|
|
|
/// Get activity summary for dashboard
|
|
pub async fn get_activity_summary(&self) -> Result<ActivitySummary> {
|
|
let builds_response = self.list_build_requests(BuildsListRequest {
|
|
limit: Some(5),
|
|
offset: Some(0),
|
|
status_filter: None,
|
|
}).await?;
|
|
|
|
let active_builds_count = builds_response.builds.iter()
|
|
.filter(|b| matches!(
|
|
BuildRequestStatus::try_from(b.status_code).unwrap_or(BuildRequestStatus::BuildRequestUnknown),
|
|
BuildRequestStatus::BuildRequestReceived |
|
|
BuildRequestStatus::BuildRequestPlanning |
|
|
BuildRequestStatus::BuildRequestExecuting |
|
|
BuildRequestStatus::BuildRequestAnalysisCompleted
|
|
))
|
|
.count() as u32;
|
|
|
|
let recent_builds = builds_response.builds.into_iter()
|
|
.map(|b| BuildRequestSummary {
|
|
build_request_id: b.build_request_id,
|
|
status: BuildRequestStatus::try_from(b.status_code).unwrap_or(BuildRequestStatus::BuildRequestUnknown),
|
|
requested_partitions: b.requested_partitions.into_iter().map(|p| p.str).collect(),
|
|
created_at: b.requested_at,
|
|
updated_at: b.completed_at.unwrap_or(b.requested_at),
|
|
})
|
|
.collect();
|
|
|
|
// For partitions, we'd need a separate implementation
|
|
let recent_partitions = Vec::new(); // TODO: Implement partition listing
|
|
|
|
Ok(ActivitySummary {
|
|
active_builds_count,
|
|
recent_builds,
|
|
recent_partitions,
|
|
total_partitions_count: 0, // TODO: Implement
|
|
})
|
|
}
|
|
|
|
/// Helper to aggregate partition status from events
|
|
fn aggregate_partition_status(&self, events: &[BuildEvent]) -> Result<Option<(PartitionStatus, i64)>> {
|
|
let mut latest_status = None;
|
|
let mut latest_timestamp = 0i64;
|
|
|
|
// Look for the most recent partition event for this partition
|
|
for event in events {
|
|
if let Some(crate::build_event::EventType::PartitionEvent(p_event)) = &event.event_type {
|
|
if event.timestamp >= latest_timestamp {
|
|
if let Ok(status) = PartitionStatus::try_from(p_event.status_code) {
|
|
latest_status = Some(status);
|
|
latest_timestamp = event.timestamp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(latest_status.map(|status| (status, latest_timestamp)))
|
|
}
|
|
|
|
/// Get build request ID that created an available partition
|
|
pub async fn get_build_request_for_available_partition(&self, partition_ref: &str) -> Result<Option<String>> {
|
|
let filter = EventFilter {
|
|
partition_refs: vec![partition_ref.to_string()],
|
|
partition_patterns: vec![],
|
|
job_labels: vec![],
|
|
job_run_ids: vec![],
|
|
build_request_ids: vec![],
|
|
};
|
|
|
|
let events = self.storage.list_events(0, filter).await?;
|
|
|
|
// Find the most recent PARTITION_AVAILABLE event
|
|
let mut latest_available_build_id = None;
|
|
let mut latest_timestamp = 0i64;
|
|
|
|
for event in events.events {
|
|
if let Some(crate::build_event::EventType::PartitionEvent(p_event)) = &event.event_type {
|
|
if let Some(partition_event_ref) = &p_event.partition_ref {
|
|
if partition_event_ref.str == partition_ref {
|
|
if let Ok(status) = PartitionStatus::try_from(p_event.status_code) {
|
|
if status == PartitionStatus::PartitionAvailable && event.timestamp >= latest_timestamp {
|
|
latest_available_build_id = Some(event.build_request_id.clone());
|
|
latest_timestamp = event.timestamp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(latest_available_build_id)
|
|
}
|
|
|
|
/// Append an event to storage
|
|
pub async fn append_event(&self, event: BuildEvent) -> Result<i64> {
|
|
self.storage.append_event(event).await
|
|
}
|
|
|
|
/// Get all events for a specific partition
|
|
pub async fn get_partition_events(&self, partition_ref: &str, _limit: Option<u32>) -> Result<Vec<BuildEvent>> {
|
|
let filter = EventFilter {
|
|
partition_refs: vec![partition_ref.to_string()],
|
|
partition_patterns: vec![],
|
|
job_labels: vec![],
|
|
job_run_ids: vec![],
|
|
build_request_ids: vec![],
|
|
};
|
|
|
|
let events = self.storage.list_events(0, filter).await?;
|
|
Ok(events.events)
|
|
}
|
|
|
|
/// Execute a raw SQL query (for backwards compatibility)
|
|
pub async fn execute_query(&self, _query: &str) -> Result<QueryResult> {
|
|
// TODO: Implement SQL query execution if needed
|
|
// For now, return empty result to avoid compilation errors
|
|
Ok(QueryResult {
|
|
columns: vec![],
|
|
rows: vec![],
|
|
})
|
|
}
|
|
|
|
/// Get all events in a timestamp range
|
|
pub async fn get_events_in_range(&self, _start: i64, _end: i64) -> Result<Vec<BuildEvent>> {
|
|
// TODO: Implement range filtering
|
|
// For now, get all events
|
|
let filter = EventFilter {
|
|
partition_refs: vec![],
|
|
partition_patterns: vec![],
|
|
job_labels: vec![],
|
|
job_run_ids: vec![],
|
|
build_request_ids: vec![],
|
|
};
|
|
|
|
let events = self.storage.list_events(0, filter).await?;
|
|
Ok(events.events)
|
|
}
|
|
|
|
/// Get all events for a specific job run
|
|
pub async fn get_job_run_events(&self, job_run_id: &str) -> Result<Vec<BuildEvent>> {
|
|
let filter = EventFilter {
|
|
partition_refs: vec![],
|
|
partition_patterns: vec![],
|
|
job_labels: vec![],
|
|
job_run_ids: vec![job_run_id.to_string()],
|
|
build_request_ids: vec![],
|
|
};
|
|
|
|
let events = self.storage.list_events(0, filter).await?;
|
|
Ok(events.events)
|
|
}
|
|
|
|
/// Get all events for a specific build request
|
|
pub async fn get_build_request_events(&self, build_request_id: &str, _limit: Option<u32>) -> Result<Vec<BuildEvent>> {
|
|
let filter = EventFilter {
|
|
partition_refs: vec![],
|
|
partition_patterns: vec![],
|
|
job_labels: vec![],
|
|
job_run_ids: vec![],
|
|
build_request_ids: vec![build_request_id.to_string()],
|
|
};
|
|
|
|
let events = self.storage.list_events(0, filter).await?;
|
|
Ok(events.events)
|
|
}
|
|
}
|
|
|
|
|