Update ui
This commit is contained in:
parent
7bb999ab47
commit
307f146e7c
5 changed files with 178 additions and 96 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
import m from 'mithril';
|
import m from 'mithril';
|
||||||
import { DashboardService, pollingManager, formatTime, formatDateTime, RecentActivitySummary } from './services';
|
import { DashboardService, pollingManager, formatTime, formatDateTime, RecentActivitySummary } from './services';
|
||||||
import { encodePartitionRef } from './utils';
|
import { encodePartitionRef, BuildStatusBadge, PartitionStatusBadge, EventTypeBadge } from './utils';
|
||||||
|
|
||||||
// Page scaffold components
|
// Page scaffold components
|
||||||
export const RecentActivity = {
|
export const RecentActivity = {
|
||||||
|
|
@ -10,20 +10,16 @@ export const RecentActivity = {
|
||||||
pollInterval: null as NodeJS.Timeout | null,
|
pollInterval: null as NodeJS.Timeout | null,
|
||||||
|
|
||||||
loadData() {
|
loadData() {
|
||||||
console.log('RecentActivity: Starting loadData, loading=', this.loading);
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
m.redraw(); // Redraw to show loading state
|
m.redraw(); // Redraw to show loading state
|
||||||
|
|
||||||
const service = DashboardService.getInstance();
|
const service = DashboardService.getInstance();
|
||||||
console.log('RecentActivity: Got service instance, calling getRecentActivity');
|
|
||||||
|
|
||||||
return service.getRecentActivity()
|
return service.getRecentActivity()
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log('RecentActivity: Got data successfully', data);
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
console.log('RecentActivity: Data loaded, loading=', this.loading, 'data=', !!this.data);
|
|
||||||
m.redraw(); // Explicitly redraw after data loads
|
m.redraw(); // Explicitly redraw after data loads
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|
@ -52,7 +48,6 @@ export const RecentActivity = {
|
||||||
},
|
},
|
||||||
|
|
||||||
view: function() {
|
view: function() {
|
||||||
console.log('RecentActivity: view() called, loading=', this.loading, 'data=', !!this.data, 'error=', this.error);
|
|
||||||
|
|
||||||
if (this.loading && !this.data) {
|
if (this.loading && !this.data) {
|
||||||
return m('div.container.mx-auto.p-4', [
|
return m('div.container.mx-auto.p-4', [
|
||||||
|
|
@ -207,12 +202,7 @@ export const RecentActivity = {
|
||||||
}, build.id)
|
}, build.id)
|
||||||
]),
|
]),
|
||||||
m('td', [
|
m('td', [
|
||||||
m(`span.badge.badge-sm.${
|
m(BuildStatusBadge, { status: build.status })
|
||||||
build.status === 'completed' ? 'badge-success' :
|
|
||||||
build.status === 'running' ? 'badge-warning' :
|
|
||||||
build.status === 'failed' ? 'badge-error' :
|
|
||||||
'badge-neutral'
|
|
||||||
}`, build.status)
|
|
||||||
]),
|
]),
|
||||||
m('td.text-sm.opacity-70', formatTime(build.createdAt)),
|
m('td.text-sm.opacity-70', formatTime(build.createdAt)),
|
||||||
])
|
])
|
||||||
|
|
@ -266,12 +256,7 @@ export const RecentActivity = {
|
||||||
}, partition.ref)
|
}, partition.ref)
|
||||||
]),
|
]),
|
||||||
m('td', [
|
m('td', [
|
||||||
m(`span.badge.badge-sm.${
|
m(PartitionStatusBadge, { status: partition.status })
|
||||||
partition.status === 'available' ? 'badge-success' :
|
|
||||||
partition.status === 'building' ? 'badge-warning' :
|
|
||||||
partition.status === 'failed' ? 'badge-error' :
|
|
||||||
'badge-neutral'
|
|
||||||
}`, partition.status)
|
|
||||||
]),
|
]),
|
||||||
m('td.text-sm.opacity-70', formatTime(partition.updatedAt)),
|
m('td.text-sm.opacity-70', formatTime(partition.updatedAt)),
|
||||||
])
|
])
|
||||||
|
|
@ -291,7 +276,6 @@ export const BuildStatus = {
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null as string | null,
|
error: null as string | null,
|
||||||
partitionStatuses: new Map<string, any>(),
|
partitionStatuses: new Map<string, any>(),
|
||||||
logsExpanded: {} as Record<string, boolean>,
|
|
||||||
buildId: '',
|
buildId: '',
|
||||||
|
|
||||||
oninit(vnode: any) {
|
oninit(vnode: any) {
|
||||||
|
|
@ -310,8 +294,6 @@ export const BuildStatus = {
|
||||||
this.error = null;
|
this.error = null;
|
||||||
m.redraw();
|
m.redraw();
|
||||||
|
|
||||||
console.log(`BuildStatus: Loading build ${this.buildId}`);
|
|
||||||
|
|
||||||
// Import types dynamically to avoid circular dependencies
|
// Import types dynamically to avoid circular dependencies
|
||||||
const { DefaultApi, Configuration } = await import('../client/typescript_generated/src/index');
|
const { DefaultApi, Configuration } = await import('../client/typescript_generated/src/index');
|
||||||
const apiClient = new DefaultApi(new Configuration({ basePath: '' }));
|
const apiClient = new DefaultApi(new Configuration({ basePath: '' }));
|
||||||
|
|
@ -320,8 +302,6 @@ export const BuildStatus = {
|
||||||
const buildResponse = await apiClient.apiV1BuildsBuildRequestIdGet({ buildRequestId: this.buildId });
|
const buildResponse = await apiClient.apiV1BuildsBuildRequestIdGet({ buildRequestId: this.buildId });
|
||||||
this.data = buildResponse;
|
this.data = buildResponse;
|
||||||
|
|
||||||
console.log('BuildStatus: Got build response:', buildResponse);
|
|
||||||
|
|
||||||
// Load partition statuses for all requested partitions
|
// Load partition statuses for all requested partitions
|
||||||
if (buildResponse.requestedPartitions) {
|
if (buildResponse.requestedPartitions) {
|
||||||
for (const partitionRef of buildResponse.requestedPartitions) {
|
for (const partitionRef of buildResponse.requestedPartitions) {
|
||||||
|
|
@ -357,42 +337,42 @@ export const BuildStatus = {
|
||||||
}, interval);
|
}, interval);
|
||||||
},
|
},
|
||||||
|
|
||||||
getStatusClass(status: string): string {
|
|
||||||
switch (status) {
|
getEventLink(event: any): { href: string; text: string } | null {
|
||||||
case 'BuildRequestCompleted': return 'badge-success';
|
const eventType = event.eventType;
|
||||||
case 'BuildRequestExecuting': return 'badge-warning';
|
|
||||||
case 'BuildRequestPlanning': return 'badge-warning';
|
switch (eventType) {
|
||||||
case 'BuildRequestFailed': return 'badge-error';
|
case 'job':
|
||||||
case 'BuildRequestCancelled': return 'badge-error';
|
if (event.jobLabel) {
|
||||||
default: return 'badge-neutral';
|
return {
|
||||||
|
href: `/jobs/${encodeURIComponent(event.jobLabel)}`,
|
||||||
|
text: 'Job Details'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
case 'partition':
|
||||||
|
if (event.partitionRef) {
|
||||||
|
return {
|
||||||
|
href: `/partitions/${encodePartitionRef(event.partitionRef)}`,
|
||||||
|
text: 'Partition Status'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
case 'delegation':
|
||||||
|
if (event.delegatedBuildId) {
|
||||||
|
return {
|
||||||
|
href: `/builds/${event.delegatedBuildId}`,
|
||||||
|
text: 'Delegated Build'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
case 'build_request':
|
||||||
|
// Self-referential, no additional link needed
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getPartitionStatusClass(status?: string): string {
|
|
||||||
switch (status) {
|
|
||||||
case 'PartitionAvailable': return 'badge-success';
|
|
||||||
case 'PartitionBuilding': return 'badge-warning';
|
|
||||||
case 'PartitionScheduled': return 'badge-warning';
|
|
||||||
case 'PartitionFailed': return 'badge-error';
|
|
||||||
case 'PartitionDelegated': return 'badge-info';
|
|
||||||
default: return 'badge-neutral';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isJobEvent(event: any): boolean {
|
|
||||||
return event.eventType?.includes('Job') || event.eventType?.includes('job');
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleLogs(eventId: string) {
|
|
||||||
this.logsExpanded[eventId] = !this.logsExpanded[eventId];
|
|
||||||
m.redraw();
|
|
||||||
},
|
|
||||||
|
|
||||||
formatJobLogs(event: any): string {
|
|
||||||
// For now, just return the message as formatted log
|
|
||||||
// In the future, this could parse structured log data
|
|
||||||
return event.message || '';
|
|
||||||
},
|
|
||||||
|
|
||||||
view() {
|
view() {
|
||||||
// Loading/error states similar to RecentActivity component
|
// Loading/error states similar to RecentActivity component
|
||||||
|
|
@ -438,7 +418,7 @@ export const BuildStatus = {
|
||||||
m('.build-header.mb-6', [
|
m('.build-header.mb-6', [
|
||||||
m('h1.text-3xl.font-bold.mb-4', `Build ${this.buildId}`),
|
m('h1.text-3xl.font-bold.mb-4', `Build ${this.buildId}`),
|
||||||
m('.build-meta.flex.gap-4.items-center.mb-4', [
|
m('.build-meta.flex.gap-4.items-center.mb-4', [
|
||||||
m(`span.badge.badge-lg.${this.getStatusClass(this.data.status)}`, this.data.status),
|
m(BuildStatusBadge, { status: this.data.status, size: 'lg' }),
|
||||||
m('.timestamp.text-sm.opacity-70', formatDateTime(new Date(this.data.createdAt / 1000000).toISOString())),
|
m('.timestamp.text-sm.opacity-70', formatDateTime(new Date(this.data.createdAt / 1000000).toISOString())),
|
||||||
m('.partitions.text-sm.opacity-70', `${this.data.requestedPartitions?.length || 0} partitions`),
|
m('.partitions.text-sm.opacity-70', `${this.data.requestedPartitions?.length || 0} partitions`),
|
||||||
])
|
])
|
||||||
|
|
@ -454,8 +434,7 @@ export const BuildStatus = {
|
||||||
return m('.partition-card.border.border-base-300.rounded.p-3', [
|
return m('.partition-card.border.border-base-300.rounded.p-3', [
|
||||||
m('.partition-ref.font-mono.text-sm.break-all.mb-2', partitionRef),
|
m('.partition-ref.font-mono.text-sm.break-all.mb-2', partitionRef),
|
||||||
m('.flex.justify-between.items-center', [
|
m('.flex.justify-between.items-center', [
|
||||||
m(`span.badge.${this.getPartitionStatusClass(status?.status)}`,
|
m(PartitionStatusBadge, { status: status?.status || 'Unknown' }),
|
||||||
status?.status || 'Unknown'),
|
|
||||||
status?.lastUpdated ?
|
status?.lastUpdated ?
|
||||||
m('.updated-time.text-xs.opacity-60',
|
m('.updated-time.text-xs.opacity-60',
|
||||||
formatDateTime(new Date(status.lastUpdated / 1000000).toISOString())) : null
|
formatDateTime(new Date(status.lastUpdated / 1000000).toISOString())) : null
|
||||||
|
|
@ -477,7 +456,7 @@ export const BuildStatus = {
|
||||||
m('th', 'Timestamp'),
|
m('th', 'Timestamp'),
|
||||||
m('th', 'Event Type'),
|
m('th', 'Event Type'),
|
||||||
m('th', 'Message'),
|
m('th', 'Message'),
|
||||||
m('th', 'Actions')
|
m('th', 'Link')
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
m('tbody',
|
m('tbody',
|
||||||
|
|
@ -486,32 +465,24 @@ export const BuildStatus = {
|
||||||
m('td.text-xs.font-mono',
|
m('td.text-xs.font-mono',
|
||||||
formatDateTime(new Date(event.timestamp / 1000000).toISOString())),
|
formatDateTime(new Date(event.timestamp / 1000000).toISOString())),
|
||||||
m('td', [
|
m('td', [
|
||||||
m('.badge.badge-sm.badge-outline', event.eventType)
|
m(EventTypeBadge, { eventType: event.eventType })
|
||||||
]),
|
]),
|
||||||
m('td.text-sm', event.message || ''),
|
m('td.text-sm', event.message || ''),
|
||||||
m('td', [
|
m('td', [
|
||||||
this.isJobEvent(event) ?
|
(() => {
|
||||||
m('button.btn.btn-xs.btn-outline', {
|
const link = this.getEventLink(event);
|
||||||
onclick: () => this.toggleLogs(event.eventId)
|
return link ?
|
||||||
}, this.logsExpanded[event.eventId] ? 'Hide' : 'Details') : null
|
m(m.route.Link, {
|
||||||
|
href: link.href,
|
||||||
|
class: 'link link-primary text-sm'
|
||||||
|
}, link.text) :
|
||||||
|
m('span.text-xs.opacity-50', '—');
|
||||||
|
})()
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]),
|
])
|
||||||
// Expandable logs section
|
|
||||||
this.data.events.some((event: any) => this.logsExpanded[event.eventId]) ?
|
|
||||||
m('.mt-4', [
|
|
||||||
m('h3.text-lg.font-semibold.mb-2', 'Event Details'),
|
|
||||||
this.data.events.filter((event: any) => this.logsExpanded[event.eventId])
|
|
||||||
.map((event: any) =>
|
|
||||||
m('.bg-base-200.rounded.p-3.mb-2', [
|
|
||||||
m('.text-sm.font-semibold.mb-1', `${event.eventType} - ${event.eventId}`),
|
|
||||||
m('.text-xs.font-mono.whitespace-pre-wrap',
|
|
||||||
this.formatJobLogs(event))
|
|
||||||
])
|
|
||||||
)
|
|
||||||
]) : null
|
|
||||||
]) :
|
]) :
|
||||||
m('.text-center.py-8.text-base-content.opacity-60', 'No events')
|
m('.text-center.py-8.text-base-content.opacity-60', 'No events')
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,9 @@ export class DashboardService {
|
||||||
|
|
||||||
async getRecentActivity(): Promise<RecentActivitySummary> {
|
async getRecentActivity(): Promise<RecentActivitySummary> {
|
||||||
try {
|
try {
|
||||||
console.log('DashboardService: Fetching real data from API...');
|
|
||||||
|
|
||||||
// Use the new activity endpoint that aggregates all the data we need
|
// Use the new activity endpoint that aggregates all the data we need
|
||||||
const activityResponse: ActivityResponse = await apiClient.apiV1ActivityGet();
|
const activityResponse: ActivityResponse = await apiClient.apiV1ActivityGet();
|
||||||
|
|
||||||
console.log('DashboardService: Got activity response:', activityResponse);
|
|
||||||
|
|
||||||
// Convert the API response to our dashboard format
|
// Convert the API response to our dashboard format
|
||||||
const recentBuilds: BuildRequest[] = activityResponse.recentBuilds.map((build: BuildSummary) => ({
|
const recentBuilds: BuildRequest[] = activityResponse.recentBuilds.map((build: BuildSummary) => ({
|
||||||
id: build.buildRequestId,
|
id: build.buildRequestId,
|
||||||
|
|
@ -79,7 +75,6 @@ export class DashboardService {
|
||||||
console.error('Failed to fetch recent activity:', error);
|
console.error('Failed to fetch recent activity:', error);
|
||||||
|
|
||||||
// Fall back to mock data if API call fails
|
// Fall back to mock data if API call fails
|
||||||
console.log('DashboardService: Falling back to mock data due to API error');
|
|
||||||
return {
|
return {
|
||||||
activeBuilds: 0,
|
activeBuilds: 0,
|
||||||
recentBuilds: [],
|
recentBuilds: [],
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,82 @@ export function decodePartitionRef(encoded: string): string {
|
||||||
const padding = '='.repeat((4 - (encoded.length % 4)) % 4);
|
const padding = '='.repeat((4 - (encoded.length % 4)) % 4);
|
||||||
const padded = encoded.replace(/-/g, '+').replace(/_/g, '/') + padding;
|
const padded = encoded.replace(/-/g, '+').replace(/_/g, '/') + padding;
|
||||||
return atob(padded);
|
return atob(padded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import m from 'mithril';
|
||||||
|
|
||||||
|
// Mithril components for status badges - encapsulates both logic and presentation
|
||||||
|
|
||||||
|
export const BuildStatusBadge = {
|
||||||
|
view(vnode: any) {
|
||||||
|
const { status, size = 'sm', ...attrs } = vnode.attrs;
|
||||||
|
const normalizedStatus = status.toLowerCase();
|
||||||
|
|
||||||
|
let badgeClass = 'badge-neutral';
|
||||||
|
if (normalizedStatus.includes('completed')) {
|
||||||
|
badgeClass = 'badge-success';
|
||||||
|
} else if (normalizedStatus.includes('executing') || normalizedStatus.includes('planning')) {
|
||||||
|
badgeClass = 'badge-warning';
|
||||||
|
} else if (normalizedStatus.includes('received')) {
|
||||||
|
badgeClass = 'badge-info';
|
||||||
|
} else if (normalizedStatus.includes('failed') || normalizedStatus.includes('cancelled')) {
|
||||||
|
badgeClass = 'badge-error';
|
||||||
|
}
|
||||||
|
|
||||||
|
return m(`span.badge.badge-${size}.${badgeClass}`, attrs, status);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PartitionStatusBadge = {
|
||||||
|
view(vnode: any) {
|
||||||
|
const { status, size = 'sm', ...attrs } = vnode.attrs;
|
||||||
|
if (!status) {
|
||||||
|
return m(`span.badge.badge-${size}.badge-neutral`, attrs, 'Unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedStatus = status.toLowerCase();
|
||||||
|
let badgeClass = 'badge-neutral';
|
||||||
|
|
||||||
|
if (normalizedStatus.includes('available')) {
|
||||||
|
badgeClass = 'badge-success';
|
||||||
|
} else if (normalizedStatus.includes('building') || normalizedStatus.includes('scheduled')) {
|
||||||
|
badgeClass = 'badge-warning';
|
||||||
|
} else if (normalizedStatus.includes('requested') || normalizedStatus.includes('delegated')) {
|
||||||
|
badgeClass = 'badge-info';
|
||||||
|
} else if (normalizedStatus.includes('failed')) {
|
||||||
|
badgeClass = 'badge-error';
|
||||||
|
}
|
||||||
|
|
||||||
|
return m(`span.badge.badge-${size}.${badgeClass}`, attrs, status);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EventTypeBadge = {
|
||||||
|
view(vnode: any) {
|
||||||
|
const { eventType, size = 'sm', ...attrs } = vnode.attrs;
|
||||||
|
|
||||||
|
let badgeClass = 'badge-ghost';
|
||||||
|
let displayName = eventType;
|
||||||
|
|
||||||
|
switch (eventType) {
|
||||||
|
case 'build_request':
|
||||||
|
badgeClass = 'badge-primary';
|
||||||
|
displayName = 'Build';
|
||||||
|
break;
|
||||||
|
case 'job':
|
||||||
|
badgeClass = 'badge-secondary';
|
||||||
|
displayName = 'Job';
|
||||||
|
break;
|
||||||
|
case 'partition':
|
||||||
|
badgeClass = 'badge-accent';
|
||||||
|
displayName = 'Partition';
|
||||||
|
break;
|
||||||
|
case 'delegation':
|
||||||
|
badgeClass = 'badge-info';
|
||||||
|
displayName = 'Delegation';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m(`span.badge.badge-${size}.${badgeClass}`, attrs, displayName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -178,11 +178,17 @@ pub async fn get_build_status(
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Convert events to summary format for response
|
// Convert events to summary format for response
|
||||||
let event_summaries: Vec<BuildEventSummary> = events.into_iter().map(|e| BuildEventSummary {
|
let event_summaries: Vec<BuildEventSummary> = events.into_iter().map(|e| {
|
||||||
event_id: e.event_id,
|
let (job_label, partition_ref, delegated_build_id) = extract_navigation_data(&e.event_type);
|
||||||
timestamp: e.timestamp,
|
BuildEventSummary {
|
||||||
event_type: event_type_to_string(&e.event_type),
|
event_id: e.event_id,
|
||||||
message: event_to_message(&e.event_type),
|
timestamp: e.timestamp,
|
||||||
|
event_type: event_type_to_string(&e.event_type),
|
||||||
|
message: event_to_message(&e.event_type),
|
||||||
|
job_label,
|
||||||
|
partition_ref,
|
||||||
|
delegated_build_id,
|
||||||
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
let final_status_string = BuildGraphService::status_to_string(final_status);
|
let final_status_string = BuildGraphService::status_to_string(final_status);
|
||||||
|
|
@ -311,11 +317,17 @@ pub async fn get_partition_events(
|
||||||
Path(request): Path<PartitionEventsRequest>,
|
Path(request): Path<PartitionEventsRequest>,
|
||||||
) -> Result<Json<PartitionEventsResponse>, (StatusCode, Json<ErrorResponse>)> {
|
) -> Result<Json<PartitionEventsResponse>, (StatusCode, Json<ErrorResponse>)> {
|
||||||
let events = match service.event_log.get_partition_events(&request.ref_param, None).await {
|
let events = match service.event_log.get_partition_events(&request.ref_param, None).await {
|
||||||
Ok(events) => events.into_iter().map(|e| BuildEventSummary {
|
Ok(events) => events.into_iter().map(|e| {
|
||||||
event_id: e.event_id,
|
let (job_label, partition_ref, delegated_build_id) = extract_navigation_data(&e.event_type);
|
||||||
timestamp: e.timestamp,
|
BuildEventSummary {
|
||||||
event_type: event_type_to_string(&e.event_type),
|
event_id: e.event_id,
|
||||||
message: event_to_message(&e.event_type),
|
timestamp: e.timestamp,
|
||||||
|
event_type: event_type_to_string(&e.event_type),
|
||||||
|
message: event_to_message(&e.event_type),
|
||||||
|
job_label,
|
||||||
|
partition_ref,
|
||||||
|
delegated_build_id,
|
||||||
|
}
|
||||||
}).collect(),
|
}).collect(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to get partition events: {}", e);
|
error!("Failed to get partition events: {}", e);
|
||||||
|
|
@ -583,6 +595,28 @@ fn event_to_message(event_type: &Option<crate::build_event::EventType>) -> Strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_navigation_data(event_type: &Option<crate::build_event::EventType>) -> (Option<String>, Option<String>, Option<String>) {
|
||||||
|
match event_type {
|
||||||
|
Some(crate::build_event::EventType::JobEvent(event)) => {
|
||||||
|
let job_label = event.job_label.as_ref().map(|l| l.label.clone());
|
||||||
|
(job_label, None, None)
|
||||||
|
},
|
||||||
|
Some(crate::build_event::EventType::PartitionEvent(event)) => {
|
||||||
|
let partition_ref = event.partition_ref.as_ref().map(|r| r.str.clone());
|
||||||
|
(None, partition_ref, None)
|
||||||
|
},
|
||||||
|
Some(crate::build_event::EventType::DelegationEvent(event)) => {
|
||||||
|
let delegated_build_id = Some(event.delegated_to_build_request_id.clone());
|
||||||
|
(None, None, delegated_build_id)
|
||||||
|
},
|
||||||
|
Some(crate::build_event::EventType::BuildRequestEvent(_)) => {
|
||||||
|
// Build request events don't need navigation links (self-referential)
|
||||||
|
(None, None, None)
|
||||||
|
},
|
||||||
|
None => (None, None, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// New handlers for list endpoints
|
// New handlers for list endpoints
|
||||||
use axum::extract::Query;
|
use axum::extract::Query;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,10 @@ pub struct BuildEventSummary {
|
||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
pub event_type: String,
|
pub event_type: String,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
|
// Navigation-relevant fields (populated based on event type)
|
||||||
|
pub job_label: Option<String>, // For job events
|
||||||
|
pub partition_ref: Option<String>, // For partition events
|
||||||
|
pub delegated_build_id: Option<String>, // For delegation events
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue