databuild/plans/webapp_v1/chunk-5-build-status.md
soaxelbrooke ebcbe3e928
Some checks are pending
/ setup (push) Waiting to run
Update next plan
2025-07-12 11:01:04 -07:00

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, events
  • PartitionStatusResponse - Individual partition status
  • PartitionEventsResponse - Partition build events
  • BuildRequestStatus enum - Status values (Received, Planning, Executing, Completed, Failed, Cancelled)
  • PartitionStatus enum - 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 pollingManager from services.ts with Page Visibility API
  • Poll every 2 seconds when build status is BuildRequestExecuting or BuildRequestPlanning
  • Poll every 10 seconds when build is BuildRequestCompleted, BuildRequestFailed, or BuildRequestCancelled
  • 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-success for BuildRequestCompleted / PartitionAvailable
    • badge-warning for BuildRequestExecuting / PartitionBuilding
    • badge-error for BuildRequestFailed / PartitionFailed
    • badge-neutral for other states
  • Timeline visualization for build events with timestamp formatting
  • Delegation indicators for PartitionDelegated status with build request links

Implementation Strategy

  1. Extend Existing Infrastructure

    • Use established DefaultApi client pattern from services.ts
    • Leverage existing pollingManager with Page Visibility API
    • Follow RecentActivity component patterns for loading/error states
    • Import generated TypeScript types from client
  2. Build Status Components

    • Build header with metadata using BuildStatusResponse
    • Partition status grid using PartitionStatusResponse for each partition
    • Execution timeline parsing events array from build response
    • Expandable log sections for job events with state management
  3. Real-time Updates

    • Intelligent polling based on BuildRequestStatus enum values
    • Reuse existing pollingManager infrastructure
    • Loading states and error handling following established patterns
    • Proper m.redraw() calls after async operations
  4. 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 formatTime utility

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