@@ -16,7 +16,7 @@ import { SFProjectService } from '../core/sf-project.service';
1616import { EventMetric } from '../event-metrics/event-metric' ;
1717import { NoticeComponent } from '../shared/notice/notice.component' ;
1818import { projectLabel } from '../shared/utils' ;
19- import { JobEventsDialogComponent } from './job-events -dialog.component' ;
19+ import { JobDetailsDialogComponent } from './job-details -dialog.component' ;
2020import { ServalAdministrationService } from './serval-administration.service' ;
2121
2222/**
@@ -36,6 +36,7 @@ interface DraftJob {
3636 finishEvent ?: EventMetric ;
3737 cancelEvent ?: EventMetric ;
3838 events : EventMetric [ ] ;
39+ additionalEvents : EventMetric [ ] ; // Events with same build ID that weren't included in the main job tracking
3940 status : 'running' | 'success' | 'failed' | 'cancelled' | 'incomplete' ;
4041 startTime : Date | null ;
4142 finishTime : Date | null ;
@@ -61,6 +62,14 @@ interface Row {
6162 clearmlUrl ?: string ;
6263}
6364
65+ const DRAFTING_EVENTS = [
66+ 'StartPreTranslationBuildAsync' ,
67+ 'BuildProjectAsync' ,
68+ 'RetrievePreTranslationStatusAsync' ,
69+ 'ExecuteWebhookAsync' ,
70+ 'CancelPreTranslationBuildAsync'
71+ ] ;
72+
6473@Component ( {
6574 selector : 'app-draft-jobs' ,
6675 templateUrl : './draft-jobs.component.html' ,
@@ -75,8 +84,7 @@ export class DraftJobsComponent extends DataLoadingComponent implements OnInit {
7584 'translationBooks' ,
7685 'startTime' ,
7786 'duration' ,
78- 'author' ,
79- 'buildId'
87+ 'author'
8088 ] ;
8189 rows : Row [ ] = [ ] ;
8290
@@ -102,7 +110,7 @@ export class DraftJobsComponent extends DataLoadingComponent implements OnInit {
102110 this . daysBack$ . next ( value ) ;
103111 }
104112
105- private eventMetrics ?: EventMetric [ ] ;
113+ private draftEvents ?: EventMetric [ ] ;
106114 private draftJobs : DraftJob [ ] = [ ] ;
107115 private projectNames = new Map < string , string | null > ( ) ; // Cache for project names
108116 private projectShortNames = new Map < string , string | null > ( ) ; // Cache for project short names
@@ -124,16 +132,16 @@ export class DraftJobsComponent extends DataLoadingComponent implements OnInit {
124132 }
125133
126134 get isLoading ( ) : boolean {
127- return this . eventMetrics == null ;
135+ return this . draftEvents == null ;
128136 }
129137
130138 ngOnInit ( ) : void {
131139 if (
132- ! this . columnsToDisplay . includes ( 'details ' ) &&
140+ ! this . columnsToDisplay . includes ( 'buildDetails ' ) &&
133141 ( this . authService . currentUserRoles . includes ( SystemRole . ServalAdmin ) ||
134142 this . authService . currentUserRoles . includes ( SystemRole . SystemAdmin ) )
135143 ) {
136- this . columnsToDisplay . push ( 'details ' ) ;
144+ this . columnsToDisplay . push ( 'buildDetails ' ) ;
137145 }
138146 this . loadingStarted ( ) ;
139147
@@ -159,14 +167,15 @@ export class DraftJobsComponent extends DataLoadingComponent implements OnInit {
159167 }
160168
161169 if ( isOnline ) {
162- const queryResults = await this . projectService . onlineAllEventMetricsForConstructionDraftJobs (
170+ const queryResults = await this . projectService . onlineAllEventMetricsForConstructingDraftJobs (
171+ DRAFTING_EVENTS ,
163172 projectFilterId ?? undefined ,
164173 daysBack === 'all_time' ? undefined : daysBack
165174 ) ;
166175 if ( Array . isArray ( queryResults ?. results ) ) {
167- this . eventMetrics = queryResults . results as EventMetric [ ] ;
176+ this . draftEvents = queryResults . results as EventMetric [ ] ;
168177 } else {
169- this . eventMetrics = [ ] ;
178+ this . draftEvents = [ ] ;
170179 }
171180 this . processDraftJobs ( ) ;
172181 await this . loadProjectNames ( ) ;
@@ -178,20 +187,52 @@ export class DraftJobsComponent extends DataLoadingComponent implements OnInit {
178187 . subscribe ( ) ;
179188 }
180189
181- openDetailsDialog ( job : DraftJob ) : void {
190+ openJobDetailsDialog ( job : DraftJob ) : void {
191+ if ( job . buildId == null ) {
192+ void this . noticeService . show ( 'No build ID available for this job' ) ;
193+ return ;
194+ }
195+
196+ // Format event-based duration if available
197+ const eventBasedDuration = job . duration != null ? this . formatDuration ( job . duration ) : undefined ;
198+
199+ // Format start time if available
200+ const startTime = job . startTime != null ? this . i18n . formatDate ( job . startTime , { showTimeZone : true } ) : undefined ;
201+
182202 // Collect all events that were used to create this job
183203 const events = [ job . startEvent , job . buildEvent , job . finishEvent , job . cancelEvent ]
184204 . filter ( ( event ) : event is EventMetric => event != null )
185205 . sort ( ( a , b ) => new Date ( a . timeStamp ) . getTime ( ) - new Date ( b . timeStamp ) . getTime ( ) ) ;
186206
207+ // Get ClearML URL
208+ const clearmlUrl = job . buildId
209+ ? `https://app.sil.hosted.allegro.ai/projects?gq=${ job . buildId } &tab=tasks`
210+ : undefined ;
211+
212+ // Count how many builds have started on this project since this build started
213+ let buildsStartedSince = 0 ;
214+ if ( job . startTime != null ) {
215+ const jobStartTime = job . startTime ;
216+ buildsStartedSince = this . draftJobs . filter (
217+ otherJob =>
218+ otherJob . projectId === job . projectId && otherJob . startTime != null && otherJob . startTime > jobStartTime
219+ ) . length ;
220+ }
221+
187222 const dialogData = {
223+ buildId : job . buildId ,
188224 projectId : job . projectId ,
189225 jobStatus : this . getStatusDisplay ( job . status ) ,
190- events
226+ events,
227+ additionalEvents : job . additionalEvents ,
228+ eventBasedDuration,
229+ startTime,
230+ clearmlUrl,
231+ buildsStartedSince
191232 } ;
192233
193- const dialogConfig : MatDialogConfig < any > = { data : dialogData , width : '800px ' , maxHeight : '80vh' } ;
194- this . dialogService . openMatDialog ( JobEventsDialogComponent , dialogConfig ) ;
234+ const dialogConfig : MatDialogConfig < any > = { data : dialogData , width : '1000px ' , maxHeight : '80vh' } ;
235+ this . dialogService . openMatDialog ( JobDetailsDialogComponent , dialogConfig ) ;
195236 }
196237
197238 clearProjectFilter ( ) : void {
@@ -203,26 +244,16 @@ export class DraftJobsComponent extends DataLoadingComponent implements OnInit {
203244 }
204245
205246 private processDraftJobs ( ) : void {
206- if ( this . eventMetrics == null ) {
247+ if ( this . draftEvents == null ) {
207248 this . draftJobs = [ ] ;
208249 this . generateRows ( ) ;
209250 return ;
210251 }
211252
212- // Filter draft-related events
213- const draftEvents = this . eventMetrics . filter (
214- event =>
215- event . eventType === 'StartPreTranslationBuildAsync' ||
216- event . eventType === 'BuildProjectAsync' ||
217- event . eventType === 'RetrievePreTranslationStatusAsync' ||
218- event . eventType === 'ExecuteWebhookAsync' ||
219- event . eventType === 'CancelPreTranslationBuildAsync'
220- ) ;
221-
222253 const jobs : DraftJob [ ] = [ ] ;
223254
224255 // Step 1: Find all build events (BuildProjectAsync) - these are our anchors
225- const buildEvents = draftEvents . filter ( event => event . eventType === 'BuildProjectAsync' ) ;
256+ const buildEvents = this . draftEvents . filter ( event => event . eventType === 'BuildProjectAsync' ) ;
226257
227258 // Step 2: For each build event, find the nearest preceding start event
228259 for ( const buildEvent of buildEvents ) {
@@ -234,7 +265,7 @@ export class DraftJobsComponent extends DataLoadingComponent implements OnInit {
234265 const buildTime = new Date ( buildEvent . timeStamp ) ;
235266
236267 // Find all StartPreTranslationBuildAsync events for this project that precede the build
237- const candidateStartEvents = draftEvents . filter (
268+ const candidateStartEvents = this . draftEvents . filter (
238269 event =>
239270 event . eventType === 'StartPreTranslationBuildAsync' &&
240271 event . projectId === buildEvent . projectId &&
@@ -252,7 +283,7 @@ export class DraftJobsComponent extends DataLoadingComponent implements OnInit {
252283 } ) ;
253284
254285 // Step 3: Find the first completion event after the build
255- const candidateCompletionEvents = draftEvents . filter ( event => {
286+ const candidateCompletionEvents = this . draftEvents . filter ( event => {
256287 if ( event . projectId !== buildEvent . projectId ) return false ;
257288 if ( new Date ( event . timeStamp ) <= buildTime ) return false ;
258289
@@ -295,6 +326,7 @@ export class DraftJobsComponent extends DataLoadingComponent implements OnInit {
295326 finishEvent : undefined ,
296327 cancelEvent : undefined ,
297328 events : [ ] ,
329+ additionalEvents : [ ] ,
298330 startTime : new Date ( startEvent . timeStamp ) ,
299331 userId : startEvent . userId ,
300332 trainingBooks,
@@ -314,6 +346,22 @@ export class DraftJobsComponent extends DataLoadingComponent implements OnInit {
314346 }
315347 }
316348
349+ // Step 4: Collect additional events (any events with same build ID that weren't already included)
350+ const additionalEvents = this . draftEvents . filter ( event => {
351+ // Don't include events we've already tracked
352+ if ( event === startEvent || event === buildEvent || event === completionEvent ) {
353+ return false ;
354+ }
355+
356+ // Check if this event has the same build ID
357+ const eventBuildId = this . extractBuildIdFromEvent ( event ) ;
358+ return eventBuildId === buildId ;
359+ } ) ;
360+
361+ job . additionalEvents = additionalEvents . sort (
362+ ( a , b ) => new Date ( a . timeStamp ) . getTime ( ) - new Date ( b . timeStamp ) . getTime ( )
363+ ) ;
364+
317365 jobs . push ( job ) ;
318366 }
319367
0 commit comments