8.8 KiB
8.8 KiB
Chunk 5: Build Status
Parent: Build Graph Dashboard
Previous: Chunk 4: Recent Activity
Next: Chunk 6: Partition Pages
Overview
Implement the core operational build request status page with real-time updates, job/partition status visualization, and execution logs.
Scope
In Scope
- Build request status display with real-time updates
- Job and partition status visualization
- Execution timeline and logs from build events
- Delegation indicators for shared builds
- Auto-refresh with Page Visibility API
- Expandable job details
Out of Scope
- Complex graph visualizations (simple status indicators)
- Historical build comparisons
- Advanced filtering of events
- User interactions beyond viewing
Technical Approach
Data Sources
From Build Graph Service API:
/api/v1/builds/:id- Individual build request details and events/api/v1/partitions/:ref/status- Individual partition status/api/v1/partitions/:ref/events- Partition-specific events
Available TypeScript types from generated client:
BuildStatusResponse- Build request metadata, status, eventsPartitionStatusResponse- Individual partition statusPartitionEventsResponse- Partition build eventsBuildRequestStatusenum - Status values (Received, Planning, Executing, Completed, Failed, Cancelled)PartitionStatusenum - Status values (Requested, Scheduled, Building, Available, Failed, Delegated)
Note: Timestamps are in nanoseconds and require conversion via /1000000 for JavaScript Date objects.
Component Structure
import { DefaultApi, Configuration, BuildStatusResponse, PartitionStatusResponse } from '../client/typescript_generated/src/index';
import { pollingManager, formatTime } from './services';
const BuildStatus = {
data: null as BuildStatusResponse | null,
loading: true,
error: null as string | null,
partitionStatuses: new Map<string, PartitionStatusResponse>(),
logsExpanded: {} as Record<string, boolean>,
oninit: (vnode) => {
this.buildId = vnode.attrs.id;
this.loadBuild();
this.startPolling();
},
onremove: () => {
pollingManager.stopPolling(`build-status-${this.buildId}`);
},
async loadBuild() {
try {
this.loading = true;
this.error = null;
m.redraw();
const apiClient = new DefaultApi(new Configuration({ basePath: '' }));
// Get build status
const buildResponse = await apiClient.apiV1BuildsBuildRequestIdGet({ buildRequestId: this.buildId });
this.data = buildResponse;
// Load partition statuses for all requested partitions
if (buildResponse.requestedPartitions) {
for (const partition of buildResponse.requestedPartitions) {
try {
const partitionStatus = await apiClient.apiV1PartitionsRefStatusGet({
ref: partition.str
});
this.partitionStatuses.set(partition.str, partitionStatus);
} catch (e) {
console.warn(`Failed to load status for partition ${partition.str}:`, e);
}
}
}
this.loading = false;
m.redraw();
} catch (error) {
console.error('Failed to load build:', error);
this.error = error instanceof Error ? error.message : 'Failed to load build';
this.loading = false;
m.redraw();
}
},
startPolling() {
// Use different poll intervals based on build status
const isActive = this.data?.status === 'BuildRequestExecuting' ||
this.data?.status === 'BuildRequestPlanning';
const interval = isActive ? 2000 : 10000; // 2s for active, 10s for completed
pollingManager.startPolling(`build-status-${this.buildId}`, () => {
this.loadBuild();
}, interval);
},
view: () => [
// Loading/error states similar to RecentActivity component
this.loading && !this.data ? m('.loading-state', '...') : null,
this.error ? m('.error-state', this.error) : null,
this.data ? [
m('.build-header', [
m('h1', `Build ${this.buildId}`),
m('.build-meta', [
m(`span.badge.${this.getStatusClass(this.data.status)}`, this.data.status),
m('.timestamp', formatTime(new Date(this.data.createdAt / 1000000).toISOString())),
m('.partitions', `${this.data.requestedPartitions?.length || 0} partitions`),
])
]),
m('.build-content', [
m('.partition-status', [
m('h2', 'Partition Status'),
m('.partition-grid',
this.data.requestedPartitions?.map(partition => {
const status = this.partitionStatuses.get(partition.str);
return m('.partition-card', [
m('.partition-ref', partition.str),
m(`span.badge.${this.getPartitionStatusClass(status?.status)}`,
status?.status || 'Unknown'),
status?.updatedAt ?
m('.updated-time', formatTime(new Date(status.updatedAt / 1000000).toISOString())) : null
]);
}) || []
)
]),
m('.execution-timeline', [
m('h2', 'Execution Timeline'),
m('.timeline', this.data.events?.map(event =>
m('.timeline-item', [
m('.timestamp', formatTime(new Date(event.timestamp / 1000000).toISOString())),
m('.event-type', event.eventType),
m('.message', event.message || ''),
// Add expandable logs for job events
this.isJobEvent(event) ? m('.expandable-logs', [
m('button.btn.btn-sm', {
onclick: () => this.toggleLogs(event.eventId)
}, this.logsExpanded[event.eventId] ? 'Hide Logs' : 'Show Logs'),
this.logsExpanded[event.eventId] ?
m('.logs', this.formatJobLogs(event)) : null
]) : null
])
) || [])
])
])
] : null
]
};
Real-time Updates
- Use existing
pollingManagerfromservices.tswith Page Visibility API - Poll every 2 seconds when build status is
BuildRequestExecutingorBuildRequestPlanning - Poll every 10 seconds when build is
BuildRequestCompleted,BuildRequestFailed, orBuildRequestCancelled - Automatic polling pause when tab is not visible
- Explicit
m.redraw()calls after async data loading
Status Visualization
- Color-coded status badges using DaisyUI classes:
badge-successforBuildRequestCompleted/PartitionAvailablebadge-warningforBuildRequestExecuting/PartitionBuildingbadge-errorforBuildRequestFailed/PartitionFailedbadge-neutralfor other states
- Timeline visualization for build events with timestamp formatting
- Delegation indicators for
PartitionDelegatedstatus with build request links
Implementation Strategy
-
Extend Existing Infrastructure
- Use established
DefaultApiclient pattern fromservices.ts - Leverage existing
pollingManagerwith Page Visibility API - Follow
RecentActivitycomponent patterns for loading/error states - Import generated TypeScript types from client
- Use established
-
Build Status Components
- Build header with metadata using
BuildStatusResponse - Partition status grid using
PartitionStatusResponsefor each partition - Execution timeline parsing
eventsarray from build response - Expandable log sections for job events with state management
- Build header with metadata using
-
Real-time Updates
- Intelligent polling based on
BuildRequestStatusenum values - Reuse existing
pollingManagerinfrastructure - Loading states and error handling following established patterns
- Proper
m.redraw()calls after async operations
- Intelligent polling based on
-
Status Visualization
- Status badge classes using established DaisyUI patterns
- Timeline layout similar to existing dashboard components
- Partition delegation links using build request IDs
- Timestamp formatting using existing
formatTimeutility
Deliverables
- Build request status page with real-time updates
- Partition status grid with visual indicators
- Execution timeline with build events
- Expandable job logs and details
- Auto-refresh with visibility detection
- Delegation indicators and links
Success Criteria
- Real-time updates show build progress accurately
- All partition statuses are clearly displayed
- Job logs are accessible and readable
- Polling behaves correctly based on build state
- Delegation to other builds is clearly indicated
- Page is responsive and performs well
Testing
- Test with running builds to verify real-time updates
- Verify partition status changes are reflected
- Test job log expansion and readability
- Validate polling behavior with tab visibility
- Test with delegated builds
- Verify error handling with invalid build IDs
- Check performance with large build requests