databuild/databuild/dashboard/types.ts
Stuart Axelbrooke 24482e2cc4
Some checks are pending
/ setup (push) Waiting to run
Big compile time correctness commit
2025-07-21 19:22:51 -07:00

287 lines
No EOL
8.1 KiB
TypeScript

import m from 'mithril';
import {
ActivityResponse,
ActivityApiResponse,
BuildSummary,
BuildDetailResponse,
PartitionSummary,
PartitionDetailResponse,
PartitionEventsResponse,
JobSummary,
JobMetricsResponse,
JobDailyStats,
JobRunSummary,
PartitionRef
} from '../client/typescript_generated/src/index';
// Dashboard-optimized types - canonical frontend types independent of backend schema
// These types prevent runtime errors by ensuring consistent data shapes throughout components
export interface DashboardBuild {
build_request_id: string;
status_code: number;
status_name: string;
requested_partitions: PartitionRef[];
total_jobs: number;
completed_jobs: number;
failed_jobs: number;
cancelled_jobs: number;
requested_at: number;
started_at: number | null;
completed_at: number | null;
duration_ms: number | null;
cancelled: boolean;
}
export interface DashboardPartition {
partition_ref: PartitionRef;
status_code: number;
status_name: string;
last_updated: number | null;
build_requests: string[];
}
export interface DashboardJob {
job_label: string;
total_runs: number;
successful_runs: number;
failed_runs: number;
cancelled_runs: number;
last_run_timestamp: number;
last_run_status_code: number;
last_run_status_name: string;
average_partitions_per_run: number;
recent_builds: string[];
}
export interface DashboardActivity {
active_builds_count: number;
recent_builds: DashboardBuild[];
recent_partitions: DashboardPartition[];
total_partitions_count: number;
system_status: string;
graph_name: string;
}
// Dashboard timeline event types for consistent UI handling
export interface DashboardBuildTimelineEvent {
timestamp: number;
status_code: number;
status_name: string;
message: string;
event_type: string;
cancel_reason?: string;
}
export interface DashboardPartitionTimelineEvent {
timestamp: number;
status_code: number;
status_name: string;
message: string;
build_request_id: string;
job_run_id?: string;
}
// Generic typed component interface that extends Mithril's component
// Uses intersection type to allow arbitrary properties while ensuring type safety for lifecycle methods
export interface TypedComponent<TAttrs = {}> extends Record<string, any> {
oninit?(vnode: m.Vnode<TAttrs>): void;
oncreate?(vnode: m.VnodeDOM<TAttrs>): void;
onupdate?(vnode: m.VnodeDOM<TAttrs>): void;
onbeforeremove?(vnode: m.VnodeDOM<TAttrs>): Promise<any> | void;
onremove?(vnode: m.VnodeDOM<TAttrs>): void;
onbeforeupdate?(vnode: m.Vnode<TAttrs>, old: m.VnodeDOM<TAttrs>): boolean | void;
view(vnode: m.Vnode<TAttrs>): m.Children;
}
// Helper type for typed vnodes
export type TypedVnode<TAttrs = {}> = m.Vnode<TAttrs>;
export type TypedVnodeDOM<TAttrs = {}> = m.VnodeDOM<TAttrs>;
// Route parameter types
export interface RouteParams {
[key: string]: string;
}
export interface BuildRouteParams extends RouteParams {
id: string;
}
export interface PartitionRouteParams extends RouteParams {
base64_ref: string;
}
export interface JobRouteParams extends RouteParams {
label: string;
}
// Component attribute interfaces that reference OpenAPI types
export interface RecentActivityAttrs {
// No external attrs needed - component manages its own data loading
}
export interface BuildStatusAttrs {
id: string;
}
export interface PartitionStatusAttrs {
base64_ref: string;
}
export interface PartitionsListAttrs {
// No external attrs needed - component manages its own data loading
}
export interface JobsListAttrs {
// No external attrs needed - component manages its own data loading
}
export interface JobMetricsAttrs {
label: string;
}
export interface GraphAnalysisAttrs {
// No external attrs needed for now
}
// Badge component attribute interfaces with OpenAPI type constraints
export interface BuildStatusBadgeAttrs {
status: string; // This should be constrained to BuildSummary status values
size?: 'xs' | 'sm' | 'md' | 'lg';
class?: string;
}
export interface PartitionStatusBadgeAttrs {
status: string; // This should be constrained to PartitionSummary status values
size?: 'xs' | 'sm' | 'md' | 'lg';
class?: string;
}
export interface EventTypeBadgeAttrs {
eventType: string; // This should be constrained to known event types
size?: 'xs' | 'sm' | 'md' | 'lg';
class?: string;
}
// Layout wrapper attributes
export interface LayoutWrapperAttrs {
// Layout wrapper will pass through attributes to wrapped component
[key: string]: any;
}
// Data types for component state (using Dashboard types for consistency)
export interface RecentActivityData {
data: DashboardActivity | null;
loading: boolean;
error: string | null;
}
export interface BuildStatusData {
data: DashboardBuild | null;
partitionStatuses: Map<string, DashboardPartition>; // Key is partition_ref.str
timeline: DashboardBuildTimelineEvent[];
loading: boolean;
error: string | null;
buildId: string;
}
export interface PartitionStatusData {
data: DashboardPartition | null;
timeline: DashboardPartitionTimelineEvent[];
loading: boolean;
error: string | null;
partitionRef: string;
buildHistory: DashboardBuild[];
}
export interface JobsListData {
jobs: DashboardJob[];
searchTerm: string;
loading: boolean;
error: string | null;
searchTimeout: NodeJS.Timeout | null;
}
export interface JobMetricsData {
jobLabel: string;
job: DashboardJob | null;
loading: boolean;
error: string | null;
}
// Utility type for creating typed components
export type CreateTypedComponent<TAttrs> = TypedComponent<TAttrs>;
/*
## Dashboard Type Transformation Rationale
The dashboard types provide a stable interface between the OpenAPI-generated types and UI components:
1. **Explicit Null Handling**: Protobuf optional fields become `T | null` instead of `T | undefined`
to ensure consistent null checking throughout the application.
2. **Type Safety**: Keep protobuf structure (PartitionRef objects, status codes) to maintain
type safety from backend to frontend. Only convert to display strings in components.
3. **Clear Boundaries**: Dashboard types are the contract between services and components.
Services handle API responses, components handle presentation.
Key principles:
- Preserve protobuf structure for type safety
- Explicit null handling for optional fields
- Convert to display strings only at the UI layer
- Consistent types prevent runtime errors
*/
// Type guards and validators for Dashboard types
export function isDashboardActivity(data: any): data is DashboardActivity {
return data &&
typeof data.active_builds_count === 'number' &&
typeof data.graph_name === 'string' &&
Array.isArray(data.recent_builds) &&
Array.isArray(data.recent_partitions) &&
typeof data.system_status === 'string' &&
typeof data.total_partitions_count === 'number';
}
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.requested_at === 'number' &&
Array.isArray(data.requested_partitions);
}
export function isDashboardPartition(data: any): data is DashboardPartition {
return data &&
data.partition_ref &&
typeof data.partition_ref.str === 'string' &&
typeof data.status_code === 'number' &&
typeof data.status_name === 'string' &&
(data.last_updated === null || typeof data.last_updated === 'number') &&
Array.isArray(data.build_requests);
}
export function isDashboardJob(data: any): data is DashboardJob {
return data &&
typeof data.job_label === 'string' &&
typeof data.total_runs === 'number' &&
typeof data.last_run_status_code === 'number' &&
typeof data.last_run_status_name === 'string' &&
Array.isArray(data.recent_builds);
}
// Helper function to create type-safe Mithril components
export function createTypedComponent<TAttrs>(
component: TypedComponent<TAttrs>
): m.Component<TAttrs> {
return component as m.Component<TAttrs>;
}
// Helper for type-safe route handling
export function getTypedRouteParams<T extends RouteParams>(vnode: m.Vnode<T>): T {
return vnode.attrs;
}