Skip to content

Commit 3aaf810

Browse files
committed
experiments: allow multiple activation events
1 parent 1f32001 commit 3aaf810

File tree

2 files changed

+83
-43
lines changed

2 files changed

+83
-43
lines changed

src/vs/workbench/contrib/experiments/common/experimentService.ts

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,27 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
6+
import { distinct } from 'vs/base/common/arrays';
7+
import { RunOnceWorker } from 'vs/base/common/async';
8+
import { CancellationToken } from 'vs/base/common/cancellation';
79
import { Emitter, Event } from 'vs/base/common/event';
8-
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
9-
import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
10-
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
10+
import { match } from 'vs/base/common/glob';
11+
import { Disposable } from 'vs/base/common/lifecycle';
12+
import { equals } from 'vs/base/common/objects';
13+
import { language, OperatingSystem, OS } from 'vs/base/common/platform';
14+
import { isDefined } from 'vs/base/common/types';
1115
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1216
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
13-
import { language, OperatingSystem, OS } from 'vs/base/common/platform';
14-
import { Disposable } from 'vs/base/common/lifecycle';
15-
import { match } from 'vs/base/common/glob';
16-
import { IRequestService, asJson } from 'vs/platform/request/common/request';
17-
import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
18-
import { CancellationToken } from 'vs/base/common/cancellation';
19-
import { distinct } from 'vs/base/common/arrays';
2017
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
18+
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
2119
import { IProductService } from 'vs/platform/product/common/productService';
20+
import { asJson, IRequestService } from 'vs/platform/request/common/request';
21+
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
22+
import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
2223
import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags';
23-
import { RunOnceWorker } from 'vs/base/common/async';
2424
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
25-
import { equals } from 'vs/base/common/objects';
25+
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
26+
import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
2627

