5
5
6
6
import { localize } from '../../../../nls.js' ;
7
7
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js' ;
8
- import type { IKeyValueStorage , IExperimentationTelemetry } from 'tas-client-umd' ;
8
+ import type { IKeyValueStorage , IExperimentationTelemetry , ExperimentationService as TASClient } from 'tas-client-umd' ;
9
9
import { MementoObject , Memento } from '../../../common/memento.js' ;
10
10
import { ITelemetryService , TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js' ;
11
11
import { IStorageService , StorageScope , StorageTarget } from '../../../../platform/storage/common/storage.js' ;
12
12
import { ITelemetryData } from '../../../../base/common/actions.js' ;
13
13
import { InstantiationType , registerSingleton } from '../../../../platform/instantiation/common/extensions.js' ;
14
14
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js' ;
15
15
import { IProductService } from '../../../../platform/product/common/productService.js' ;
16
- import { IAssignmentService } from '../../../../platform/assignment/common/assignment.js' ;
16
+ import { ASSIGNMENT_REFETCH_INTERVAL , ASSIGNMENT_STORAGE_KEY , AssignmentFilterProvider , IAssignmentService , TargetPopulation } from '../../../../platform/assignment/common/assignment.js' ;
17
17
import { Registry } from '../../../../platform/registry/common/platform.js' ;
18
- import { BaseAssignmentService } from '../../../../platform/assignment/common/assignmentService.js' ;
19
18
import { workbenchConfigurationNodeBase } from '../../../common/configuration.js' ;
20
19
import { IConfigurationRegistry , Extensions as ConfigurationExtensions , ConfigurationScope } from '../../../../platform/configuration/common/configurationRegistry.js' ;
21
20
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js' ;
22
21
import { getTelemetryLevel } from '../../../../platform/telemetry/common/telemetryUtils.js' ;
22
+ import { importAMDNodeModule } from '../../../../amdX.js' ;
23
+ import { timeout } from '../../../../base/common/async.js' ;
23
24
24
- export const IWorkbenchAssignmentService = createDecorator < IWorkbenchAssignmentService > ( 'WorkbenchAssignmentService ' ) ;
25
+ export const IWorkbenchAssignmentService = createDecorator < IWorkbenchAssignmentService > ( 'assignmentService ' ) ;
25
26
26
27
export interface IWorkbenchAssignmentService extends IAssignmentService {
27
28
getCurrentExperiments ( ) : Promise < string [ ] | undefined > ;
28
29
}
29
30
30
31
class MementoKeyValueStorage implements IKeyValueStorage {
31
- private mementoObj : MementoObject ;
32
- constructor ( private memento : Memento ) {
32
+
33
+ private readonly mementoObj : MementoObject ;
34
+
35
+ constructor ( private readonly memento : Memento ) {
33
36
this . mementoObj = memento . getMemento ( StorageScope . APPLICATION , StorageTarget . MACHINE ) ;
34
37
}
35
38
36
39
async getValue < T > ( key : string , defaultValue ?: T | undefined ) : Promise < T | undefined > {
37
40
const value = await this . mementoObj [ key ] ;
41
+
38
42
return value || defaultValue ;
39
43
}
40
44
@@ -45,16 +49,17 @@ class MementoKeyValueStorage implements IKeyValueStorage {
45
49
}
46
50
47
51
class WorkbenchAssignmentServiceTelemetry implements IExperimentationTelemetry {
48
- private _lastAssignmentContext : string | undefined ;
49
- constructor (
50
- private telemetryService : ITelemetryService ,
51
- private productService : IProductService
52
- ) { }
53
52
53
+ private _lastAssignmentContext : string | undefined ;
54
54
get assignmentContext ( ) : string [ ] | undefined {
55
55
return this . _lastAssignmentContext ?. split ( ';' ) ;
56
56
}
57
57
58
+ constructor (
59
+ private readonly telemetryService : ITelemetryService ,
60
+ private readonly productService : IProductService
61
+ ) { }
62
+
58
63
// __GDPR__COMMON__ "abexp.assignmentcontext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
59
64
setSharedProperty ( name : string , value : string ) : void {
60
65
if ( name === this . productService . tasConfig ?. assignmentContextTelemetryPropertyName ) {
@@ -81,34 +86,49 @@ class WorkbenchAssignmentServiceTelemetry implements IExperimentationTelemetry {
81
86
}
82
87
}
83
88
84
- export class WorkbenchAssignmentService extends BaseAssignmentService {
89
+ export class WorkbenchAssignmentService implements IAssignmentService {
90
+
91
+ declare readonly _serviceBrand : undefined ;
92
+
93
+ private readonly tasClient : Promise < TASClient > | undefined ;
94
+
95
+ private networkInitialized = false ;
96
+ private readonly overrideInitDelay : Promise < void > ;
97
+
98
+ private readonly telemetry : WorkbenchAssignmentServiceTelemetry ;
99
+ private readonly keyValueStorage : IKeyValueStorage ;
100
+
101
+ private readonly experimentsEnabled : boolean ;
85
102
86
103
constructor (
87
- @ITelemetryService private telemetryService : ITelemetryService ,
104
+ @ITelemetryService private readonly telemetryService : ITelemetryService ,
88
105
@IStorageService storageService : IStorageService ,
89
- @IConfigurationService configurationService : IConfigurationService ,
90
- @IProductService productService : IProductService ,
106
+ @IConfigurationService private readonly configurationService : IConfigurationService ,
107
+ @IProductService private readonly productService : IProductService ,
91
108
@IWorkbenchEnvironmentService environmentService : IWorkbenchEnvironmentService
92
109
) {
93
- const experimentsEnabled = getTelemetryLevel ( configurationService ) === TelemetryLevel . USAGE &&
110
+ this . experimentsEnabled = getTelemetryLevel ( configurationService ) === TelemetryLevel . USAGE &&
94
111
! environmentService . disableExperiments &&
95
112
! environmentService . extensionTestsLocationURI &&
96
113
! environmentService . enableSmokeTestDriver &&
97
114
configurationService . getValue ( 'workbench.enableExperiments' ) === true ;
98
115
99
- super (
100
- telemetryService . machineId ,
101
- configurationService ,
102
- productService ,
103
- environmentService ,
104
- new WorkbenchAssignmentServiceTelemetry ( telemetryService , productService ) ,
105
- experimentsEnabled ,
106
- new MementoKeyValueStorage ( new Memento ( 'experiment.service.memento' , storageService ) )
107
- ) ;
116
+ if ( productService . tasConfig && this . experimentsEnabled ) {
117
+ this . tasClient = this . setupTASClient ( ) ;
118
+ }
119
+
120
+ this . telemetry = new WorkbenchAssignmentServiceTelemetry ( telemetryService , productService ) ;
121
+ this . keyValueStorage = new MementoKeyValueStorage ( new Memento ( 'experiment.service.memento' , storageService ) ) ;
122
+
123
+ // For development purposes, configure the delay until tas local tas treatment ovverrides are available
124
+ const overrideDelaySetting = configurationService . getValue ( 'experiments.overrideDelay' ) ;
125
+ const overrideDelay = typeof overrideDelaySetting === 'number' ? overrideDelaySetting : 0 ;
126
+ this . overrideInitDelay = timeout ( overrideDelay ) ;
108
127
}
109
128
110
- override async getTreatment < T extends string | number | boolean > ( name : string ) : Promise < T | undefined > {
111
- const result = await super . getTreatment < T > ( name ) ;
129
+ async getTreatment < T extends string | number | boolean > ( name : string ) : Promise < T | undefined > {
130
+ const result = await this . doGetTreatment < T > ( name ) ;
131
+
112
132
type TASClientReadTreatmentData = {
113
133
treatmentName : string ;
114
134
treatmentValue : string ;
@@ -121,12 +141,77 @@ export class WorkbenchAssignmentService extends BaseAssignmentService {
121
141
treatmentName : { classification : 'SystemMetaData' ; purpose : 'PerformanceAndHealth' ; comment : 'The name of the treatment that was read' } ;
122
142
} ;
123
143
124
- this . telemetryService . publicLog2 < TASClientReadTreatmentData , TASClientReadTreatmentClassification > ( 'tasClientReadTreatmentComplete' ,
125
- { treatmentName : name , treatmentValue : JSON . stringify ( result ) } ) ;
144
+ this . telemetryService . publicLog2 < TASClientReadTreatmentData , TASClientReadTreatmentClassification > ( 'tasClientReadTreatmentComplete' , {
145
+ treatmentName : name ,
146
+ treatmentValue : JSON . stringify ( result )
147
+ } ) ;
148
+
149
+ return result ;
150
+ }
151
+
152
+ private async doGetTreatment < T extends string | number | boolean > ( name : string ) : Promise < T | undefined > {
153
+ await this . overrideInitDelay ; // For development purposes, allow overriding tas assignments to test variants locally.
126
154
155
+ const override = this . configurationService . getValue < T > ( `experiments.override.${ name } ` ) ;
156
+ if ( override !== undefined ) {
157
+ return override ;
158
+ }
159
+
160
+ if ( ! this . tasClient ) {
161
+ return undefined ;
162
+ }
163
+
164
+ if ( ! this . experimentsEnabled ) {
165
+ return undefined ;
166
+ }
167
+
168
+ let result : T | undefined ;
169
+ const client = await this . tasClient ;
170
+
171
+ // The TAS client is initialized but we need to check if the initial fetch has completed yet
172
+ // If it is complete, return a cached value for the treatment
173
+ // If not, use the async call with `checkCache: true`. This will allow the module to return a cached value if it is present.
174
+ // Otherwise it will await the initial fetch to return the most up to date value.
175
+ if ( this . networkInitialized ) {
176
+ result = client . getTreatmentVariable < T > ( 'vscode' , name ) ;
177
+ } else {
178
+ result = await client . getTreatmentVariableAsync < T > ( 'vscode' , name , true ) ;
179
+ }
180
+
181
+ result = client . getTreatmentVariable < T > ( 'vscode' , name ) ;
127
182
return result ;
128
183
}
129
184
185
+ private async setupTASClient ( ) : Promise < TASClient > {
186
+ const targetPopulation = this . productService . quality === 'stable' ?
187
+ TargetPopulation . Public : ( this . productService . quality === 'exploration' ?
188
+ TargetPopulation . Exploration : TargetPopulation . Insiders ) ;
189
+
190
+ const filterProvider = new AssignmentFilterProvider (
191
+ this . productService . version ,
192
+ this . productService . nameLong ,
193
+ this . telemetryService . machineId ,
194
+ targetPopulation
195
+ ) ;
196
+
197
+ const tasConfig = this . productService . tasConfig ! ;
198
+ const tasClient = new ( await importAMDNodeModule < typeof import ( 'tas-client-umd' ) > ( 'tas-client-umd' , 'lib/tas-client-umd.js' ) ) . ExperimentationService ( {
199
+ filterProviders : [ filterProvider ] ,
200
+ telemetry : this . telemetry ,
201
+ storageKey : ASSIGNMENT_STORAGE_KEY ,
202
+ keyValueStorage : this . keyValueStorage ,
203
+ assignmentContextTelemetryPropertyName : tasConfig . assignmentContextTelemetryPropertyName ,
204
+ telemetryEventName : tasConfig . telemetryEventName ,
205
+ endpoint : tasConfig . endpoint ,
206
+ refetchInterval : ASSIGNMENT_REFETCH_INTERVAL ,
207
+ } ) ;
208
+
209
+ await tasClient . initializePromise ;
210
+ tasClient . initialFetch . then ( ( ) => this . networkInitialized = true ) ;
211
+
212
+ return tasClient ;
213
+ }
214
+
130
215
async getCurrentExperiments ( ) : Promise < string [ ] | undefined > {
131
216
if ( ! this . tasClient ) {
132
217
return undefined ;
@@ -138,11 +223,12 @@ export class WorkbenchAssignmentService extends BaseAssignmentService {
138
223
139
224
await this . tasClient ;
140
225
141
- return ( this . telemetry as WorkbenchAssignmentServiceTelemetry ) ? .assignmentContext ;
226
+ return this . telemetry . assignmentContext ;
142
227
}
143
228
}
144
229
145
230
registerSingleton ( IWorkbenchAssignmentService , WorkbenchAssignmentService , InstantiationType . Delayed ) ;
231
+
146
232
const registry = Registry . as < IConfigurationRegistry > ( ConfigurationExtensions . Configuration ) ;
147
233
registry . registerConfiguration ( {
148
234
...workbenchConfigurationNodeBase ,
0 commit comments