Skip to content

Commit c6e6e69

Browse files
authored
debt - lift assignment service into its only use (microsoft#254796)
1 parent a40c65e commit c6e6e69

File tree

2 files changed

+116
-134
lines changed

2 files changed

+116
-134
lines changed

src/vs/platform/assignment/common/assignmentService.ts

Lines changed: 0 additions & 104 deletions
This file was deleted.

src/vs/workbench/services/assignment/common/assignmentService.ts

Lines changed: 116 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,40 @@
55

66
import { localize } from '../../../../nls.js';
77
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';
99
import { MementoObject, Memento } from '../../../common/memento.js';
1010
import { ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js';
1111
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
1212
import { ITelemetryData } from '../../../../base/common/actions.js';
1313
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
1414
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
1515
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';
1717
import { Registry } from '../../../../platform/registry/common/platform.js';
18-
import { BaseAssignmentService } from '../../../../platform/assignment/common/assignmentService.js';
1918
import { workbenchConfigurationNodeBase } from '../../../common/configuration.js';
2019
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from '../../../../platform/configuration/common/configurationRegistry.js';
2120
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
2221
import { getTelemetryLevel } from '../../../../platform/telemetry/common/telemetryUtils.js';
22+
import { importAMDNodeModule } from '../../../../amdX.js';
23+
import { timeout } from '../../../../base/common/async.js';
2324

24-
export const IWorkbenchAssignmentService = createDecorator<IWorkbenchAssignmentService>('WorkbenchAssignmentService');
25+
export const IWorkbenchAssignmentService = createDecorator<IWorkbenchAssignmentService>('assignmentService');
2526

2627
export interface IWorkbenchAssignmentService extends IAssignmentService {
2728
getCurrentExperiments(): Promise<string[] | undefined>;
2829
}
2930

3031
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) {
3336
this.mementoObj = memento.getMemento(StorageScope.APPLICATION, StorageTarget.MACHINE);
3437
}
3538

3639
async getValue<T>(key: string, defaultValue?: T | undefined): Promise<T | undefined> {
3740
const value = await this.mementoObj[key];
41+
3842
return value || defaultValue;
3943
}
4044

@@ -45,16 +49,17 @@ class MementoKeyValueStorage implements IKeyValueStorage {
4549
}
4650

4751
class WorkbenchAssignmentServiceTelemetry implements IExperimentationTelemetry {
48-
private _lastAssignmentContext: string | undefined;
49-
constructor(
50-
private telemetryService: ITelemetryService,
51-
private productService: IProductService
52-
) { }
5352

53+
private _lastAssignmentContext: string | undefined;
5454
get assignmentContext(): string[] | undefined {
5555
return this._lastAssignmentContext?.split(';');
5656
}
5757

58+
constructor(
59+
private readonly telemetryService: ITelemetryService,
60+
private readonly productService: IProductService
61+
) { }
62+
5863
// __GDPR__COMMON__ "abexp.assignmentcontext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
5964
setSharedProperty(name: string, value: string): void {
6065
if (name === this.productService.tasConfig?.assignmentContextTelemetryPropertyName) {
@@ -81,34 +86,49 @@ class WorkbenchAssignmentServiceTelemetry implements IExperimentationTelemetry {
8186
}
8287
}
8388

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;
85102

86103
constructor(
87-
@ITelemetryService private telemetryService: ITelemetryService,
104+
@ITelemetryService private readonly telemetryService: ITelemetryService,
88105
@IStorageService storageService: IStorageService,
89-
@IConfigurationService configurationService: IConfigurationService,
90-
@IProductService productService: IProductService,
106+
@IConfigurationService private readonly configurationService: IConfigurationService,
107+
@IProductService private readonly productService: IProductService,
91108
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
92109
) {
93-
const experimentsEnabled = getTelemetryLevel(configurationService) === TelemetryLevel.USAGE &&
110+
this.experimentsEnabled = getTelemetryLevel(configurationService) === TelemetryLevel.USAGE &&
94111
!environmentService.disableExperiments &&
95112
!environmentService.extensionTestsLocationURI &&
96113
!environmentService.enableSmokeTestDriver &&
97114
configurationService.getValue('workbench.enableExperiments') === true;
98115

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);
108127
}
109128

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+
112132
type TASClientReadTreatmentData = {
113133
treatmentName: string;
114134
treatmentValue: string;
@@ -121,12 +141,77 @@ export class WorkbenchAssignmentService extends BaseAssignmentService {
121141
treatmentName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the treatment that was read' };
122142
};
123143

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.
126154

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);
127182
return result;
128183
}
129184

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+
130215
async getCurrentExperiments(): Promise<string[] | undefined> {
131216
if (!this.tasClient) {
132217
return undefined;
@@ -138,11 +223,12 @@ export class WorkbenchAssignmentService extends BaseAssignmentService {
138223

139224
await this.tasClient;
140225

141-
return (this.telemetry as WorkbenchAssignmentServiceTelemetry)?.assignmentContext;
226+
return this.telemetry.assignmentContext;
142227
}
143228
}
144229

145230
registerSingleton(IWorkbenchAssignmentService, WorkbenchAssignmentService, InstantiationType.Delayed);
231+
146232
const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
147233
registry.registerConfiguration({
148234
...workbenchConfigurationNodeBase,

0 commit comments

Comments
 (0)