287 lines
No EOL
8.1 KiB
TypeScript
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;
|
|
} |