in src/vs/workbench/contrib/experiments/common/experimentService.ts [443:590]
private shouldRunExperiment(experiment: IRawExperiment, processedExperiment: IExperiment): Promise<ExperimentState> {
if (processedExperiment.state !== ExperimentState.Evaluating) {
return Promise.resolve(processedExperiment.state);
}
if (!experiment.enabled) {
return Promise.resolve(ExperimentState.NoRun);
}
const condition = experiment.condition;
if (!condition) {
return Promise.resolve(ExperimentState.Run);
}
if (experiment.condition?.os && !experiment.condition.os.includes(OS)) {
return Promise.resolve(ExperimentState.NoRun);
}
if (!this.checkExperimentDependencies(experiment)) {
return Promise.resolve(ExperimentState.NoRun);
}
for (const [key, value] of Object.entries(experiment.condition?.userSetting || {})) {
if (!equals(this.configurationService.getValue(key), value)) {
return Promise.resolve(ExperimentState.NoRun);
}
}
if (!this.checkActivationEventFrequency(experiment)) {
return Promise.resolve(ExperimentState.Evaluating);
}
if (this.productService.quality === 'stable' && condition.insidersOnly === true) {
return Promise.resolve(ExperimentState.NoRun);
}
const isNewUser = !this.storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL);
if ((condition.newUser === true && !isNewUser)
|| (condition.newUser === false && isNewUser)) {
return Promise.resolve(ExperimentState.NoRun);
}
if (typeof condition.displayLanguage === 'string') {
let localeToCheck = condition.displayLanguage.toLowerCase();
let displayLanguage = language!.toLowerCase();
if (localeToCheck !== displayLanguage) {
const a = displayLanguage.indexOf('-');
const b = localeToCheck.indexOf('-');
if (a > -1) {
displayLanguage = displayLanguage.substr(0, a);
}
if (b > -1) {
localeToCheck = localeToCheck.substr(0, b);
}
if (displayLanguage !== localeToCheck) {
return Promise.resolve(ExperimentState.NoRun);
}
}
}
if (!condition.userProbability) {
condition.userProbability = 1;
}
let extensionsCheckPromise = Promise.resolve(true);
const installedExtensions = condition.installedExtensions;
if (installedExtensions) {
extensionsCheckPromise = this.extensionManagementService.getInstalled(ExtensionType.User).then(locals => {
let includesCheck = true;
let excludesCheck = true;
const localExtensions = locals.map(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}`);
if (Array.isArray(installedExtensions.includes) && installedExtensions.includes.length) {
const extensionIncludes = installedExtensions.includes.map(e => e.toLowerCase());
includesCheck = localExtensions.some(e => extensionIncludes.indexOf(e) > -1);
}
if (Array.isArray(installedExtensions.excludes) && installedExtensions.excludes.length) {
const extensionExcludes = installedExtensions.excludes.map(e => e.toLowerCase());
excludesCheck = !localExtensions.some(e => extensionExcludes.indexOf(e) > -1);
}
return includesCheck && excludesCheck;
});
}
const storageKey = 'experiments.' + experiment.id;
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
return extensionsCheckPromise.then(success => {
const fileEdits = condition.fileEdits;
if (!success || !fileEdits || typeof fileEdits.minEditCount !== 'number') {
const runExperiment = success && typeof condition.userProbability === 'number' && Math.random() < condition.userProbability;
return runExperiment ? ExperimentState.Run : ExperimentState.NoRun;
}
experimentState.editCount = experimentState.editCount || 0;
if (experimentState.editCount >= fileEdits.minEditCount) {
return ExperimentState.Run;
}
// Process model-save event every 250ms to reduce load
const onModelsSavedWorker = this._register(new RunOnceWorker<ITextFileEditorModel>(models => {
const date = new Date().toDateString();
const latestExperimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
if (latestExperimentState.state !== ExperimentState.Evaluating) {
onSaveHandler.dispose();
onModelsSavedWorker.dispose();
return;
}
models.forEach(async model => {
if (latestExperimentState.state !== ExperimentState.Evaluating
|| date === latestExperimentState.lastEditedDate
|| (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount)
) {
return;
}
let filePathCheck = true;
let workspaceCheck = true;
if (typeof fileEdits.filePathPattern === 'string') {
filePathCheck = match(fileEdits.filePathPattern, model.resource.fsPath);
}
if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) {
const tags = await this.workspaceTagsService.getTags();
workspaceCheck = !!tags && fileEdits.workspaceIncludes.some(x => !!tags[x]);
}
if (workspaceCheck && Array.isArray(fileEdits.workspaceExcludes) && fileEdits.workspaceExcludes.length) {
const tags = await this.workspaceTagsService.getTags();
workspaceCheck = !!tags && !fileEdits.workspaceExcludes.some(x => !!tags[x]);
}
if (filePathCheck && workspaceCheck) {
latestExperimentState.editCount = (latestExperimentState.editCount || 0) + 1;
latestExperimentState.lastEditedDate = date;
this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL, StorageTarget.MACHINE);
}
});
if (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) {
processedExperiment.state = latestExperimentState.state = (typeof condition.userProbability === 'number' && Math.random() < condition.userProbability && this.checkExperimentDependencies(experiment)) ? ExperimentState.Run : ExperimentState.NoRun;
this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL, StorageTarget.MACHINE);
if (latestExperimentState.state === ExperimentState.Run && processedExperiment.action && ExperimentActionType[processedExperiment.action.type] === ExperimentActionType.Prompt) {
this.fireRunExperiment(processedExperiment);
}
}
}, 250));
const onSaveHandler = this._register(this.textFileService.files.onDidSave(e => onModelsSavedWorker.work(e.model)));
return ExperimentState.Evaluating;
});
}