@@ -773,6 +773,13 @@ public async Task ExecuteWebhookAsync(string json, string signature)
773773 { "event" , delivery . Event } ,
774774 { "translationEngineId" , delivery . Payload . Engine . Id } ,
775775 } ;
776+
777+ string ? draftGenerationRequestId = await GetDraftGenerationRequestIdForBuildAsync ( buildId ) ;
778+ if ( ! string . IsNullOrEmpty ( draftGenerationRequestId ) )
779+ {
780+ arguments [ "draftGenerationRequestId" ] = draftGenerationRequestId ;
781+ }
782+
776783 await eventMetricService . SaveEventMetricAsync (
777784 projectId ,
778785 userId : null ,
@@ -1750,6 +1757,9 @@ public async Task<LanguageDto> IsLanguageSupportedAsync(string languageCode, Can
17501757 CancellationToken cancellationToken
17511758 )
17521759 {
1760+ string ? buildId = null ;
1761+ Exception ? caughtException = null ;
1762+
17531763 try
17541764 {
17551765 // Record the SF Project id to help with debugging
@@ -1779,6 +1789,8 @@ await translationEnginesClient.GetAllBuildsAsync(translationEngineId, cancellati
17791789 . Where ( b => b . State == JobState . Completed )
17801790 . MaxBy ( b => b . DateFinished ) ;
17811791
1792+ buildId = translationBuild ? . Id ;
1793+
17821794 // Set the retrieved flag as in progress
17831795 await projectSecrets . UpdateAsync (
17841796 sfProjectId ,
@@ -1802,21 +1814,15 @@ await projectSecrets.UpdateAsync(
18021814 // Notify any SignalR clients subscribed to the project
18031815 await hubContext . NotifyBuildProgress (
18041816 sfProjectId ,
1805- new ServalBuildState
1806- {
1807- BuildId = translationBuild ? . Id ,
1808- State = nameof ( ServalData . PreTranslationsRetrieved ) ,
1809- }
1817+ new ServalBuildState { BuildId = buildId , State = nameof ( ServalData . PreTranslationsRetrieved ) }
18101818 ) ;
1811-
1812- // Return the build id
1813- return translationBuild ? . Id ;
18141819 }
18151820 }
18161821 catch ( TaskCanceledException e ) when ( e . InnerException is not TimeoutException )
18171822 {
18181823 // Do not log error - the job was cancelled
18191824 // Exclude TaskCanceledException with an inner TimeoutException, as this generated by an HttpClient timeout
1825+ caughtException = e ;
18201826
18211827 // Ensure that the retrieved flag is cleared
18221828 await projectSecrets . UpdateAsync (
@@ -1827,6 +1833,8 @@ await projectSecrets.UpdateAsync(
18271833 }
18281834 catch ( Exception e )
18291835 {
1836+ caughtException = e ;
1837+
18301838 // Log the error and report to bugsnag
18311839 const string message =
18321840 "Retrieve pre-translation status exception occurred for project {sfProjectId} running in background job." ;
@@ -1847,8 +1855,34 @@ await projectSecrets.UpdateAsync(
18471855 throw ;
18481856 }
18491857 }
1858+ finally
1859+ {
1860+ var payload = new Dictionary < string , object > { { "sfProjectId" , sfProjectId } } ;
1861+ if ( buildId is not null )
1862+ {
1863+ string ? draftGenerationRequestId = await GetDraftGenerationRequestIdForBuildAsync ( buildId ) ;
1864+ if ( ! string . IsNullOrEmpty ( draftGenerationRequestId ) )
1865+ {
1866+ payload [ "draftGenerationRequestId" ] = draftGenerationRequestId ;
1867+ }
1868+ }
1869+ if ( ! string . IsNullOrEmpty ( buildId ) )
1870+ {
1871+ payload [ "buildId" ] = buildId ;
1872+ }
18501873
1851- return null ;
1874+ await eventMetricService . SaveEventMetricAsync (
1875+ sfProjectId ,
1876+ userId : null ,
1877+ eventType : nameof ( RetrievePreTranslationStatusAsync ) ,
1878+ EventScope . Drafting ,
1879+ payload ,
1880+ result : buildId ,
1881+ exception : caughtException
1882+ ) ;
1883+ }
1884+
1885+ return buildId ;
18521886 }
18531887
18541888 public async Task StartBuildAsync ( string curUserId , string sfProjectId , CancellationToken cancellationToken )
@@ -1879,7 +1913,8 @@ public async Task StartBuildAsync(string curUserId, string sfProjectId, Cancella
18791913 curUserId ,
18801914 new BuildConfig { ProjectId = sfProjectId } ,
18811915 false ,
1882- CancellationToken . None
1916+ CancellationToken . None ,
1917+ null
18831918 ) ,
18841919 null ,
18851920 JobContinuationOptions . OnAnyFinishedState
@@ -1904,6 +1939,22 @@ public async Task StartPreTranslationBuildAsync(
19041939 CancellationToken cancellationToken
19051940 )
19061941 {
1942+ // SF-specific id of the draft generation request
1943+ string draftGenerationRequestId = ObjectId . GenerateNewId ( ) . ToString ( ) ;
1944+
1945+ await eventMetricService . SaveEventMetricAsync (
1946+ buildConfig . ProjectId ,
1947+ curUserId ,
1948+ eventType : nameof ( StartPreTranslationBuildAsync ) ,
1949+ EventScope . Drafting ,
1950+ new Dictionary < string , object >
1951+ {
1952+ { "curUserId" , curUserId } ,
1953+ { "buildConfig" , buildConfig } ,
1954+ { "draftGenerationRequestId" , draftGenerationRequestId } ,
1955+ }
1956+ ) ;
1957+
19071958 // Load the project from the realtime service
19081959 await using IConnection conn = await realtimeService . ConnectAsync ( curUserId ) ;
19091960 IDocument < SFProject > projectDoc = await conn . FetchAsync < SFProject > ( buildConfig . ProjectId ) ;
@@ -2018,7 +2069,14 @@ await projectDoc.SubmitJson0OpAsync(op =>
20182069 // so that the interceptor functions for BuildProjectAsync().
20192070 jobId = backgroundJobClient . ContinueJobWith < MachineProjectService > (
20202071 jobId ,
2021- r => r . BuildProjectForBackgroundJobAsync ( curUserId , buildConfig , true , CancellationToken . None )
2072+ r =>
2073+ r . BuildProjectForBackgroundJobAsync (
2074+ curUserId ,
2075+ buildConfig ,
2076+ true ,
2077+ CancellationToken . None ,
2078+ draftGenerationRequestId
2079+ )
20222080 ) ;
20232081
20242082 // Set the pre-translation queued date and time, and hang fire job id
@@ -2586,6 +2644,28 @@ CancellationToken cancellationToken
25862644 return project ;
25872645 }
25882646
2647+ /// <summary>
2648+ /// Gets the SF-specific draft generation request identifier for a build by looking up the BuildProjectAsync event.
2649+ /// </summary>
2650+ /// <param name="buildId">The Serval build identifier.</param>
2651+ /// <returns>The draft generation request identifier, or null if not found.</returns>
2652+ private async Task < string ? > GetDraftGenerationRequestIdForBuildAsync ( string buildId )
2653+ {
2654+ // BuildProjectAsync events serve as a record of what Serval build id corresponds to what draft generation
2655+ // request id.
2656+ QueryResults < EventMetric > buildProjectEvents = await eventMetricService . GetEventMetricsAsync (
2657+ projectId : null ,
2658+ scopes : [ EventScope . Drafting ] ,
2659+ eventTypes : [ nameof ( MachineProjectService . BuildProjectAsync ) ]
2660+ ) ;
2661+ EventMetric ? buildEvent = buildProjectEvents . Results . FirstOrDefault ( e => e . Result ? . ToString ( ) == buildId ) ;
2662+ if ( buildEvent ? . Payload . TryGetValue ( "draftGenerationRequestId" , out BsonValue ? requestId ) == true )
2663+ {
2664+ return requestId . ToString ( ) ;
2665+ }
2666+ return null ;
2667+ }
2668+
25892669 private async Task < string > GetTranslationIdAsync (
25902670 string sfProjectId ,
25912671 bool preTranslate ,
0 commit comments