This commit is contained in:
Stuart Axelbrooke 2025-08-20 23:34:37 -07:00
parent bfcf7cdfd2
commit 8ba4820654
23 changed files with 301 additions and 270 deletions

View file

@ -900,7 +900,7 @@ async fn handle_builds_command(matches: &ArgMatches, event_log_uri: &str) -> Res
}
_ => {
println!("Build: {}", detail.build_request_id);
println!("Status: {} ({})", detail.status_name, detail.status_code);
println!("Status: {} ({})", detail.status.clone().unwrap().name, detail.status.unwrap().code);
println!("Requested partitions: {}", detail.requested_partitions.len());
println!("Total jobs: {}", detail.total_jobs);
println!("Completed jobs: {}", detail.completed_jobs);
@ -958,11 +958,7 @@ async fn handle_builds_command(matches: &ArgMatches, event_log_uri: &str) -> Res
println!("\nTimeline ({} events):", detail.timeline.len());
for event in detail.timeline {
let timestamp = format_timestamp(event.timestamp);
let status_info = if let Some(ref status_name) = event.status_name {
format!(" -> {}", status_name)
} else {
String::new()
};
let status_info = event.status.unwrap().name;
println!(" {} [{}]{} {}", timestamp, event.event_type, status_info, event.message);
if let Some(ref reason) = event.cancel_reason {

View file

@ -44,6 +44,7 @@ genrule(
"typescript_generated/src/models/BuildRequest.ts",
"typescript_generated/src/models/BuildRequestResponse.ts",
"typescript_generated/src/models/BuildSummary.ts",
"typescript_generated/src/models/BuildRequestStatus.ts",
"typescript_generated/src/models/BuildTimelineEvent.ts",
"typescript_generated/src/models/BuildsListApiResponse.ts",
"typescript_generated/src/models/BuildsListResponse.ts",
@ -118,6 +119,7 @@ genrule(
cp $$TEMP_DIR/src/models/BuildRequest.ts $(location typescript_generated/src/models/BuildRequest.ts)
cp $$TEMP_DIR/src/models/BuildRequestResponse.ts $(location typescript_generated/src/models/BuildRequestResponse.ts)
cp $$TEMP_DIR/src/models/BuildSummary.ts $(location typescript_generated/src/models/BuildSummary.ts)
cp $$TEMP_DIR/src/models/BuildRequestStatus.ts $(location typescript_generated/src/models/BuildRequestStatus.ts)
cp $$TEMP_DIR/src/models/BuildTimelineEvent.ts $(location typescript_generated/src/models/BuildTimelineEvent.ts)
cp $$TEMP_DIR/src/models/BuildsListApiResponse.ts $(location typescript_generated/src/models/BuildsListApiResponse.ts)
cp $$TEMP_DIR/src/models/BuildsListResponse.ts $(location typescript_generated/src/models/BuildsListResponse.ts)

View file

@ -220,8 +220,8 @@ export const RecentActivity: TypedComponent<RecentActivityAttrs> = {
}, build.build_request_id)
]),
m('td', [
// KEY FIX: build.status_name is now always a string, prevents runtime errors
m(BuildStatusBadge, { status: build.status_name })
// KEY FIX: build.status.name is now always a string, prevents runtime errors
m(BuildStatusBadge, { status: build.status.name })
]),
m('td.text-sm.opacity-70', formatTime(build.requested_at)),
])
@ -276,7 +276,7 @@ export const RecentActivity: TypedComponent<RecentActivityAttrs> = {
}, partition.partition_ref.str)
]),
m('td', [
// KEY FIX: partition.status_name is now always a string, prevents runtime errors
// KEY FIX: partition.status.name is now always a string, prevents runtime errors
m(PartitionStatusBadge, { status: partition.status_name })
]),
m('td.text-sm.opacity-70',
@ -402,8 +402,8 @@ export const BuildStatus: TypedComponent<BuildStatusAttrs> = {
startPolling() {
// Use different poll intervals based on build status
const isActive = this.data?.status_name === 'EXECUTING' ||
this.data?.status_name === 'PLANNING';
const isActive = this.data?.status.name === 'EXECUTING' ||
this.data?.status.name === 'PLANNING';
const interval = isActive ? 2000 : 10000; // 2s for active, 10s for completed
pollingManager.startPolling(`build-status-${this.buildId}`, () => {
@ -457,7 +457,7 @@ export const BuildStatus: TypedComponent<BuildStatusAttrs> = {
{stage: 'Build Requested', time: build.requested_at, icon: '🕚'},
...(build.started_at ? [{stage: 'Build Started', time: build.started_at, icon: '▶️'}] : []),
// ...(this.data.events as BuildEvent[]).filter(ev => ev.job_event !== null).map((ev) => ({
// stage: ev.job_event.status_name, time: ev.timestamp, icon: '🙃'
// stage: ev.job_event.status.name, time: ev.timestamp, icon: '🙃'
// })),
...(build.completed_at ? [{stage: 'Build Completed', time: build.completed_at, icon: '✅'}] : []),
];
@ -472,7 +472,7 @@ export const BuildStatus: TypedComponent<BuildStatusAttrs> = {
m('.stat.bg-base-100.shadow.rounded-lg.p-4', [
m('.stat-title', 'Status'),
m('.stat-value.text-2xl', [
m(BuildStatusBadge, { status: build.status_name, size: 'lg' })
m(BuildStatusBadge, { status: build.status.name, size: 'lg' })
])
]),
m('.stat.bg-base-100.shadow.rounded-lg.p-4', [
@ -518,7 +518,7 @@ export const BuildStatus: TypedComponent<BuildStatusAttrs> = {
m('.partition-status.flex.justify-between.items-center', [
// CLEAN: Always string status, no nested object access
m(PartitionStatusBadge, {
status: partitionStatus?.status_name || 'Loading...',
status: partitionStatus?.status.name || 'Loading...',
size: 'sm'
}),
partitionStatus?.last_updated ?
@ -905,15 +905,15 @@ export const PartitionStatus: TypedComponent<PartitionStatusAttrs> = {
// Update status based on event type
if (event.eventType === 'build_request') {
if (event.message?.includes('completed') || event.message?.includes('successful')) {
build.status_name = 'Completed';
build.status.name = 'Completed';
build.completedAt = event.timestamp;
} else if (event.message?.includes('failed') || event.message?.includes('error')) {
build.status_name = 'Failed';
build.status.name = 'Failed';
build.completedAt = event.timestamp;
} else if (event.message?.includes('executing') || event.message?.includes('running')) {
build.status_name = 'Executing';
build.status.name = 'Executing';
} else if (event.message?.includes('planning')) {
build.status_name = 'Planning';
build.status.name = 'Planning';
}
}
}
@ -1012,7 +1012,7 @@ export const PartitionStatus: TypedComponent<PartitionStatusAttrs> = {
]),
m('div.partition-meta.flex.gap-4.items-center.mb-4', [
m(PartitionStatusBadge, { status: this.data?.status_name || 'Unknown', size: 'lg' }),
m(PartitionStatusBadge, { status: this.data?.status.name || 'Unknown', size: 'lg' }),
this.data?.last_updated ?
m('.timestamp.text-sm.opacity-70',
`Last updated: ${formatDateTime(this.data.last_updated)}`) : null,
@ -1051,7 +1051,7 @@ export const PartitionStatus: TypedComponent<PartitionStatusAttrs> = {
}, build.id)
]),
m('td', [
m(BuildStatusBadge, { status: build.status_name })
m(BuildStatusBadge, { status: build.status.name })
]),
m('td.text-sm.opacity-70',
formatDateTime(build.startedAt)),

View file

@ -38,8 +38,7 @@ const apiClient = new DefaultApi(apiConfig);
function transformBuildSummary(apiResponse: BuildSummary): DashboardBuild {
return {
build_request_id: apiResponse.build_request_id,
status_code: apiResponse.status_code,
status_name: apiResponse.status_name,
status: apiResponse.status!,
requested_partitions: apiResponse.requested_partitions, // Keep as PartitionRef array
total_jobs: apiResponse.total_jobs,
completed_jobs: apiResponse.completed_jobs,
@ -56,8 +55,7 @@ function transformBuildSummary(apiResponse: BuildSummary): DashboardBuild {
function transformBuildDetail(apiResponse: BuildDetailResponse): DashboardBuild {
return {
build_request_id: apiResponse.build_request_id,
status_code: apiResponse.status_code,
status_name: apiResponse.status_name,
status: apiResponse.status!,
requested_partitions: apiResponse.requested_partitions, // Keep as PartitionRef array
total_jobs: apiResponse.total_jobs,
completed_jobs: apiResponse.completed_jobs,

View file

@ -7,7 +7,8 @@ import {
BuildDetailResponse,
PartitionSummary,
JobSummary,
ActivityResponse
ActivityResponse,
BuildRequestStatus
} from '../client/typescript_generated/src/index';
// Import types directly since we're now in the same ts_project
@ -26,8 +27,7 @@ import {
function transformBuildSummary(apiResponse: BuildSummary): DashboardBuild {
return {
build_request_id: apiResponse.build_request_id,
status_code: apiResponse.status_code,
status_name: apiResponse.status_name,
status: apiResponse.status!,
requested_partitions: apiResponse.requested_partitions, // Keep as PartitionRef array
total_jobs: apiResponse.total_jobs,
completed_jobs: apiResponse.completed_jobs,
@ -44,8 +44,7 @@ function transformBuildSummary(apiResponse: BuildSummary): DashboardBuild {
function transformBuildDetail(apiResponse: BuildDetailResponse): DashboardBuild {
return {
build_request_id: apiResponse.build_request_id,
status_code: apiResponse.status_code,
status_name: apiResponse.status_name,
status: apiResponse.status!,
requested_partitions: apiResponse.requested_partitions, // Keep as PartitionRef array
total_jobs: apiResponse.total_jobs,
completed_jobs: apiResponse.completed_jobs,
@ -98,8 +97,7 @@ function transformActivityResponse(apiResponse: ActivityResponse): DashboardActi
// Test Data Mocks
const mockBuildSummary: BuildSummary = {
build_request_id: 'build-123',
status_code: 4, // BUILD_REQUEST_COMPLETED
status_name: 'COMPLETED',
status: {code: 4, name: 'COMPLETED'},
requested_partitions: [{ str: 'partition-1' }, { str: 'partition-2' }],
total_jobs: 5,
completed_jobs: 5,
@ -151,12 +149,12 @@ o.spec('Transformation Functions', () => {
const result = transformBuildSummary(mockBuildSummary);
// The key fix: status_name should be a string, status_code a number
o(typeof result.status_code).equals('number');
o(typeof result.status_name).equals('string');
o(result.status_name).equals('COMPLETED');
o(typeof result.status?.code).equals('number');
o(typeof result.status?.name).equals('string');
o(result.status.name).equals('COMPLETED');
// This should not throw (preventing the original runtime error)
o(() => result.status_name.toLowerCase()).notThrows('status_name.toLowerCase should work');
o(() => result.status.name.toLowerCase()).notThrows('status_name.toLowerCase should work');
});
o('transformBuildSummary handles null optional fields', () => {
@ -219,7 +217,7 @@ o.spec('Transformation Functions', () => {
// All nested objects should be properly transformed
o(result.recent_builds.length).equals(1);
o(typeof result.recent_builds[0]?.status_name).equals('string');
o(typeof result.recent_builds[0]?.status.name).equals('string');
o(result.recent_partitions.length).equals(1);
o(typeof result.recent_partitions[0]?.partition_ref).equals('object');
@ -233,8 +231,8 @@ o.spec('Transformation Functions', () => {
// 1. status_name.toLowerCase() - should not crash
result.recent_builds.forEach((build: DashboardBuild) => {
o(() => build.status_name.toLowerCase()).notThrows('build.status_name.toLowerCase should work');
o(build.status_name.toLowerCase()).equals('completed');
o(() => build.status.name.toLowerCase()).notThrows('build.status.name.toLowerCase should work');
o(build.status.name.toLowerCase()).equals('completed');
});
// 2. partition_ref.str access - should access string property

View file

@ -11,7 +11,8 @@ import {
JobMetricsResponse,
JobDailyStats,
JobRunSummary,
PartitionRef
PartitionRef,
BuildRequestStatus
} from '../client/typescript_generated/src/index';
// Dashboard-optimized types - canonical frontend types independent of backend schema
@ -19,8 +20,7 @@ import {
export interface DashboardBuild {
build_request_id: string;
status_code: number;
status_name: string;
status: BuildRequestStatus;
requested_partitions: PartitionRef[];
total_jobs: number;
completed_jobs: number;
@ -66,8 +66,7 @@ export interface DashboardActivity {
// Dashboard timeline event types for consistent UI handling
export interface DashboardBuildTimelineEvent {
timestamp: number;
status_code: number;
status_name: string;
status: BuildRequestStatus;
message: string;
event_type: string;
cancel_reason?: string;
@ -75,8 +74,7 @@ export interface DashboardBuildTimelineEvent {
export interface DashboardPartitionTimelineEvent {
timestamp: number;
status_code: number;
status_name: string;
status: BuildRequestStatus;
message: string;
build_request_id: string;
job_run_id?: string;
@ -249,8 +247,8 @@ export function isDashboardActivity(data: any): data is DashboardActivity {
export function isDashboardBuild(data: any): data is DashboardBuild {
return data &&
typeof data.build_request_id === 'string' &&
typeof data.status_code === 'number' &&
typeof data.status_name === 'string' &&
typeof data.status?.code === 'number' &&
typeof data.status?.name === 'string' &&
typeof data.requested_at === 'number' &&
Array.isArray(data.requested_partitions);
}

View file

@ -239,9 +239,9 @@ message BuildRequestEvent {
// Optional status message
string message = 4;
// The comment attached to the request - contains arbitrary text
string comment = 5;
optional string comment = 5;
// The id of the want that triggered this build
string want_id = 6;
optional string want_id = 6;
}
// Partition state change event
@ -301,6 +301,7 @@ message WantEvent {
string want_id = 2;
// How this want was created
WantSource source = 3;
string comment = 4;
}
message PartitionWant {
@ -362,6 +363,7 @@ message BuildEvent {
// Event metadata
string event_id = 1; // UUID for this event
int64 timestamp = 2; // Unix timestamp (nanoseconds)
optional string build_request_id = 3;
// Event type and payload (one of)
oneof event_type {
@ -530,19 +532,18 @@ message BuildsListResponse {
message BuildSummary {
string build_request_id = 1;
BuildRequestStatusCode status_code = 2; // Enum for programmatic use
string status_name = 3; // Human-readable string
repeated PartitionRef requested_partitions = 4;
uint32 total_jobs = 5;
uint32 completed_jobs = 6;
uint32 failed_jobs = 7;
uint32 cancelled_jobs = 8;
int64 requested_at = 9;
optional int64 started_at = 10;
optional int64 completed_at = 11;
optional int64 duration_ms = 12;
bool cancelled = 13;
string comment = 14;
BuildRequestStatus status = 2;
repeated PartitionRef requested_partitions = 3;
uint32 total_jobs = 4;
uint32 completed_jobs = 5;
uint32 failed_jobs = 6;
uint32 cancelled_jobs = 7;
int64 requested_at = 8;
optional int64 started_at = 9;
optional int64 completed_at = 10;
optional int64 duration_ms = 11;
bool cancelled = 12;
optional string comment = 13;
}
//
@ -572,29 +573,27 @@ message BuildDetailRequest {
message BuildDetailResponse {
string build_request_id = 1;
BuildRequestStatusCode status_code = 2; // Enum for programmatic use
string status_name = 3; // Human-readable string
repeated PartitionRef requested_partitions = 4;
uint32 total_jobs = 5;
uint32 completed_jobs = 6;
uint32 failed_jobs = 7;
uint32 cancelled_jobs = 8;
int64 requested_at = 9;
optional int64 started_at = 10;
optional int64 completed_at = 11;
optional int64 duration_ms = 12;
bool cancelled = 13;
optional string cancel_reason = 14;
repeated BuildTimelineEvent timeline = 15;
BuildRequestStatus status = 2;
repeated PartitionRef requested_partitions = 3;
uint32 total_jobs = 4;
uint32 completed_jobs = 5;
uint32 failed_jobs = 6;
uint32 cancelled_jobs = 7;
int64 requested_at = 8;
optional int64 started_at = 9;
optional int64 completed_at = 10;
optional int64 duration_ms = 11;
bool cancelled = 12;
optional string cancel_reason = 13;
repeated BuildTimelineEvent timeline = 14;
}
message BuildTimelineEvent {
int64 timestamp = 1;
optional BuildRequestStatusCode status_code = 2; // Enum for programmatic use
optional string status_name = 3; // Human-readable string
string message = 4;
string event_type = 5;
optional string cancel_reason = 6;
optional BuildRequestStatus status = 2;
string message = 3;
string event_type = 4;
optional string cancel_reason = 5;
}
//

View file

@ -204,6 +204,8 @@ impl MockBuildEventLog {
Some(crate::build_event::EventType::PartitionInvalidationEvent(_)) => "partition_invalidation",
Some(crate::build_event::EventType::JobRunCancelEvent(_)) => "job_run_cancel",
Some(crate::build_event::EventType::BuildCancelEvent(_)) => "build_cancel",
Some(crate::build_event::EventType::WantEvent(_)) => "want",
Some(crate::build_event::EventType::TaintEvent(_)) => "taint",
None => "unknown",
},
event_data
@ -220,7 +222,7 @@ impl MockBuildEventLog {
"INSERT INTO build_request_events (event_id, status, requested_partitions, message) VALUES (?1, ?2, ?3, ?4)",
rusqlite::params![
event.event_id,
br_event.status_code.to_string(),
br_event.clone().status.unwrap().code.to_string(),
partitions_json,
br_event.message
],
@ -379,12 +381,13 @@ pub mod test_events {
BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id: build_request_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
build_request_id,
event_type: Some(build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestReceived as i32,
status_name: BuildRequestStatus::BuildRequestReceived.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestReceived.status()),
requested_partitions: partitions,
message: "Build request received".to_string(),
comment: None,
want_id: None,
})),
}
}
@ -398,12 +401,13 @@ pub mod test_events {
BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id: build_request_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
build_request_id,
event_type: Some(build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: status as i32,
status_name: status.to_display_string(),
status: Some(status.clone()),
requested_partitions: partitions,
message: format!("Build request status: {:?}", status),
message: format!("Build request status: {:?}", status.name),
comment: None,
want_id: None,
})),
}
}
@ -418,7 +422,7 @@ pub mod test_events {
BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id: build_request_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
build_request_id,
event_type: Some(build_event::EventType::PartitionEvent(PartitionEvent {
partition_ref: Some(partition_ref),
status_code: status as i32,
@ -440,7 +444,7 @@ pub mod test_events {
BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id: build_request_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
build_request_id,
event_type: Some(build_event::EventType::JobEvent(JobEvent {
job_run_id: job_run_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
job_label: Some(job_label),
@ -563,9 +567,10 @@ impl BELStorage for MockBELStorage {
// Apply filtering based on EventFilter
events.retain(|event| {
// Filter by build request IDs if specified
if !filter.build_request_ids.is_empty() {
if !filter.build_request_ids.contains(&event.build_request_id) {
if !filter.build_request_ids.contains(&event.build_request_id.clone().unwrap()) {
return false;
}
}

View file

@ -85,7 +85,7 @@ pub fn create_build_event(
BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id,
build_request_id: Some(build_request_id.clone()),
event_type: Some(event_type),
}
}

View file

@ -38,15 +38,16 @@ impl BELQueryEngine {
};
let events = self.storage.list_events(0, filter).await?;
let mut active_builds = Vec::new();
let mut build_states: HashMap<String, BuildRequestStatus> = HashMap::new();
let mut active_builds: Vec<String> = Vec::new();
let mut build_states: HashMap<String, BuildRequestStatusCode> = HashMap::new();
// Process events chronologically to track build states
for event in events.events {
let build_request_id = event.build_request_id.clone().unwrap();
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);
if let Ok(code) = BuildRequestStatusCode::try_from(br_event.clone().status.unwrap().code) {
build_states.insert(build_request_id.clone(), code);
}
}
Some(crate::build_event::EventType::PartitionEvent(p_event)) => {
@ -56,15 +57,15 @@ impl BELQueryEngine {
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 let Some(build_status) = build_states.get(&build_request_id) {
if matches!(build_status,
BuildRequestStatus::BuildRequestReceived |
BuildRequestStatus::BuildRequestPlanning |
BuildRequestStatus::BuildRequestExecuting |
BuildRequestStatus::BuildRequestAnalysisCompleted
BuildRequestStatusCode::BuildRequestReceived |
BuildRequestStatusCode::BuildRequestPlanning |
BuildRequestStatusCode::BuildRequestExecuting |
BuildRequestStatusCode::BuildRequestAnalysisCompleted
) {
if !active_builds.contains(&event.build_request_id) {
active_builds.push(event.build_request_id.clone());
if !active_builds.contains(&build_request_id) {
active_builds.push(build_request_id.clone());
}
}
}
@ -97,12 +98,12 @@ impl BELQueryEngine {
return Err(BuildEventLogError::QueryError(format!("Build request '{}' not found", build_id)));
}
let mut status = BuildRequestStatus::BuildRequestUnknown;
let mut status = BuildRequestStatusCode::BuildRequestUnknown.status();
let mut requested_partitions = Vec::new();
let mut created_at = 0i64;
let mut updated_at = 0i64;
for event in events.events {
for event in events.events.iter().filter(|event| event.build_request_id.is_some()) {
if event.timestamp > 0 {
if created_at == 0 || event.timestamp < created_at {
created_at = event.timestamp;
@ -113,7 +114,7 @@ impl BELQueryEngine {
}
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) {
if let Ok(event_status) = BuildRequestStatus::try_from(br_event.status.clone().unwrap()) {
status = event_status;
}
if !br_event.requested_partitions.is_empty() {
@ -148,20 +149,20 @@ impl BELQueryEngine {
let mut build_summaries: HashMap<String, BuildRequestSummary> = HashMap::new();
// Aggregate by build request ID
for event in events.events {
for event in events.events.iter().filter(|event| event.build_request_id.is_some()) {
if let Some(crate::build_event::EventType::BuildRequestEvent(br_event)) = &event.event_type {
let build_id = &event.build_request_id;
let build_id = &event.build_request_id.clone().unwrap();
let entry = build_summaries.entry(build_id.clone()).or_insert_with(|| {
BuildRequestSummary {
build_request_id: build_id.clone(),
status: BuildRequestStatus::BuildRequestUnknown,
status: BuildRequestStatusCode::BuildRequestUnknown.status(),
requested_partitions: Vec::new(),
created_at: event.timestamp,
updated_at: event.timestamp,
}
});
if let Ok(status) = BuildRequestStatus::try_from(br_event.status_code) {
if let Ok(status) = BuildRequestStatus::try_from(br_event.status.clone().unwrap()) {
entry.status = status;
}
entry.updated_at = event.timestamp.max(entry.updated_at);
@ -179,8 +180,8 @@ impl BELQueryEngine {
// 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);
if let Ok(status) = BuildRequestStatusCode::try_from(filter_status) {
builds.retain(|b| b.status.code == status as i32);
}
}
}
@ -194,8 +195,7 @@ impl BELQueryEngine {
.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(),
status: Some(summary.status),
requested_partitions: summary.requested_partitions.into_iter()
.map(|s| PartitionRef { str: s })
.collect(),
@ -208,6 +208,7 @@ impl BELQueryEngine {
completed_at: None, // TODO: Implement
duration_ms: None, // TODO: Implement
cancelled: false, // TODO: Implement
comment: None,
})
.collect();
@ -228,18 +229,18 @@ impl BELQueryEngine {
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
BuildRequestStatusCode::try_from(b.status.clone().unwrap().code).unwrap_or(BuildRequestStatusCode::BuildRequestUnknown),
BuildRequestStatusCode::BuildRequestReceived |
BuildRequestStatusCode::BuildRequestPlanning |
BuildRequestStatusCode::BuildRequestExecuting |
BuildRequestStatusCode::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),
status: b.status.unwrap_or(BuildRequestStatusCode::BuildRequestUnknown.status()),
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),
@ -299,7 +300,7 @@ impl BELQueryEngine {
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_available_build_id = event.build_request_id.clone();
latest_timestamp = event.timestamp;
}
}

View file

@ -35,10 +35,11 @@ impl EventWriter {
let event = create_build_event(
build_request_id,
build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestReceived as i32,
status_name: BuildRequestStatus::BuildRequestReceived.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestReceived.status()),
requested_partitions,
message: "Build request received".to_string(),
comment: None,
want_id: None,
}),
);
@ -57,10 +58,11 @@ impl EventWriter {
let event = create_build_event(
build_request_id,
build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: status as i32,
status_name: status.to_display_string(),
status: Some(status),
requested_partitions: vec![],
message,
comment: None,
want_id: None,
}),
);
@ -80,10 +82,11 @@ impl EventWriter {
let event = create_build_event(
build_request_id,
build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: status as i32,
status_name: status.to_display_string(),
status: Some(status),
requested_partitions,
message,
comment: None,
want_id: None,
}),
);
@ -104,7 +107,7 @@ impl EventWriter {
let event = BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id,
build_request_id: Some(build_request_id),
event_type: Some(build_event::EventType::PartitionEvent(PartitionEvent {
partition_ref: Some(partition_ref),
status_code: status as i32,
@ -136,7 +139,7 @@ impl EventWriter {
let event = BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id,
build_request_id: Some(build_request_id),
event_type: Some(build_event::EventType::PartitionInvalidationEvent(
PartitionInvalidationEvent {
partition_ref: Some(partition_ref),
@ -162,7 +165,7 @@ impl EventWriter {
let event = BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id,
build_request_id: Some(build_request_id),
event_type: Some(build_event::EventType::JobEvent(JobEvent {
job_run_id,
job_label: Some(job_label),
@ -194,7 +197,7 @@ impl EventWriter {
let event = BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id,
build_request_id: Some(build_request_id),
event_type: Some(build_event::EventType::JobEvent(JobEvent {
job_run_id,
job_label: Some(job_label),
@ -256,7 +259,7 @@ impl EventWriter {
let event = BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id,
build_request_id: Some(build_request_id),
event_type: Some(build_event::EventType::JobRunCancelEvent(JobRunCancelEvent {
job_run_id,
reason,
@ -285,22 +288,22 @@ impl EventWriter {
let latest_status = build_events.iter()
.rev()
.find_map(|e| match &e.event_type {
Some(build_event::EventType::BuildRequestEvent(br)) => Some(br.status_code),
Some(build_event::EventType::BuildRequestEvent(br)) => Some(br.clone().status.unwrap().code),
_ => None,
});
match latest_status {
Some(status) if status == BuildRequestStatus::BuildRequestCompleted as i32 => {
Some(status) if status == BuildRequestStatusCode::BuildRequestCompleted as i32 => {
return Err(BuildEventLogError::QueryError(
format!("Cannot cancel completed build: {}", build_request_id)
));
}
Some(status) if status == BuildRequestStatus::BuildRequestFailed as i32 => {
Some(status) if status == BuildRequestStatusCode::BuildRequestFailed as i32 => {
return Err(BuildEventLogError::QueryError(
format!("Cannot cancel failed build: {}", build_request_id)
));
}
Some(status) if status == BuildRequestStatus::BuildRequestCancelled as i32 => {
Some(status) if status == BuildRequestStatusCode::BuildRequestCancelled as i32 => {
return Err(BuildEventLogError::QueryError(
format!("Build already cancelled: {}", build_request_id)
));
@ -311,7 +314,7 @@ impl EventWriter {
let event = BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id: build_request_id.clone(),
build_request_id: Some(build_request_id.clone()),
event_type: Some(build_event::EventType::BuildCancelEvent(BuildCancelEvent {
reason,
})),
@ -322,7 +325,7 @@ impl EventWriter {
// Also emit a build request status update
self.update_build_status(
build_request_id,
BuildRequestStatus::BuildRequestCancelled,
BuildRequestStatusCode::BuildRequestCancelled.status(),
"Build cancelled by user".to_string(),
).await
}
@ -361,7 +364,7 @@ impl EventWriter {
let event = BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id,
build_request_id: Some(build_request_id),
event_type: Some(build_event::EventType::JobGraphEvent(JobGraphEvent {
job_graph: Some(job_graph),
message,
@ -391,19 +394,19 @@ mod tests {
// Test status updates
writer.update_build_status(
build_id.clone(),
BuildRequestStatus::BuildRequestPlanning,
BuildRequestStatusCode::BuildRequestPlanning.status(),
"Starting planning".to_string(),
).await.unwrap();
writer.update_build_status(
build_id.clone(),
BuildRequestStatus::BuildRequestExecuting,
BuildRequestStatusCode::BuildRequestExecuting.status(),
"Starting execution".to_string(),
).await.unwrap();
writer.update_build_status(
build_id.clone(),
BuildRequestStatus::BuildRequestCompleted,
BuildRequestStatusCode::BuildRequestCompleted.status(),
"Build completed successfully".to_string(),
).await.unwrap();
}

View file

@ -107,9 +107,8 @@ mod format_consistency_tests {
assert_eq!(JobStatus::from_display_string("completed"), Some(job_status));
// Test BuildRequestStatus conversions
let build_status = BuildRequestStatus::BuildRequestCompleted;
assert_eq!(build_status.to_display_string(), "completed");
assert_eq!(BuildRequestStatus::from_display_string("completed"), Some(build_status));
let build_status = BuildRequestStatusCode::BuildRequestCompleted.status();
assert_eq!(build_status.name, "completed");
// Test invalid conversions
assert_eq!(PartitionStatus::from_display_string("invalid"), None);

View file

@ -203,10 +203,11 @@ async fn plan(
let event = create_build_event(
build_request_id.to_string(),
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestReceived as i32,
status_name: BuildRequestStatus::BuildRequestReceived.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestReceived.status()),
requested_partitions: output_refs.iter().map(|s| PartitionRef { str: s.clone() }).collect(),
message: "Analysis started".to_string(),
comment: None,
want_id: None,
})
);
if let Err(e) = query_engine_ref.append_event(event).await {
@ -264,10 +265,11 @@ async fn plan(
let event = create_build_event(
build_request_id.to_string(),
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestPlanning as i32,
status_name: BuildRequestStatus::BuildRequestPlanning.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestPlanning.status()),
requested_partitions: output_refs.iter().map(|s| PartitionRef { str: s.clone() }).collect(),
message: "Graph analysis in progress".to_string(),
comment: None,
want_id: None,
})
);
if let Err(e) = query_engine_ref.append_event(event).await {
@ -334,10 +336,11 @@ async fn plan(
let event = create_build_event(
build_request_id.to_string(),
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestAnalysisCompleted as i32,
status_name: BuildRequestStatus::BuildRequestAnalysisCompleted.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestAnalysisCompleted.status()),
requested_partitions: output_refs.iter().map(|s| PartitionRef { str: s.clone() }).collect(),
message: format!("Analysis completed successfully, {} tasks planned", nodes.len()),
comment: None,
want_id: None,
})
);
if let Err(e) = query_engine.append_event(event).await {
@ -376,10 +379,11 @@ async fn plan(
let event = create_build_event(
build_request_id.to_string(),
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestFailed as i32,
status_name: BuildRequestStatus::BuildRequestFailed.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestFailed.status()),
requested_partitions: output_refs.iter().map(|s| PartitionRef { str: s.clone() }).collect(),
message: "No jobs found for requested partitions".to_string(),
comment: None,
want_id: None,
})
);
if let Err(e) = query_engine.append_event(event).await {

View file

@ -1,4 +1,4 @@
use databuild::{JobGraph, Task, JobStatus, BuildRequestStatus, PartitionStatus, BuildRequestEvent, JobEvent, PartitionEvent, PartitionRef};
use databuild::{JobGraph, Task, JobStatus, BuildRequestStatus, BuildRequestStatusCode, PartitionStatus, BuildRequestEvent, JobEvent, PartitionEvent, PartitionRef};
use databuild::event_log::{create_bel_query_engine, create_build_event};
use databuild::build_event::EventType;
use databuild::log_collector::{LogCollector, LogCollectorError};
@ -460,10 +460,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let event = create_build_event(
build_request_id.clone(),
EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestExecuting as i32,
status_name: BuildRequestStatus::BuildRequestExecuting.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestExecuting.status()),
requested_partitions: graph.outputs.clone(),
message: format!("Starting execution of {} jobs", graph.nodes.len()),
comment: None,
want_id: None,
})
);
if let Err(e) = query_engine.append_event(event).await {
@ -787,18 +788,19 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Log final build request status (existing detailed event)
if let Some(ref query_engine) = build_event_log {
let final_status = if failure_count > 0 || fail_fast_triggered {
BuildRequestStatus::BuildRequestFailed
BuildRequestStatusCode::BuildRequestFailed
} else {
BuildRequestStatus::BuildRequestCompleted
BuildRequestStatusCode::BuildRequestCompleted
};
let event = create_build_event(
build_request_id.clone(),
EventType::BuildRequestEvent(BuildRequestEvent {
status_code: final_status as i32,
status_name: final_status.to_display_string(),
status: Some(final_status.status()),
requested_partitions: graph.outputs.clone(),
message: format!("Execution completed: {} succeeded, {} failed", success_count, failure_count),
comment: None,
want_id: None,
})
);
if let Err(e) = query_engine.append_event(event).await {

View file

@ -10,10 +10,11 @@ pub fn create_build_request_received_event(
create_build_event(
build_request_id,
build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestReceived as i32,
status_name: BuildRequestStatus::BuildRequestReceived.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestReceived.status()),
requested_partitions,
message: "Build request received".to_string(),
comment: None,
want_id: None,
}),
)
}
@ -24,10 +25,11 @@ pub fn create_build_planning_started_event(
create_build_event(
build_request_id,
build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestPlanning as i32,
status_name: BuildRequestStatus::BuildRequestPlanning.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestPlanning.status()),
requested_partitions: vec![],
message: "Starting build planning".to_string(),
comment: None,
want_id: None,
}),
)
}
@ -38,10 +40,11 @@ pub fn create_build_execution_started_event(
create_build_event(
build_request_id,
build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestExecuting as i32,
status_name: BuildRequestStatus::BuildRequestExecuting.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestExecuting.status()),
requested_partitions: vec![],
message: "Starting build execution".to_string(),
comment: None,
want_id: None,
}),
)
}
@ -63,17 +66,18 @@ pub fn create_build_completed_event(
};
let status = match result {
super::BuildResult::Success { .. } => BuildRequestStatus::BuildRequestCompleted,
super::BuildResult::Failed { .. } | super::BuildResult::FailFast { .. } => BuildRequestStatus::BuildRequestFailed,
super::BuildResult::Success { .. } => BuildRequestStatusCode::BuildRequestCompleted.status(),
super::BuildResult::Failed { .. } | super::BuildResult::FailFast { .. } => BuildRequestStatusCode::BuildRequestFailed.status(),
};
create_build_event(
build_request_id,
build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: status as i32,
status_name: status.to_display_string(),
status: Some(status),
requested_partitions: vec![],
message,
comment: None,
want_id: None,
}),
)
}
@ -86,10 +90,11 @@ pub fn create_analysis_completed_event(
create_build_event(
build_request_id,
build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestAnalysisCompleted as i32,
status_name: BuildRequestStatus::BuildRequestAnalysisCompleted.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestAnalysisCompleted.status()),
requested_partitions,
message: format!("Analysis completed successfully, {} tasks planned", task_count),
comment: None,
want_id: None,
}),
)
}
@ -101,7 +106,7 @@ pub fn create_job_scheduled_event(
BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id,
build_request_id: Some(build_request_id),
event_type: Some(build_event::EventType::JobEvent(job_event.clone())),
}
}
@ -113,7 +118,7 @@ pub fn create_job_completed_event(
BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id,
build_request_id: Some(build_request_id),
event_type: Some(build_event::EventType::JobEvent(job_event.clone())),
}
}
@ -125,7 +130,7 @@ pub fn create_partition_available_event(
BuildEvent {
event_id: generate_event_id(),
timestamp: current_timestamp_nanos(),
build_request_id,
build_request_id: Some(build_request_id),
event_type: Some(build_event::EventType::PartitionEvent(partition_event.clone())),
}
}

View file

@ -66,7 +66,7 @@ impl BuildOrchestrator {
self.event_writer.update_build_status(
self.build_request_id.clone(),
BuildRequestStatus::BuildRequestPlanning,
BuildRequestStatusCode::BuildRequestPlanning.status(),
"Starting build planning".to_string(),
).await
.map_err(OrchestrationError::EventLog)?;
@ -80,7 +80,7 @@ impl BuildOrchestrator {
self.event_writer.update_build_status(
self.build_request_id.clone(),
BuildRequestStatus::BuildRequestExecuting,
BuildRequestStatusCode::BuildRequestExecuting.status(),
"Starting build execution".to_string(),
).await
.map_err(OrchestrationError::EventLog)?;
@ -95,22 +95,22 @@ impl BuildOrchestrator {
let (status, message) = match &result {
BuildResult::Success { jobs_completed } => {
(BuildRequestStatus::BuildRequestCompleted,
(BuildRequestStatusCode::BuildRequestCompleted,
format!("Build completed successfully with {} jobs", jobs_completed))
}
BuildResult::Failed { jobs_completed, jobs_failed } => {
(BuildRequestStatus::BuildRequestFailed,
(BuildRequestStatusCode::BuildRequestFailed,
format!("Build failed: {} jobs completed, {} jobs failed", jobs_completed, jobs_failed))
}
BuildResult::FailFast { trigger_job } => {
(BuildRequestStatus::BuildRequestFailed,
(BuildRequestStatusCode::BuildRequestFailed,
format!("Build failed fast due to job: {}", trigger_job))
}
};
self.event_writer.update_build_status(
self.build_request_id.clone(),
status,
status.status(),
message,
).await
.map_err(OrchestrationError::EventLog)?;
@ -122,7 +122,7 @@ impl BuildOrchestrator {
pub async fn emit_analysis_completed(&self, task_count: usize) -> Result<()> {
self.event_writer.update_build_status_with_partitions(
self.build_request_id.clone(),
BuildRequestStatus::BuildRequestAnalysisCompleted,
BuildRequestStatusCode::BuildRequestAnalysisCompleted.status(),
self.requested_partitions.clone(),
format!("Analysis completed successfully, {} tasks planned", task_count),
).await

View file

@ -62,7 +62,7 @@ impl BuildsRepository {
let builds = response.builds.into_iter().map(|build| {
BuildInfo {
build_request_id: build.build_request_id,
status: BuildRequestStatus::try_from(build.status_code).unwrap_or(BuildRequestStatus::BuildRequestUnknown),
status: build.status.clone().unwrap_or(BuildRequestStatusCode::BuildRequestUnknown.status()),
requested_partitions: build.requested_partitions,
requested_at: build.requested_at,
started_at: build.started_at,
@ -116,7 +116,7 @@ impl BuildsRepository {
let mut timeline = Vec::new();
for event in all_events {
if let Some(crate::build_event::EventType::BuildRequestEvent(br_event)) = &event.event_type {
if let Ok(status) = BuildRequestStatus::try_from(br_event.status_code) {
if let Some(status) = br_event.clone().status {
timeline.push(BuildEvent {
timestamp: event.timestamp,
event_type: "build_status".to_string(),
@ -151,8 +151,7 @@ impl BuildsRepository {
.into_iter()
.map(|event| ServiceBuildTimelineEvent {
timestamp: event.timestamp,
status_code: event.status.map(|s| s as i32),
status_name: event.status.map(|s| s.to_display_string()),
status: event.status,
message: event.message,
event_type: event.event_type,
cancel_reason: event.cancel_reason,
@ -161,8 +160,7 @@ impl BuildsRepository {
let response = BuildDetailResponse {
build_request_id: build_info.build_request_id,
status_code: build_info.status as i32,
status_name: build_info.status.to_display_string(),
status: Some(build_info.status),
requested_partitions: build_info.requested_partitions,
total_jobs: build_info.total_jobs as u32,
completed_jobs: build_info.completed_jobs as u32,
@ -200,18 +198,18 @@ impl BuildsRepository {
let (build, _timeline) = build_info.unwrap();
// Check if build is in a cancellable state
match build.status {
BuildRequestStatus::BuildRequestCompleted => {
match BuildRequestStatusCode::try_from(build.status.code) {
Ok(BuildRequestStatusCode::BuildRequestCompleted) => {
return Err(BuildEventLogError::QueryError(
format!("Cannot cancel completed build: {}", build_request_id)
));
}
BuildRequestStatus::BuildRequestFailed => {
Ok(BuildRequestStatusCode::BuildRequestFailed) => {
return Err(BuildEventLogError::QueryError(
format!("Cannot cancel failed build: {}", build_request_id)
));
}
BuildRequestStatus::BuildRequestCancelled => {
Ok(BuildRequestStatusCode::BuildRequestCancelled) => {
return Err(BuildEventLogError::QueryError(
format!("Build already cancelled: {}", build_request_id)
));
@ -225,10 +223,11 @@ impl BuildsRepository {
let cancel_event = create_build_event(
build_request_id.to_string(),
crate::build_event::EventType::BuildRequestEvent(crate::BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestCancelled as i32,
status_name: BuildRequestStatus::BuildRequestCancelled.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestCancelled.status()),
requested_partitions: build.requested_partitions,
message: format!("Build cancelled"),
comment: None,
want_id: None,
})
);
@ -250,8 +249,7 @@ impl BuildsRepository {
.into_iter()
.map(|build| crate::BuildSummary {
build_request_id: build.build_request_id,
status_code: build.status as i32,
status_name: build.status.to_display_string(),
status: Some(build.status),
requested_partitions: build.requested_partitions.into_iter().map(|p| crate::PartitionRef { str: p.str }).collect(),
total_jobs: build.total_jobs as u32,
completed_jobs: build.completed_jobs as u32,
@ -262,6 +260,7 @@ impl BuildsRepository {
completed_at: build.completed_at,
duration_ms: build.duration_ms,
cancelled: build.cancelled,
comment: None,
})
.collect();
@ -292,10 +291,10 @@ mod tests {
// Create events for multiple builds
let events = vec![
test_events::build_request_event(Some(build_id1.clone()), vec![partition1.clone()], BuildRequestStatus::BuildRequestReceived),
test_events::build_request_event(Some(build_id1.clone()), vec![partition1.clone()], BuildRequestStatus::BuildRequestCompleted),
test_events::build_request_event(Some(build_id2.clone()), vec![partition2.clone()], BuildRequestStatus::BuildRequestReceived),
test_events::build_request_event(Some(build_id2.clone()), vec![partition2.clone()], BuildRequestStatus::BuildRequestFailed),
test_events::build_request_event(Some(build_id1.clone()), vec![partition1.clone()], BuildRequestStatusCode::BuildRequestReceived.status()),
test_events::build_request_event(Some(build_id1.clone()), vec![partition1.clone()], BuildRequestStatusCode::BuildRequestCompleted.status()),
test_events::build_request_event(Some(build_id2.clone()), vec![partition2.clone()], BuildRequestStatusCode::BuildRequestReceived.status()),
test_events::build_request_event(Some(build_id2.clone()), vec![partition2.clone()], BuildRequestStatusCode::BuildRequestFailed.status()),
];
let query_engine = create_mock_bel_query_engine_with_events(events).await.unwrap();
@ -308,11 +307,11 @@ mod tests {
let build1 = builds.iter().find(|b| b.build_request_id == build_id1).unwrap();
let build2 = builds.iter().find(|b| b.build_request_id == build_id2).unwrap();
assert_eq!(build1.status, BuildRequestStatus::BuildRequestCompleted);
assert_eq!(build1.status, BuildRequestStatusCode::BuildRequestCompleted.status());
assert_eq!(build1.requested_partitions.len(), 1);
assert!(!build1.cancelled);
assert_eq!(build2.status, BuildRequestStatus::BuildRequestFailed);
assert_eq!(build2.status, BuildRequestStatusCode::BuildRequestFailed.status());
assert_eq!(build2.requested_partitions.len(), 1);
assert!(!build2.cancelled);
}
@ -323,10 +322,10 @@ mod tests {
let partition = PartitionRef { str: "analytics/daily".to_string() };
let events = vec![
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatus::BuildRequestReceived),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatus::BuildRequestPlanning),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatus::BuildRequestExecuting),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatus::BuildRequestCompleted),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatusCode::BuildRequestReceived.status()),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatusCode::BuildRequestPlanning.status()),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatusCode::BuildRequestExecuting.status()),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatusCode::BuildRequestCompleted.status()),
];
let query_engine = create_mock_bel_query_engine_with_events(events).await.unwrap();
@ -337,14 +336,14 @@ mod tests {
let (info, timeline) = result.unwrap();
assert_eq!(info.build_request_id, build_id);
assert_eq!(info.status, BuildRequestStatus::BuildRequestCompleted);
assert_eq!(info.status, BuildRequestStatusCode::BuildRequestCompleted.status());
assert!(!info.cancelled);
assert_eq!(timeline.len(), 4);
assert_eq!(timeline[0].status, Some(BuildRequestStatus::BuildRequestReceived));
assert_eq!(timeline[1].status, Some(BuildRequestStatus::BuildRequestPlanning));
assert_eq!(timeline[2].status, Some(BuildRequestStatus::BuildRequestExecuting));
assert_eq!(timeline[3].status, Some(BuildRequestStatus::BuildRequestCompleted));
assert_eq!(timeline[0].status, Some(BuildRequestStatusCode::BuildRequestReceived.status()));
assert_eq!(timeline[1].status, Some(BuildRequestStatusCode::BuildRequestPlanning.status()));
assert_eq!(timeline[2].status, Some(BuildRequestStatusCode::BuildRequestExecuting.status()));
assert_eq!(timeline[3].status, Some(BuildRequestStatusCode::BuildRequestCompleted.status()));
}
#[tokio::test]
@ -363,8 +362,8 @@ mod tests {
// Start with a running build
let events = vec![
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatus::BuildRequestReceived),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatus::BuildRequestExecuting),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatusCode::BuildRequestReceived.status()),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatusCode::BuildRequestExecuting.status()),
];
let query_engine = create_mock_bel_query_engine_with_events(events).await.unwrap();
@ -389,8 +388,8 @@ mod tests {
// Create a completed build
let events = vec![
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatus::BuildRequestReceived),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatus::BuildRequestCompleted),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatusCode::BuildRequestReceived.status()),
test_events::build_request_event(Some(build_id.clone()), vec![partition.clone()], BuildRequestStatusCode::BuildRequestCompleted.status()),
];
let query_engine = create_mock_bel_query_engine_with_events(events).await.unwrap();

View file

@ -102,7 +102,7 @@ impl JobsRepository {
let job_run = JobRunDetail {
job_run_id: j_event.job_run_id.clone(),
job_label: job_label.clone(),
build_request_id: event.build_request_id.clone(),
build_request_id: event.build_request_id.clone().unwrap(),
target_partitions: j_event.target_partitions.clone(),
status,
scheduled_at: event.timestamp,
@ -229,7 +229,7 @@ impl JobsRepository {
let job_run = JobRunDetail {
job_run_id: j_event.job_run_id.clone(),
job_label: job_label.to_string(),
build_request_id: event.build_request_id.clone(),
build_request_id: event.build_request_id.clone().unwrap(),
target_partitions: j_event.target_partitions.clone(),
status,
scheduled_at: event.timestamp,

View file

@ -164,7 +164,7 @@ impl PartitionsRepository {
timestamp: event.timestamp,
status: event_status,
message: p_event.message.clone(),
build_request_id: event.build_request_id,
build_request_id: event.build_request_id.unwrap(),
job_run_id: if p_event.job_run_id.is_empty() { None } else { Some(p_event.job_run_id.clone()) },
});
}

View file

@ -86,7 +86,7 @@ impl TasksRepository {
TaskInfo {
job_run_id: j_event.job_run_id.clone(),
job_label: job_label.clone(),
build_request_id: event.build_request_id.clone(),
build_request_id: event.build_request_id.clone().unwrap(),
status: JobStatus::JobUnknown,
target_partitions: j_event.target_partitions.clone(),
scheduled_at: event.timestamp,
@ -182,7 +182,7 @@ impl TasksRepository {
task_info = Some(TaskInfo {
job_run_id: j_event.job_run_id.clone(),
job_label: job_label.clone(),
build_request_id: event.build_request_id.clone(),
build_request_id: event.build_request_id.clone().unwrap(),
status: JobStatus::JobUnknown,
target_partitions: j_event.target_partitions.clone(),
scheduled_at: event.timestamp,

View file

@ -61,7 +61,7 @@ pub async fn submit_build_request(
// Create build request state
let build_state = BuildRequestState {
build_request_id: build_request_id.clone(),
status: BuildRequestStatus::BuildRequestReceived,
status: BuildRequestStatusCode::BuildRequestReceived.status(),
requested_partitions: request.partitions.clone(),
created_at: timestamp,
updated_at: timestamp,
@ -160,7 +160,7 @@ pub async fn cancel_build_request(
{
let mut active_builds = service.active_builds.write().await;
if let Some(build_state) = active_builds.get_mut(&build_request_id) {
build_state.status = BuildRequestStatus::BuildRequestCancelled;
build_state.status = BuildRequestStatusCode::BuildRequestCancelled.status();
build_state.updated_at = current_timestamp_nanos();
} else {
return Err((
@ -176,10 +176,11 @@ pub async fn cancel_build_request(
let event = create_build_event(
build_request_id.clone(),
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
status_code: BuildRequestStatus::BuildRequestCancelled as i32,
status_name: BuildRequestStatus::BuildRequestCancelled.to_display_string(),
status: Some(BuildRequestStatusCode::BuildRequestCancelled.status()),
requested_partitions: vec![],
message: "Build request cancelled".to_string(),
comment: None,
want_id: None,
}),
);
@ -262,14 +263,14 @@ pub async fn get_partition_events(
let decoded_partition_ref = base64_url_decode(&partition_ref).unwrap();
let events = match service.query_engine.get_partition_events(&decoded_partition_ref, None).await {
Ok(events) => events.into_iter().map(|e| {
Ok(events) => events.into_iter().filter(|e| e.build_request_id.is_some()).map(|e| {
let (job_label, partition_ref, delegated_build_id) = extract_navigation_data(&e.event_type);
BuildEventSummary {
event_id: e.event_id,
timestamp: e.timestamp,
event_type: event_type_to_string(&e.event_type),
message: event_to_message(&e.event_type),
build_request_id: e.build_request_id,
build_request_id: e.build_request_id.clone().unwrap(),
job_label,
partition_ref,
delegated_build_id,
@ -350,7 +351,7 @@ async fn execute_build_request(
);
// Update status to planning
update_build_request_status(&service, &build_request_id, BuildRequestStatus::BuildRequestPlanning).await;
update_build_request_status(&service, &build_request_id, BuildRequestStatusCode::BuildRequestPlanning.status()).await;
// Log planning event
if let Err(e) = orchestrator.start_planning().await {
@ -362,7 +363,7 @@ async fn execute_build_request(
Ok(graph) => graph,
Err(e) => {
error!("Failed to analyze build graph: {}", e);
update_build_request_status(&service, &build_request_id, BuildRequestStatus::BuildRequestFailed).await;
update_build_request_status(&service, &build_request_id, BuildRequestStatusCode::BuildRequestFailed.status()).await;
// Log failure event
if let Err(log_err) = orchestrator.complete_build(BuildResult::Failed { jobs_completed: 0, jobs_failed: 1 }).await {
@ -375,7 +376,7 @@ async fn execute_build_request(
// Update status to executing
update_build_request_status(&service, &build_request_id, BuildRequestStatus::BuildRequestExecuting).await;
update_build_request_status(&service, &build_request_id, BuildRequestStatusCode::BuildRequestExecuting.status()).await;
// Log executing event
if let Err(e) = orchestrator.start_execution().await {
@ -386,7 +387,7 @@ async fn execute_build_request(
match run_execute_command(&service, &build_request_id, &job_graph).await {
Ok(_) => {
info!("Build request {} completed successfully", build_request_id);
update_build_request_status(&service, &build_request_id, BuildRequestStatus::BuildRequestCompleted).await;
update_build_request_status(&service, &build_request_id, BuildRequestStatusCode::BuildRequestCompleted.status()).await;
// Log completion event
if let Err(e) = orchestrator.complete_build(BuildResult::Success { jobs_completed: 0 }).await {
@ -397,7 +398,7 @@ async fn execute_build_request(
}
Err(e) => {
error!("Build request {} failed: {}", build_request_id, e);
update_build_request_status(&service, &build_request_id, BuildRequestStatus::BuildRequestFailed).await;
update_build_request_status(&service, &build_request_id, BuildRequestStatusCode::BuildRequestFailed.status()).await;
// Log failure event
if let Err(log_err) = orchestrator.complete_build(BuildResult::Failed { jobs_completed: 0, jobs_failed: 1 }).await {
@ -505,7 +506,9 @@ fn event_type_to_string(event_type: &Option<crate::build_event::EventType>) -> S
Some(crate::build_event::EventType::PartitionInvalidationEvent(_)) => "partition_invalidation".to_string(),
Some(crate::build_event::EventType::JobRunCancelEvent(_)) => "task_cancel".to_string(),
Some(crate::build_event::EventType::BuildCancelEvent(_)) => "build_cancel".to_string(),
None => "INVALID_EVENT_TYPE".to_string(), // Make this obvious rather than hiding it
Some(build_event::EventType::WantEvent(_)) => "want".to_string(),
Some(build_event::EventType::TaintEvent(_)) => "taint".to_string(),
None => "INVALID_EVENT_TYPE".to_string(),
}
}
@ -519,7 +522,10 @@ fn event_to_message(event_type: &Option<crate::build_event::EventType>) -> Strin
Some(crate::build_event::EventType::PartitionInvalidationEvent(event)) => event.reason.clone(),
Some(crate::build_event::EventType::JobRunCancelEvent(event)) => event.reason.clone(),
Some(crate::build_event::EventType::BuildCancelEvent(event)) => event.reason.clone(),
None => "INVALID_EVENT_NO_MESSAGE".to_string(), // Make this obvious
Some(build_event::EventType::WantEvent(event)) => event.comment.clone(),
Some(build_event::EventType::TaintEvent(event)) => event.comment.clone(),
None => "INVALID_EVENT_NO_MESSAGE".to_string(),
}
}
@ -557,6 +563,12 @@ fn extract_navigation_data(event_type: &Option<crate::build_event::EventType>) -
// Build cancel events don't need navigation links
(None, None, None)
},
Some(crate::build_event::EventType::WantEvent(_)) => {
(None, None, None)
},
Some(crate::build_event::EventType::TaintEvent(_)) => {
(None, None, None)
},
None => (None, None, None),
}
}
@ -1417,8 +1429,7 @@ pub async fn get_build_detail(
let timeline_events: Vec<BuildTimelineEvent> = protobuf_response.timeline.into_iter().map(|event| {
BuildTimelineEvent {
timestamp: event.timestamp,
status_code: event.status_code,
status_name: event.status_name,
status: event.status,
message: event.message,
event_type: event.event_type,
cancel_reason: event.cancel_reason,
@ -1427,8 +1438,7 @@ pub async fn get_build_detail(
Ok(Json(BuildDetailResponse {
build_request_id: protobuf_response.build_request_id,
status_code: protobuf_response.status_code,
status_name: protobuf_response.status_name,
status: protobuf_response.status,
requested_partitions: protobuf_response.requested_partitions,
total_jobs: protobuf_response.total_jobs,
completed_jobs: protobuf_response.completed_jobs,

View file

@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize};
use schemars::JsonSchema;
use std::collections::HashMap;
use std::sync::Arc;
use rusqlite::ToSql;
use tokio::sync::RwLock;
use uuid::Uuid;
@ -398,15 +399,17 @@ impl BuildGraphService {
}
pub fn status_to_string(status: BuildRequestStatus) -> String {
match status {
BuildRequestStatus::BuildRequestUnknown => "unknown".to_string(),
BuildRequestStatus::BuildRequestReceived => "received".to_string(),
BuildRequestStatus::BuildRequestPlanning => "planning".to_string(),
BuildRequestStatus::BuildRequestAnalysisCompleted => "analysis_completed".to_string(),
BuildRequestStatus::BuildRequestExecuting => "executing".to_string(),
BuildRequestStatus::BuildRequestCompleted => "completed".to_string(),
BuildRequestStatus::BuildRequestFailed => "failed".to_string(),
BuildRequestStatus::BuildRequestCancelled => "cancelled".to_string(),
match BuildRequestStatusCode::try_from(status.code) {
Ok(BuildRequestStatusCode::BuildRequestUnknown) => "unknown".to_string(),
Ok(BuildRequestStatusCode::BuildRequestReceived) => "received".to_string(),
Ok(BuildRequestStatusCode::BuildRequestPlanning) => "planning".to_string(),
Ok(BuildRequestStatusCode::BuildRequestAnalysisCompleted) => "analysis_completed".to_string(),
Ok(BuildRequestStatusCode::BuildRequestExecuting) => "executing".to_string(),
Ok(BuildRequestStatusCode::BuildRequestCompleted) => "completed".to_string(),
Ok(BuildRequestStatusCode::BuildRequestFailed) => "failed".to_string(),
Ok(BuildRequestStatusCode::BuildRequestCancelled) => "cancelled".to_string(),
Ok(BuildRequestStatusCode::BuildRequestPreconditionFailed) => "precondition_failed".to_string(),
Err(_) => "error".to_string(),
}
}

View file

@ -61,35 +61,44 @@ impl JobStatus {
}
}
impl BuildRequestStatus {
impl BuildRequestStatusCode {
/// Convert build request status to human-readable string matching current CLI/service format
pub fn to_display_string(&self) -> String {
match self {
BuildRequestStatus::BuildRequestUnknown => "unknown".to_string(),
BuildRequestStatus::BuildRequestReceived => "received".to_string(),
BuildRequestStatus::BuildRequestPlanning => "planning".to_string(),
BuildRequestStatus::BuildRequestAnalysisCompleted => "analysis_completed".to_string(),
BuildRequestStatus::BuildRequestExecuting => "executing".to_string(),
BuildRequestStatus::BuildRequestCompleted => "completed".to_string(),
BuildRequestStatus::BuildRequestFailed => "failed".to_string(),
BuildRequestStatus::BuildRequestCancelled => "cancelled".to_string(),
BuildRequestStatusCode::BuildRequestUnknown => "unknown".to_string(),
BuildRequestStatusCode::BuildRequestReceived => "received".to_string(),
BuildRequestStatusCode::BuildRequestPlanning => "planning".to_string(),
BuildRequestStatusCode::BuildRequestAnalysisCompleted => "analysis_completed".to_string(),
BuildRequestStatusCode::BuildRequestExecuting => "executing".to_string(),
BuildRequestStatusCode::BuildRequestCompleted => "completed".to_string(),
BuildRequestStatusCode::BuildRequestFailed => "failed".to_string(),
BuildRequestStatusCode::BuildRequestCancelled => "cancelled".to_string(),
&BuildRequestStatusCode::BuildRequestPreconditionFailed => "precondition failed".to_string(),
}
}
/// Parse a display string back to enum
pub fn from_display_string(s: &str) -> Option<Self> {
match s {
"unknown" => Some(BuildRequestStatus::BuildRequestUnknown),
"received" => Some(BuildRequestStatus::BuildRequestReceived),
"planning" => Some(BuildRequestStatus::BuildRequestPlanning),
"analysis_completed" => Some(BuildRequestStatus::BuildRequestAnalysisCompleted),
"executing" => Some(BuildRequestStatus::BuildRequestExecuting),
"completed" => Some(BuildRequestStatus::BuildRequestCompleted),
"failed" => Some(BuildRequestStatus::BuildRequestFailed),
"cancelled" => Some(BuildRequestStatus::BuildRequestCancelled),
"unknown" => Some(BuildRequestStatusCode::BuildRequestUnknown),
"received" => Some(BuildRequestStatusCode::BuildRequestReceived),
"planning" => Some(BuildRequestStatusCode::BuildRequestPlanning),
"analysis_completed" => Some(BuildRequestStatusCode::BuildRequestAnalysisCompleted),
"executing" => Some(BuildRequestStatusCode::BuildRequestExecuting),
"completed" => Some(BuildRequestStatusCode::BuildRequestCompleted),
"failed" => Some(BuildRequestStatusCode::BuildRequestFailed),
"cancelled" => Some(BuildRequestStatusCode::BuildRequestCancelled),
"precondition failed" => Some(BuildRequestStatusCode::BuildRequestPreconditionFailed),
_ => None,
}
}
pub fn status(&self) -> BuildRequestStatus {
BuildRequestStatus {
code: self.clone().into(),
name: self.to_display_string(),
}
}
}
impl DepType {
@ -205,11 +214,11 @@ pub mod list_response_helpers {
completed_at: Option<i64>,
duration_ms: Option<i64>,
cancelled: bool,
comment: Option<String>,
) -> BuildSummary {
BuildSummary {
build_request_id,
status_code: status as i32,
status_name: status.to_display_string(),
status: Some(status),
requested_partitions,
total_jobs: total_jobs as u32,
completed_jobs: completed_jobs as u32,
@ -220,6 +229,7 @@ pub mod list_response_helpers {
completed_at,
duration_ms,
cancelled,
comment,
}
}
@ -256,9 +266,8 @@ mod tests {
#[test]
fn test_build_request_status_conversions() {
let status = BuildRequestStatus::BuildRequestCompleted;
assert_eq!(status.to_display_string(), "completed");
assert_eq!(BuildRequestStatus::from_display_string("completed"), Some(status));
let status = BuildRequestStatusCode::BuildRequestCompleted.status();
assert_eq!(status.name, "completed");
}
#[test]
@ -276,7 +285,7 @@ mod tests {
fn test_invalid_display_string() {
assert_eq!(PartitionStatus::from_display_string("invalid"), None);
assert_eq!(JobStatus::from_display_string("invalid"), None);
assert_eq!(BuildRequestStatus::from_display_string("invalid"), None);
assert_eq!(BuildRequestStatusCode::from_display_string("invalid"), None);
assert_eq!(DepType::from_display_string("invalid"), None);
}
}