@@ -10,7 +10,7 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc
10
10
import { Action2 , IAction2Options , MenuRegistry , registerAction2 } from 'vs/platform/actions/common/actions' ;
11
11
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions' ;
12
12
import { localize } from 'vs/nls' ;
13
- import { IEditSessionsStorageService , Change , ChangeType , Folder , EditSession , FileType , EDIT_SESSION_SYNC_CATEGORY , EDIT_SESSIONS_CONTAINER_ID , EditSessionSchemaVersion , IEditSessionsLogService , EDIT_SESSIONS_VIEW_ICON , EDIT_SESSIONS_TITLE , EDIT_SESSIONS_SHOW_VIEW , EDIT_SESSIONS_DATA_VIEW_ID , decodeEditSessionFileContent } from 'vs/workbench/contrib/editSessions/common/editSessions' ;
13
+ import { IEditSessionsStorageService , Change , ChangeType , Folder , EditSession , FileType , EDIT_SESSION_SYNC_CATEGORY , EDIT_SESSIONS_CONTAINER_ID , EditSessionSchemaVersion , IEditSessionsLogService , EDIT_SESSIONS_VIEW_ICON , EDIT_SESSIONS_TITLE , EDIT_SESSIONS_SHOW_VIEW , EDIT_SESSIONS_DATA_VIEW_ID , decodeEditSessionFileContent , hashedEditSessionId } from 'vs/workbench/contrib/editSessions/common/editSessions' ;
14
14
import { ISCMRepository , ISCMService } from 'vs/workbench/contrib/scm/common/scm' ;
15
15
import { IFileService } from 'vs/platform/files/common/files' ;
16
16
import { IWorkspaceContextService , IWorkspaceFolder , WorkbenchState } from 'vs/platform/workspace/common/workspace' ;
@@ -83,7 +83,7 @@ const showOutputChannelCommand: IAction2Options = {
83
83
const resumingProgressOptions = {
84
84
location : ProgressLocation . Window ,
85
85
type : 'syncing' ,
86
- title : `[${ localize ( 'resuming edit session window' , 'Resuming edit session ...' ) } ](command:${ showOutputChannelCommand . id } )`
86
+ title : `[${ localize ( 'resuming working changes window' , 'Resuming working changes ...' ) } ](command:${ showOutputChannelCommand . id } )`
87
87
} ;
88
88
const queryParamName = 'editSessionId' ;
89
89
@@ -138,66 +138,54 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
138
138
this . _register ( this . editSessionsStorageService . onDidSignOut ( ( ) => this . updateAccountsMenuBadge ( ) ) ) ;
139
139
}
140
140
141
- private autoResumeEditSession ( ) {
142
- void this . progressService . withProgress ( resumingProgressOptions , async ( ) => {
143
- performance . mark ( 'code/willResumeEditSessionFromIdentifier' ) ;
144
-
145
- type ResumeEvent = { } ;
146
- type ResumeClassification = {
147
- owner : 'joyceerhl' ; comment : 'Reporting when an action is resumed from an edit session identifier.' ;
141
+ private async autoResumeEditSession ( ) {
142
+ const shouldAutoResumeOnReload = this . configurationService . getValue ( 'workbench.editSessions.autoResume' ) === 'onReload' ;
143
+
144
+ if ( this . environmentService . editSessionId !== undefined ) {
145
+ this . logService . info ( `Resuming cloud changes, reason: found editSessionId ${ this . environmentService . editSessionId } in environment service...` ) ;
146
+ await this . progressService . withProgress ( resumingProgressOptions , async ( ) => await this . resumeEditSession ( this . environmentService . editSessionId ) . finally ( ( ) => this . environmentService . editSessionId = undefined ) ) ;
147
+ } else if ( shouldAutoResumeOnReload && this . editSessionsStorageService . isSignedIn ) {
148
+ this . logService . info ( 'Resuming cloud changes, reason: cloud changes enabled...' ) ;
149
+ // Attempt to resume edit session based on edit workspace identifier
150
+ // Note: at this point if the user is not signed into edit sessions,
151
+ // we don't want them to be prompted to sign in and should just return early
152
+ await this . progressService . withProgress ( resumingProgressOptions , async ( ) => await this . resumeEditSession ( undefined , true ) ) ;
153
+ } else if ( shouldAutoResumeOnReload ) {
154
+ // The application has previously launched via a protocol URL Continue On flow
155
+ const hasApplicationLaunchedFromContinueOnFlow = this . storageService . getBoolean ( EditSessionsContribution . APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY , StorageScope . APPLICATION , false ) ;
156
+
157
+ const handlePendingEditSessions = ( ) => {
158
+ // display a badge in the accounts menu but do not prompt the user to sign in again
159
+ this . updateAccountsMenuBadge ( ) ;
160
+ // attempt a resume if we are in a pending state and the user just signed in
161
+ const disposable = this . editSessionsStorageService . onDidSignIn ( async ( ) => {
162
+ disposable . dispose ( ) ;
163
+ await this . progressService . withProgress ( resumingProgressOptions , async ( ) => await this . resumeEditSession ( undefined , true ) ) ;
164
+ this . storageService . remove ( EditSessionsContribution . APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY , StorageScope . APPLICATION ) ;
165
+ this . environmentService . continueOn = undefined ;
166
+ } ) ;
148
167
} ;
149
- this . telemetryService . publicLog2 < ResumeEvent , ResumeClassification > ( 'editSessions.continue.resume' ) ;
150
-
151
- const shouldAutoResumeOnReload = this . configurationService . getValue ( 'workbench.editSessions.autoResume' ) === 'onReload' ;
152
-
153
- if ( this . environmentService . editSessionId !== undefined ) {
154
- this . logService . info ( `Resuming cloud changes, reason: found editSessionId ${ this . environmentService . editSessionId } in environment service...` ) ;
155
- await this . resumeEditSession ( this . environmentService . editSessionId ) . finally ( ( ) => this . environmentService . editSessionId = undefined ) ;
156
- } else if ( shouldAutoResumeOnReload && this . editSessionsStorageService . isSignedIn ) {
157
- this . logService . info ( 'Resuming cloud changes, reason: cloud changes enabled...' ) ;
158
- // Attempt to resume edit session based on edit workspace identifier
159
- // Note: at this point if the user is not signed into edit sessions,
160
- // we don't want them to be prompted to sign in and should just return early
161
- await this . resumeEditSession ( undefined , true ) ;
162
- } else if ( shouldAutoResumeOnReload ) {
163
- // The application has previously launched via a protocol URL Continue On flow
164
- const hasApplicationLaunchedFromContinueOnFlow = this . storageService . getBoolean ( EditSessionsContribution . APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY , StorageScope . APPLICATION , false ) ;
165
-
166
- const handlePendingEditSessions = ( ) => {
167
- // display a badge in the accounts menu but do not prompt the user to sign in again
168
- this . updateAccountsMenuBadge ( ) ;
169
- // attempt a resume if we are in a pending state and the user just signed in
170
- const disposable = this . editSessionsStorageService . onDidSignIn ( async ( ) => {
171
- disposable . dispose ( ) ;
172
- this . resumeEditSession ( undefined , true ) ;
173
- this . storageService . remove ( EditSessionsContribution . APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY , StorageScope . APPLICATION ) ;
174
- this . environmentService . continueOn = undefined ;
175
- } ) ;
176
- } ;
177
168
178
- if ( ( this . environmentService . continueOn !== undefined ) &&
179
- ! this . editSessionsStorageService . isSignedIn &&
180
- // and user has not yet been prompted to sign in on this machine
181
- hasApplicationLaunchedFromContinueOnFlow === false
182
- ) {
183
- this . storageService . store ( EditSessionsContribution . APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY , true , StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
184
- await this . editSessionsStorageService . initialize ( true ) ;
185
- if ( this . editSessionsStorageService . isSignedIn ) {
186
- await this . resumeEditSession ( undefined , true ) ;
187
- } else {
188
- handlePendingEditSessions ( ) ;
189
- }
190
- // store the fact that we prompted the user
191
- } else if ( ! this . editSessionsStorageService . isSignedIn &&
192
- // and user has been prompted to sign in on this machine
193
- hasApplicationLaunchedFromContinueOnFlow === true
194
- ) {
169
+ if ( ( this . environmentService . continueOn !== undefined ) &&
170
+ ! this . editSessionsStorageService . isSignedIn &&
171
+ // and user has not yet been prompted to sign in on this machine
172
+ hasApplicationLaunchedFromContinueOnFlow === false
173
+ ) {
174
+ this . storageService . store ( EditSessionsContribution . APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY , true , StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
175
+ await this . editSessionsStorageService . initialize ( true ) ;
176
+ if ( this . editSessionsStorageService . isSignedIn ) {
177
+ await this . progressService . withProgress ( resumingProgressOptions , async ( ) => await this . resumeEditSession ( undefined , true ) ) ;
178
+ } else {
195
179
handlePendingEditSessions ( ) ;
196
180
}
181
+ // store the fact that we prompted the user
182
+ } else if ( ! this . editSessionsStorageService . isSignedIn &&
183
+ // and user has been prompted to sign in on this machine
184
+ hasApplicationLaunchedFromContinueOnFlow === true
185
+ ) {
186
+ handlePendingEditSessions ( ) ;
197
187
}
198
-
199
- performance . mark ( 'code/didResumeEditSessionFromIdentifier' ) ;
200
- } ) ;
188
+ }
201
189
}
202
190
203
191
private updateAccountsMenuBadge ( ) {
@@ -292,17 +280,19 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
292
280
}
293
281
294
282
async run ( accessor : ServicesAccessor , workspaceUri : URI | undefined , destination : string | undefined ) : Promise < void > {
295
- type ContinueEditSessionEvent = { } ;
296
- type ContinueEditSessionClassification = {
297
- owner : 'joyceerhl' ; comment : 'Reporting when the continue edit session action is run.' ;
283
+ type ContinueOnEventOutcome = { outcome : string ; hashedId ?: string } ;
284
+ type ContinueOnClassificationOutcome = {
285
+ owner : 'joyceerhl' ; comment : 'Reporting the outcome of invoking the Continue On action.' ;
286
+ outcome : { classification : 'SystemMetaData' ; purpose : 'FeatureInsight' ; isMeasurement : true ; comment : 'The outcome of invoking continue edit session.' } ;
287
+ hashedId ?: { classification : 'SystemMetaData' ; purpose : 'FeatureInsight' ; isMeasurement : true ; comment : 'The hash of the stored edit session id, for correlating success of stores and resumes.' } ;
298
288
} ;
299
- that . telemetryService . publicLog2 < ContinueEditSessionEvent , ContinueEditSessionClassification > ( 'editSessions.continue.store' ) ;
300
289
301
290
// First ask the user to pick a destination, if necessary
302
291
let uri : URI | 'noDestinationUri' | undefined = workspaceUri ;
303
292
if ( ! destination && ! uri ) {
304
293
destination = await that . pickContinueEditSessionDestination ( ) ;
305
294
if ( ! destination ) {
295
+ that . telemetryService . publicLog2 < ContinueOnEventOutcome , ContinueOnClassificationOutcome > ( 'continueOn.editSessions.pick.outcome' , { outcome : 'noSelection' } ) ;
306
296
return ;
307
297
}
308
298
}
@@ -313,16 +303,36 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
313
303
// Run the store action to get back a ref
314
304
let ref : string | undefined ;
315
305
if ( shouldStoreEditSession ) {
306
+ type ContinueWithEditSessionEvent = { } ;
307
+ type ContinueWithEditSessionClassification = {
308
+ owner : 'joyceerhl' ; comment : 'Reporting when storing an edit session as part of the Continue On flow.' ;
309
+ } ;
310
+ that . telemetryService . publicLog2 < ContinueWithEditSessionEvent , ContinueWithEditSessionClassification > ( 'continueOn.editSessions.store' ) ;
311
+
316
312
const cancellationTokenSource = new CancellationTokenSource ( ) ;
317
- ref = await that . progressService . withProgress ( {
318
- location : ProgressLocation . Notification ,
319
- cancellable : true ,
320
- type : 'syncing' ,
321
- title : localize ( 'store your working changes' , 'Storing your working changes...' )
322
- } , async ( ) => that . storeEditSession ( false , cancellationTokenSource . token ) , ( ) => {
323
- cancellationTokenSource . cancel ( ) ;
324
- cancellationTokenSource . dispose ( ) ;
325
- } ) ;
313
+ try {
314
+ ref = await that . progressService . withProgress ( {
315
+ location : ProgressLocation . Notification ,
316
+ cancellable : true ,
317
+ type : 'syncing' ,
318
+ title : localize ( 'store your working changes' , 'Storing your working changes...' )
319
+ } , async ( ) => {
320
+ const ref = await that . storeEditSession ( false , cancellationTokenSource . token ) ;
321
+ if ( ref !== undefined ) {
322
+ that . telemetryService . publicLog2 < ContinueOnEventOutcome , ContinueOnClassificationOutcome > ( 'continueOn.editSessions.store.outcome' , { outcome : 'storeSucceeded' , hashedId : hashedEditSessionId ( ref ) } ) ;
323
+ } else {
324
+ that . telemetryService . publicLog2 < ContinueOnEventOutcome , ContinueOnClassificationOutcome > ( 'continueOn.editSessions.store.outcome' , { outcome : 'storeSkipped' } ) ;
325
+ }
326
+ return ref ;
327
+ } , ( ) => {
328
+ cancellationTokenSource . cancel ( ) ;
329
+ cancellationTokenSource . dispose ( ) ;
330
+ that . telemetryService . publicLog2 < ContinueOnEventOutcome , ContinueOnClassificationOutcome > ( 'continueOn.editSessions.store.outcome' , { outcome : 'storeCancelledByUser' } ) ;
331
+ } ) ;
332
+ } catch ( ex ) {
333
+ that . telemetryService . publicLog2 < ContinueOnEventOutcome , ContinueOnClassificationOutcome > ( 'continueOn.editSessions.store.outcome' , { outcome : 'storeFailed' } ) ;
334
+ throw ex ;
335
+ }
326
336
}
327
337
328
338
// Append the ref to the URI
@@ -364,15 +374,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
364
374
}
365
375
366
376
async run ( accessor : ServicesAccessor , editSessionId ?: string ) : Promise < void > {
367
- await that . progressService . withProgress ( resumingProgressOptions , async ( ) => {
368
- type ResumeEvent = { } ;
369
- type ResumeClassification = {
370
- owner : 'joyceerhl' ; comment : 'Reporting when the resume edit session action is invoked.' ;
371
- } ;
372
- that . telemetryService . publicLog2 < ResumeEvent , ResumeClassification > ( 'editSessions.resume' ) ;
373
-
374
- await that . resumeEditSession ( editSessionId ) ;
375
- } ) ;
377
+ await that . progressService . withProgress ( resumingProgressOptions , async ( ) => await that . resumeEditSession ( editSessionId ) ) ;
376
378
}
377
379
} ) ) ;
378
380
}
@@ -423,6 +425,16 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
423
425
return ;
424
426
}
425
427
428
+ type ResumeEvent = { outcome : string ; hashedId ?: string } ;
429
+ type ResumeClassification = {
430
+ owner : 'joyceerhl' ; comment : 'Reporting when an edit session is resumed from an edit session identifier.' ;
431
+ outcome : { classification : 'SystemMetaData' ; purpose : 'FeatureInsight' ; isMeasurement : true ; comment : 'The outcome of resuming the edit session.' } ;
432
+ hashedId ?: { classification : 'SystemMetaData' ; purpose : 'FeatureInsight' ; isMeasurement : true ; comment : 'The hash of the stored edit session id, for correlating success of stores and resumes.' } ;
433
+ } ;
434
+ this . telemetryService . publicLog2 < ResumeEvent , ResumeClassification > ( 'editSessions.resume' ) ;
435
+
436
+ performance . mark ( 'code/willResumeEditSessionFromIdentifier' ) ;
437
+
426
438
const data = await this . editSessionsStorageService . read ( ref ) ;
427
439
if ( ! data ) {
428
440
if ( ref === undefined && ! silent ) {
@@ -438,6 +450,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
438
450
439
451
if ( editSession . version > EditSessionSchemaVersion ) {
440
452
this . notificationService . error ( localize ( 'client too old' , "Please upgrade to a newer version of {0} to resume your working changes from the cloud." , this . productService . nameLong ) ) ;
453
+ this . telemetryService . publicLog2 < ResumeEvent , ResumeClassification > ( 'editSessions.resume.outcome' , { hashedId : hashedEditSessionId ( ref ) , outcome : 'clientUpdateNeeded' } ) ;
441
454
return ;
442
455
}
443
456
@@ -480,10 +493,14 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
480
493
this . logService . info ( `Deleting edit session with ref ${ ref } after successfully applying it to current workspace...` ) ;
481
494
await this . editSessionsStorageService . delete ( ref ) ;
482
495
this . logService . info ( `Deleted edit session with ref ${ ref } .` ) ;
496
+
497
+ this . telemetryService . publicLog2 < ResumeEvent , ResumeClassification > ( 'editSessions.resume.outcome' , { hashedId : hashedEditSessionId ( ref ) , outcome : 'resumeSucceeded' } ) ;
483
498
} catch ( ex ) {
484
499
this . logService . error ( 'Failed to resume edit session, reason: ' , ( ex as Error ) . toString ( ) ) ;
485
500
this . notificationService . error ( localize ( 'resume failed' , "Failed to resume your working changes from the cloud." ) ) ;
486
501
}
502
+
503
+ performance . mark ( 'code/didResumeEditSessionFromIdentifier' ) ;
487
504
}
488
505
489
506
private async generateChanges ( editSession : EditSession , ref : string , force = false ) {
@@ -693,19 +710,30 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
693
710
}
694
711
695
712
private async shouldContinueOnWithEditSession ( ) : Promise < boolean > {
713
+ type EditSessionsAuthCheckEvent = { outcome : string } ;
714
+ type EditSessionsAuthCheckClassification = {
715
+ owner : 'joyceerhl' ; comment : 'Reporting whether we can and should store edit session as part of Continue On.' ;
716
+ outcome : { classification : 'SystemMetaData' ; purpose : 'FeatureInsight' ; isMeasurement : true ; comment : 'The outcome of checking whether we can store an edit session as part of the Continue On flow.' } ;
717
+ } ;
718
+
696
719
// If the user is already signed in, we should store edit session
697
720
if ( this . editSessionsStorageService . isSignedIn ) {
698
721
return this . hasEditSession ( ) ;
699
722
}
700
723
701
724
// If the user has been asked before and said no, don't use edit sessions
702
725
if ( this . configurationService . getValue ( useEditSessionsWithContinueOn ) === 'off' ) {
726
+ this . telemetryService . publicLog2 < EditSessionsAuthCheckEvent , EditSessionsAuthCheckClassification > ( 'continueOn.editSessions.canStore.outcome' , { outcome : 'disabledEditSessionsViaSetting' } ) ;
703
727
return false ;
704
728
}
705
729
706
730
// Prompt the user to use edit sessions if they currently could benefit from using it
707
731
if ( this . hasEditSession ( ) ) {
708
- return this . editSessionsStorageService . initialize ( true ) ;
732
+ const initialized = await this . editSessionsStorageService . initialize ( true ) ;
733
+ if ( ! initialized ) {
734
+ this . telemetryService . publicLog2 < EditSessionsAuthCheckEvent , EditSessionsAuthCheckClassification > ( 'continueOn.editSessions.canStore.outcome' , { outcome : 'didNotEnableEditSessionsWhenPrompted' } ) ;
735
+ }
736
+ return initialized ;
709
737
}
710
738
711
739
return false ;
@@ -827,16 +855,33 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
827
855
}
828
856
829
857
private async resolveDestination ( command : string ) : Promise < URI | 'noDestinationUri' | undefined > {
858
+ type EvaluateContinueOnDestinationEvent = { outcome : string ; selection : string } ;
859
+ type EvaluateContinueOnDestinationClassification = {
860
+ owner : 'joyceerhl' ; comment : 'Reporting the outcome of evaluating a selected Continue On destination option.' ;
861
+ selection : { classification : 'SystemMetaData' ; purpose : 'FeatureInsight' ; isMeasurement : true ; comment : 'The selected Continue On destination option.' } ;
862
+ outcome : { classification : 'SystemMetaData' ; purpose : 'FeatureInsight' ; isMeasurement : true ; comment : 'The outcome of evaluating the selected Continue On destination option.' } ;
863
+ } ;
864
+
830
865
try {
831
866
const uri = await this . commandService . executeCommand ( command ) ;
832
867
833
868
// Some continue on commands do not return a URI
834
869
// to support extensions which want to be in control
835
870
// of how the destination is opened
836
- if ( uri === undefined ) { return 'noDestinationUri' ; }
871
+ if ( uri === undefined ) {
872
+ this . telemetryService . publicLog2 < EvaluateContinueOnDestinationEvent , EvaluateContinueOnDestinationClassification > ( 'continueOn.openDestination.outcome' , { selection : command , outcome : 'noDestinationUri' } ) ;
873
+ return 'noDestinationUri' ;
874
+ }
837
875
838
- return URI . isUri ( uri ) ? uri : undefined ;
876
+ if ( URI . isUri ( uri ) ) {
877
+ this . telemetryService . publicLog2 < EvaluateContinueOnDestinationEvent , EvaluateContinueOnDestinationClassification > ( 'continueOn.openDestination.outcome' , { selection : command , outcome : 'resolvedUri' } ) ;
878
+ return uri ;
879
+ }
880
+
881
+ this . telemetryService . publicLog2 < EvaluateContinueOnDestinationEvent , EvaluateContinueOnDestinationClassification > ( 'continueOn.openDestination.outcome' , { selection : command , outcome : 'invalidDestination' } ) ;
882
+ return undefined ;
839
883
} catch ( ex ) {
884
+ this . telemetryService . publicLog2 < EvaluateContinueOnDestinationEvent , EvaluateContinueOnDestinationClassification > ( 'continueOn.openDestination.outcome' , { selection : command , outcome : 'unknownError' } ) ;
840
885
return undefined ;
841
886
}
842
887
}
0 commit comments