2728
export const enum ExperimentState {
2829
Evaluating,
@@ -93,7 +94,7 @@ interface IExperimentStorageState {
9394
* be incremented when adding a condition, otherwise experiments might activate
9495
* on older versions of VS Code where not intended.
9596
*/
96-
export const currentSchemaVersion = 4;
97+
export const currentSchemaVersion = 5;
9798

9899
interface IRawExperiment {
99100
id: string;
@@ -107,7 +108,7 @@ interface IRawExperiment {
107108
userSetting?: { [key: string]: unknown; };
108109
// Start the experiment if the number of activation events have happened over the last week:
109110
activationEvent?: {
110-
event: string;
111+
event: string | string[];
111112
uniqueDays?: number;
112113
minEvents: number;
113114
};
@@ -285,7 +286,8 @@ export class ExperimentService extends Disposable implements IExperimentService
285286
this.storageService.remove('allExperiments', StorageScope.GLOBAL);
286287
}
287288

288-
const activationEvents = new Set(rawExperiments.map(exp => exp.condition?.activationEvent?.event).filter(evt => !!evt));
289+
const activationEvents = new Set(rawExperiments.map(exp => exp.condition?.activationEvent?.event)
290+
.filter(isDefined).flatMap(evt => typeof evt === 'string' ? [evt] : []));
289291
if (activationEvents.size) {
290292
this._register(this.extensionService.onWillActivateByEvent(evt => {
291293
if (activationEvents.has(evt.event)) {
@@ -402,7 +404,14 @@ export class ExperimentService extends Disposable implements IExperimentService
402404
this.storageService.store(key, JSON.stringify(record), StorageScope.GLOBAL, StorageTarget.MACHINE);
403405

404406
this._experiments
405-
.filter(e => e.state === ExperimentState.Evaluating && e.raw?.condition?.activationEvent?.event === event)
407+
.filter(e => {
408+
const lookingFor = e.raw?.condition?.activationEvent?.event;
409+
if (e.state !== ExperimentState.Evaluating || !lookingFor) {
410+
return false;
411+
}
412+
413+
return typeof lookingFor === 'string' ? lookingFor === event : lookingFor?.includes(event);
414+
})
406415
.forEach(e => this.evaluateExperiment(e.raw!));
407416
}
408417

@@ -412,14 +421,18 @@ export class ExperimentService extends Disposable implements IExperimentService
412421
return true;
413422
}
414423

415-
const { count } = getCurrentActivationRecord(safeParse(this.storageService.get(experimentEventStorageKey(setting.event), StorageScope.GLOBAL), undefined));
416-
417424
let total = 0;
418425
let uniqueDays = 0;
419-
for (const entry of count) {
420-
if (entry > 0) {
421-
uniqueDays++;
422-
total += entry;
426+
427+
const events = typeof setting.event === 'string' ? [setting.event] : setting.event;
428+
for (const event of events) {
429+
const { count } = getCurrentActivationRecord(safeParse(this.storageService.get(experimentEventStorageKey(event), StorageScope.GLOBAL), undefined));
430+
431+
for (const entry of count) {
432+
if (entry > 0) {
433+
uniqueDays++;
434+
total += entry;
435+
}
423436
}
424437
}
425438

src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,32 @@
55

66
import * as assert from 'assert';
77
import * as sinon from 'sinon';
8-
import { ExperimentActionType, ExperimentState, IExperiment, ExperimentService, getCurrentActivationRecord, currentSchemaVersion } from 'vs/workbench/contrib/experiments/common/experimentService';
9-
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
10-
import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
11-
import {
12-
IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, ILocalExtension, InstallExtensionResult
13-
} from 'vs/platform/extensionManagement/common/extensionManagement';
14-
import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
15-
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
8+
import { timeout } from 'vs/base/common/async';
169
import { Emitter } from 'vs/base/common/event';
17-
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
18-
import { NativeURLService } from 'vs/platform/url/common/urlService';
19-
import { IURLService } from 'vs/platform/url/common/url';
20-
import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
21-
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
10+
import { OS } from 'vs/base/common/platform';
11+
import { URI } from 'vs/base/common/uri';
2212
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2313
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
24-
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
25-
import { URI } from 'vs/base/common/uri';
26-
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
14+
import { DidUninstallExtensionEvent, IExtensionIdentifier, IExtensionManagementService, ILocalExtension, InstallExtensionEvent, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement';
2715
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
16+
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
2817
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
18+
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
2919
import { IProductService } from 'vs/platform/product/common/productService';
30-
import { IWillActivateEvent, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
31-
import { timeout } from 'vs/base/common/async';
32-
import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices';
33-
import { OS } from 'vs/base/common/platform';
20+
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
21+
import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
22+
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
23+
import { IURLService } from 'vs/platform/url/common/url';
24+
import { NativeURLService } from 'vs/platform/url/common/urlService';
3425
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
26+
import { currentSchemaVersion, ExperimentActionType, ExperimentService, ExperimentState, getCurrentActivationRecord, IExperiment } from 'vs/workbench/contrib/experiments/common/experimentService';
27+
import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
28+
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
29+
import { IExtensionService, IWillActivateEvent } from 'vs/workbench/services/extensions/common/extensions';
30+
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
3531
import { TestWorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService';
32+
import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
33+
import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices';
3634

3735
interface ExperimentSettings {
3836
enabled?: boolean;
@@ -407,6 +405,35 @@ suite('Experiment Service', () => {
407405
});
408406
});
409407

408+
test('Activation event allows multiple', () => {
409+
experimentData = {
410+
experiments: [
411+
{
412+
id: 'experiment1',
413+
enabled: true,
414+
condition: {
415+
activationEvent: {
416+
event: ['other:event', 'my:event'],
417+
minEvents: 5,
418+
}
419+
}
420+
}
421+
]
422+
};
423+
424+
instantiationService.stub(IStorageService, 'get', (a: string, b: StorageScope, c?: string) => {
425+
return a === 'experimentEventRecord-my-event'
426+
? JSON.stringify({ count: [10], mostRecentBucket: Date.now() })
427+
: undefined;
428+
});
429+
430+
testObject = instantiationService.createInstance(TestExperimentService);
431+
return testObject.getExperimentById('experiment1').then(result => {
432+
assert.strictEqual(result.enabled, true);
433+
assert.strictEqual(result.state, ExperimentState.Run);
434+
});
435+
});
436+
410437
test('Activation event does not work with old data', () => {
411438
experimentData = {
412439
experiments: [

0 commit comments

Comments
 (0)