mirror of
https://github.com/Microsoft/vscode
synced 2024-09-19 02:26:04 +00:00
remove workbench/contrib/experiments
This commit is contained in:
parent
b578213118
commit
6af591662a
|
@ -74,10 +74,6 @@
|
||||||
"name": "vs/workbench/contrib/emmet",
|
"name": "vs/workbench/contrib/emmet",
|
||||||
"project": "vscode-workbench"
|
"project": "vscode-workbench"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "vs/workbench/contrib/experiments",
|
|
||||||
"project": "vscode-workbench"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "vs/workbench/contrib/extensions",
|
"name": "vs/workbench/contrib/extensions",
|
||||||
"project": "vscode-workbench"
|
"project": "vscode-workbench"
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification';
|
|
||||||
import { IExperimentService, IExperiment, ExperimentActionType, IExperimentActionPromptProperties, IExperimentActionPromptCommand, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService';
|
|
||||||
import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
|
||||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
|
||||||
import { language } from 'vs/base/common/platform';
|
|
||||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
|
||||||
import { URI } from 'vs/base/common/uri';
|
|
||||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
|
||||||
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
|
|
||||||
import { ViewContainerLocation } from 'vs/workbench/common/views';
|
|
||||||
|
|
||||||
export class ExperimentalPrompts extends Disposable implements IWorkbenchContribution {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@IExperimentService private readonly experimentService: IExperimentService,
|
|
||||||
@IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService,
|
|
||||||
@INotificationService private readonly notificationService: INotificationService,
|
|
||||||
@IOpenerService private readonly openerService: IOpenerService,
|
|
||||||
@ICommandService private readonly commandService: ICommandService
|
|
||||||
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this._register(this.experimentService.onExperimentEnabled(e => {
|
|
||||||
if (e.action && e.action.type === ExperimentActionType.Prompt && e.state === ExperimentState.Run) {
|
|
||||||
this.showExperimentalPrompts(e);
|
|
||||||
}
|
|
||||||
}, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private showExperimentalPrompts(experiment: IExperiment): void {
|
|
||||||
if (!experiment || !experiment.enabled || !experiment.action || experiment.state !== ExperimentState.Run) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionProperties = (<IExperimentActionPromptProperties>experiment.action.properties);
|
|
||||||
const promptText = ExperimentalPrompts.getLocalizedText(actionProperties.promptText, language || '');
|
|
||||||
if (!actionProperties || !promptText) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!actionProperties.commands) {
|
|
||||||
actionProperties.commands = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const choices: IPromptChoice[] = actionProperties.commands.map((command: IExperimentActionPromptCommand) => {
|
|
||||||
const commandText = ExperimentalPrompts.getLocalizedText(command.text, language || '');
|
|
||||||
return {
|
|
||||||
label: commandText,
|
|
||||||
run: () => {
|
|
||||||
if (command.externalLink) {
|
|
||||||
this.openerService.open(URI.parse(command.externalLink));
|
|
||||||
} else if (command.curatedExtensionsKey && Array.isArray(command.curatedExtensionsList)) {
|
|
||||||
this.paneCompositeService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar, true)
|
|
||||||
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
|
|
||||||
.then(viewlet => {
|
|
||||||
viewlet?.search('curated:' + command.curatedExtensionsKey);
|
|
||||||
});
|
|
||||||
} else if (command.codeCommand) {
|
|
||||||
this.commandService.executeCommand(command.codeCommand.id, ...command.codeCommand.arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.experimentService.markAsCompleted(experiment.id);
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.notificationService.prompt(Severity.Info, promptText, choices, {
|
|
||||||
onCancel: () => {
|
|
||||||
this.experimentService.markAsCompleted(experiment.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static getLocalizedText(text: string | { [key: string]: string }, displayLanguage: string): string {
|
|
||||||
if (typeof text === 'string') {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
const msgInEnglish = text['en'] || text['en-us'];
|
|
||||||
displayLanguage = displayLanguage.toLowerCase();
|
|
||||||
if (!text[displayLanguage] && displayLanguage.indexOf('-') === 2) {
|
|
||||||
displayLanguage = displayLanguage.substr(0, 2);
|
|
||||||
}
|
|
||||||
return text[displayLanguage] || msgInEnglish;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { localize } from 'vs/nls';
|
|
||||||
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
|
||||||
import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
|
||||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
|
||||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
|
||||||
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt';
|
|
||||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
|
||||||
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
|
|
||||||
|
|
||||||
registerSingleton(IExperimentService, ExperimentService, InstantiationType.Delayed);
|
|
||||||
|
|
||||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExperimentalPrompts, LifecyclePhase.Eventually);
|
|
||||||
|
|
||||||
const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
|
||||||
|
|
||||||
// Configuration
|
|
||||||
registry.registerConfiguration({
|
|
||||||
...workbenchConfigurationNodeBase,
|
|
||||||
'properties': {
|
|
||||||
'workbench.enableExperiments': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'description': localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."),
|
|
||||||
'default': true,
|
|
||||||
'scope': ConfigurationScope.APPLICATION,
|
|
||||||
'restricted': true,
|
|
||||||
'tags': ['usesOnlineServices']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,593 +0,0 @@
|
||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { distinct } from 'vs/base/common/arrays';
|
|
||||||
import { RunOnceWorker } from 'vs/base/common/async';
|
|
||||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
|
||||||
import { Emitter, Event } from 'vs/base/common/event';
|
|
||||||
import { match } from 'vs/base/common/glob';
|
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
|
||||||
import { equals } from 'vs/base/common/objects';
|
|
||||||
import { language, OperatingSystem, OS } from 'vs/base/common/platform';
|
|
||||||
import { isDefined } from 'vs/base/common/types';
|
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
|
||||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
|
||||||
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
|
|
||||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
|
||||||
import { IProductService } from 'vs/platform/product/common/productService';
|
|
||||||
import { asJson, IRequestService } from 'vs/platform/request/common/request';
|
|
||||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
|
||||||
import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
|
|
||||||
import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags';
|
|
||||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
|
||||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
|
||||||
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
|
||||||
import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
|
||||||
|
|
||||||
export const enum ExperimentState {
|
|
||||||
Evaluating,
|
|
||||||
NoRun,
|
|
||||||
Run,
|
|
||||||
Complete
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IExperimentAction {
|
|
||||||
type: ExperimentActionType;
|
|
||||||
properties: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ExperimentActionType {
|
|
||||||
Custom = 'Custom',
|
|
||||||
Prompt = 'Prompt',
|
|
||||||
AddToRecommendations = 'AddToRecommendations',
|
|
||||||
ExtensionSearchResults = 'ExtensionSearchResults'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LocalizedPromptText = { [locale: string]: string };
|
|
||||||
|
|
||||||
export interface IExperimentActionPromptProperties {
|
|
||||||
promptText: string | LocalizedPromptText;
|
|
||||||
commands: IExperimentActionPromptCommand[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IExperimentActionPromptCommand {
|
|
||||||
text: string | { [key: string]: string };
|
|
||||||
externalLink?: string;
|
|
||||||
curatedExtensionsKey?: string;
|
|
||||||
curatedExtensionsList?: string[];
|
|
||||||
codeCommand?: {
|
|
||||||
id: string;
|
|
||||||
arguments: unknown[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IExperiment {
|
|
||||||
id: string;
|
|
||||||
enabled: boolean;
|
|
||||||
raw: IRawExperiment | undefined;
|
|
||||||
state: ExperimentState;
|
|
||||||
action?: IExperimentAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IExperimentService {
|
|
||||||
readonly _serviceBrand: undefined;
|
|
||||||
getExperimentById(id: string): Promise<IExperiment>;
|
|
||||||
getExperimentsByType(type: ExperimentActionType): Promise<IExperiment[]>;
|
|
||||||
markAsCompleted(experimentId: string): void;
|
|
||||||
|
|
||||||
onExperimentEnabled: Event<IExperiment>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const IExperimentService = createDecorator<IExperimentService>('experimentService');
|
|
||||||
|
|
||||||
interface IExperimentStorageState {
|
|
||||||
enabled: boolean;
|
|
||||||
state: ExperimentState;
|
|
||||||
editCount?: number;
|
|
||||||
lastEditedDate?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current version of the experiment schema in this VS Code build. This *must*
|
|
||||||
* be incremented when adding a condition, otherwise experiments might activate
|
|
||||||
* on older versions of VS Code where not intended.
|
|
||||||
*/
|
|
||||||
export const currentSchemaVersion = 5;
|
|
||||||
|
|
||||||
interface IRawExperiment {
|
|
||||||
id: string;
|
|
||||||
schemaVersion: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
condition?: {
|
|
||||||
insidersOnly?: boolean;
|
|
||||||
newUser?: boolean;
|
|
||||||
displayLanguage?: string;
|
|
||||||
// Evaluates to true iff all the given user settings are deeply equal
|
|
||||||
userSetting?: { [key: string]: unknown };
|
|
||||||
// Start the experiment if the number of activation events have happened over the last week:
|
|
||||||
activationEvent?: {
|
|
||||||
event: string | string[];
|
|
||||||
uniqueDays?: number;
|
|
||||||
minEvents: number;
|
|
||||||
};
|
|
||||||
os: OperatingSystem[];
|
|
||||||
installedExtensions?: {
|
|
||||||
excludes?: string[];
|
|
||||||
includes?: string[];
|
|
||||||
};
|
|
||||||
fileEdits?: {
|
|
||||||
filePathPattern?: string;
|
|
||||||
workspaceIncludes?: string[];
|
|
||||||
workspaceExcludes?: string[];
|
|
||||||
minEditCount: number;
|
|
||||||
};
|
|
||||||
experimentsPreviouslyRun?: {
|
|
||||||
excludes?: string[];
|
|
||||||
includes?: string[];
|
|
||||||
};
|
|
||||||
userProbability?: number;
|
|
||||||
};
|
|
||||||
action?: IExperimentAction;
|
|
||||||
action2?: IExperimentAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IActivationEventRecord {
|
|
||||||
count: number[];
|
|
||||||
mostRecentBucket: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const experimentEventStorageKey = (event: string) => 'experimentEventRecord-' + event.replace(/[^0-9a-z]/ig, '-');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the activation record to shift off days outside the window
|
|
||||||
* we're interested in.
|
|
||||||
*/
|
|
||||||
export const getCurrentActivationRecord = (previous?: IActivationEventRecord, dayWindow = 7): IActivationEventRecord => {
|
|
||||||
const oneDay = 1000 * 60 * 60 * 24;
|
|
||||||
const now = Date.now();
|
|
||||||
if (!previous) {
|
|
||||||
return { count: new Array(dayWindow).fill(0), mostRecentBucket: now };
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the number of days, up to dayWindow, that passed since the last bucket update
|
|
||||||
const shift = Math.min(dayWindow, Math.floor((now - previous.mostRecentBucket) / oneDay));
|
|
||||||
if (!shift) {
|
|
||||||
return previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
count: new Array(shift).fill(0).concat(previous.count.slice(0, -shift)),
|
|
||||||
mostRecentBucket: previous.mostRecentBucket + shift * oneDay,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ExperimentService extends Disposable implements IExperimentService {
|
|
||||||
declare readonly _serviceBrand: undefined;
|
|
||||||
private _experiments: IExperiment[] = [];
|
|
||||||
private _loadExperimentsPromise: Promise<void>;
|
|
||||||
private _curatedMapping = Object.create(null);
|
|
||||||
|
|
||||||
private readonly _onExperimentEnabled = this._register(new Emitter<IExperiment>());
|
|
||||||
onExperimentEnabled: Event<IExperiment> = this._onExperimentEnabled.event;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@IStorageService private readonly storageService: IStorageService,
|
|
||||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
|
||||||
@ITextFileService private readonly textFileService: ITextFileService,
|
|
||||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
|
||||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
|
||||||
@IRequestService private readonly requestService: IRequestService,
|
|
||||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
|
||||||
@IProductService private readonly productService: IProductService,
|
|
||||||
@IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService,
|
|
||||||
@IExtensionService private readonly extensionService: IExtensionService,
|
|
||||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this._loadExperimentsPromise = Promise.resolve(this.lifecycleService.when(LifecyclePhase.Eventually)).then(() =>
|
|
||||||
this.loadExperiments());
|
|
||||||
}
|
|
||||||
|
|
||||||
public getExperimentById(id: string): Promise<IExperiment> {
|
|
||||||
return this._loadExperimentsPromise.then(() => {
|
|
||||||
return this._experiments.filter(x => x.id === id)[0];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getExperimentsByType(type: ExperimentActionType): Promise<IExperiment[]> {
|
|
||||||
return this._loadExperimentsPromise.then(() => {
|
|
||||||
if (type === ExperimentActionType.Custom) {
|
|
||||||
return this._experiments.filter(x => x.enabled && (!x.action || x.action.type === type));
|
|
||||||
}
|
|
||||||
return this._experiments.filter(x => x.enabled && x.action && x.action.type === type);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public markAsCompleted(experimentId: string): void {
|
|
||||||
const storageKey = 'experiments.' + experimentId;
|
|
||||||
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.APPLICATION), {});
|
|
||||||
experimentState.state = ExperimentState.Complete;
|
|
||||||
this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.APPLICATION, StorageTarget.MACHINE);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getExperiments(): Promise<IRawExperiment[] | null> {
|
|
||||||
if (this.environmentService.enableSmokeTestDriver || this.environmentService.extensionTestsLocationURI) {
|
|
||||||
return []; // TODO@sbatten add CLI argument (https://github.com/microsoft/vscode-internalbacklog/issues/2855)
|
|
||||||
}
|
|
||||||
|
|
||||||
const experimentsUrl = this.productService.experimentsUrl;
|
|
||||||
if (!experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const context = await this.requestService.request({ type: 'GET', url: experimentsUrl }, CancellationToken.None);
|
|
||||||
if (context.res.statusCode !== 200) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const result = await asJson<{ experiments?: IRawExperiment }>(context);
|
|
||||||
return result && Array.isArray(result.experiments) ? result.experiments : [];
|
|
||||||
} catch (_e) {
|
|
||||||
// Bad request or invalid JSON
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadExperiments(): Promise<any> {
|
|
||||||
return this.getExperiments().then(rawExperiments => {
|
|
||||||
// Offline mode
|
|
||||||
if (!rawExperiments) {
|
|
||||||
const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.APPLICATION), []);
|
|
||||||
if (Array.isArray(allExperimentIdsFromStorage)) {
|
|
||||||
allExperimentIdsFromStorage.forEach(experimentId => {
|
|
||||||
const storageKey = 'experiments.' + experimentId;
|
|
||||||
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.APPLICATION), null);
|
|
||||||
if (experimentState) {
|
|
||||||
this._experiments.push({
|
|
||||||
id: experimentId,
|
|
||||||
raw: undefined,
|
|
||||||
enabled: experimentState.enabled,
|
|
||||||
state: experimentState.state
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't look at experiments with newer schema versions. We can't
|
|
||||||
// understand them, trying to process them might even cause errors.
|
|
||||||
rawExperiments = rawExperiments.filter(e => (e.schemaVersion || 0) <= currentSchemaVersion);
|
|
||||||
|
|
||||||
// Clear disbaled/deleted experiments from storage
|
|
||||||
const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.APPLICATION), []);
|
|
||||||
const enabledExperiments = rawExperiments.filter(experiment => !!experiment.enabled).map(experiment => experiment.id.toLowerCase());
|
|
||||||
if (Array.isArray(allExperimentIdsFromStorage)) {
|
|
||||||
allExperimentIdsFromStorage.forEach(experiment => {
|
|
||||||
if (enabledExperiments.indexOf(experiment) === -1) {
|
|
||||||
this.storageService.remove(`experiments.${experiment}`, StorageScope.APPLICATION);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (enabledExperiments.length) {
|
|
||||||
this.storageService.store('allExperiments', JSON.stringify(enabledExperiments), StorageScope.APPLICATION, StorageTarget.MACHINE);
|
|
||||||
} else {
|
|
||||||
this.storageService.remove('allExperiments', StorageScope.APPLICATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
const activationEvents = new Set(rawExperiments.map(exp => exp.condition?.activationEvent?.event)
|
|
||||||
.filter(isDefined).flatMap(evt => typeof evt === 'string' ? [evt] : []));
|
|
||||||
if (activationEvents.size) {
|
|
||||||
this._register(this.extensionService.onWillActivateByEvent(evt => {
|
|
||||||
if (activationEvents.has(evt.event)) {
|
|
||||||
this.recordActivatedEvent(evt.event);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const promises = rawExperiments.map(experiment => this.evaluateExperiment(experiment));
|
|
||||||
return Promise.all(promises).then(() => {
|
|
||||||
type ExperimentsClassification = {
|
|
||||||
owner: 'sbatten';
|
|
||||||
comment: 'Information about the experiments in this session';
|
|
||||||
experiments: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The list of experiments in this session' };
|
|
||||||
};
|
|
||||||
this.telemetryService.publicLog2<{ experiments: string[] }, ExperimentsClassification>('experiments', { experiments: this._experiments.map(e => e.id) });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private evaluateExperiment(experiment: IRawExperiment) {
|
|
||||||
const processedExperiment: IExperiment = {
|
|
||||||
id: experiment.id,
|
|
||||||
raw: experiment,
|
|
||||||
enabled: !!experiment.enabled,
|
|
||||||
state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun
|
|
||||||
};
|
|
||||||
|
|
||||||
const action = experiment.action2 || experiment.action;
|
|
||||||
if (action) {
|
|
||||||
processedExperiment.action = {
|
|
||||||
type: ExperimentActionType[action.type] || ExperimentActionType.Custom,
|
|
||||||
properties: action.properties
|
|
||||||
};
|
|
||||||
if (processedExperiment.action.type === ExperimentActionType.Prompt) {
|
|
||||||
((<IExperimentActionPromptProperties>processedExperiment.action.properties).commands || []).forEach(x => {
|
|
||||||
if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) {
|
|
||||||
this._curatedMapping[experiment.id] = x;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!processedExperiment.action.properties) {
|
|
||||||
processedExperiment.action.properties = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._experiments = this._experiments.filter(e => e.id !== processedExperiment.id);
|
|
||||||
this._experiments.push(processedExperiment);
|
|
||||||
|
|
||||||
if (!processedExperiment.enabled) {
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const storageKey = 'experiments.' + experiment.id;
|
|
||||||
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.APPLICATION), {});
|
|
||||||
if (!experimentState.hasOwnProperty('enabled')) {
|
|
||||||
experimentState.enabled = processedExperiment.enabled;
|
|
||||||
}
|
|
||||||
if (!experimentState.hasOwnProperty('state')) {
|
|
||||||
experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun;
|
|
||||||
} else {
|
|
||||||
processedExperiment.state = experimentState.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => {
|
|
||||||
experimentState.state = processedExperiment.state = state;
|
|
||||||
this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.APPLICATION, StorageTarget.MACHINE);
|
|
||||||
|
|
||||||
if (state === ExperimentState.Run) {
|
|
||||||
this.fireRunExperiment(processedExperiment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private fireRunExperiment(experiment: IExperiment) {
|
|
||||||
this._onExperimentEnabled.fire(experiment);
|
|
||||||
const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.APPLICATION), []);
|
|
||||||
if (runExperimentIdsFromStorage.indexOf(experiment.id) === -1) {
|
|
||||||
runExperimentIdsFromStorage.push(experiment.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we dont store duplicates
|
|
||||||
const distinctExperiments = distinct(runExperimentIdsFromStorage);
|
|
||||||
if (runExperimentIdsFromStorage.length !== distinctExperiments.length) {
|
|
||||||
this.storageService.store('currentOrPreviouslyRunExperiments', JSON.stringify(distinctExperiments), StorageScope.APPLICATION, StorageTarget.MACHINE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkExperimentDependencies(experiment: IRawExperiment): boolean {
|
|
||||||
const experimentsPreviouslyRun = experiment.condition?.experimentsPreviouslyRun;
|
|
||||||
if (experimentsPreviouslyRun) {
|
|
||||||
const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.APPLICATION), []);
|
|
||||||
let includeCheck = true;
|
|
||||||
let excludeCheck = true;
|
|
||||||
const includes = experimentsPreviouslyRun.includes;
|
|
||||||
if (Array.isArray(includes)) {
|
|
||||||
includeCheck = runExperimentIdsFromStorage.some(x => includes.indexOf(x) > -1);
|
|
||||||
}
|
|
||||||
const excludes = experimentsPreviouslyRun.excludes;
|
|
||||||
if (includeCheck && Array.isArray(excludes)) {
|
|
||||||
excludeCheck = !runExperimentIdsFromStorage.some(x => excludes.indexOf(x) > -1);
|
|
||||||
}
|
|
||||||
if (!includeCheck || !excludeCheck) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private recordActivatedEvent(event: string) {
|
|
||||||
const key = experimentEventStorageKey(event);
|
|
||||||
const record = getCurrentActivationRecord(safeParse(this.storageService.get(key, StorageScope.APPLICATION), undefined));
|
|
||||||
record.count[0]++;
|
|
||||||
this.storageService.store(key, JSON.stringify(record), StorageScope.APPLICATION, StorageTarget.MACHINE);
|
|
||||||
|
|
||||||
this._experiments
|
|
||||||
.filter(e => {
|
|
||||||
const lookingFor = e.raw?.condition?.activationEvent?.event;
|
|
||||||
if (e.state !== ExperimentState.Evaluating || !lookingFor) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof lookingFor === 'string' ? lookingFor === event : lookingFor?.includes(event);
|
|
||||||
})
|
|
||||||
.forEach(e => this.evaluateExperiment(e.raw!));
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkActivationEventFrequency(experiment: IRawExperiment) {
|
|
||||||
const setting = experiment.condition?.activationEvent;
|
|
||||||
if (!setting) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let total = 0;
|
|
||||||
let uniqueDays = 0;
|
|
||||||
|
|
||||||
const events = typeof setting.event === 'string' ? [setting.event] : setting.event;
|
|
||||||
for (const event of events) {
|
|
||||||
const { count } = getCurrentActivationRecord(safeParse(this.storageService.get(experimentEventStorageKey(event), StorageScope.APPLICATION), undefined));
|
|
||||||
|
|
||||||
for (const entry of count) {
|
|
||||||
if (entry > 0) {
|
|
||||||
uniqueDays++;
|
|
||||||
total += entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return total >= setting.minEvents && (!setting.uniqueDays || uniqueDays >= setting.uniqueDays);
|
|
||||||
}
|
|
||||||
|
|
||||||
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.APPLICATION);
|
|
||||||
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.APPLICATION), {});
|
|
||||||
|
|
||||||
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.APPLICATION), {});
|
|
||||||
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.APPLICATION, 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.APPLICATION, 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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function safeParse(text: string | undefined, defaultObject: any) {
|
|
||||||
try {
|
|
||||||
return text ? JSON.parse(text) || defaultObject : defaultObject;
|
|
||||||
} catch (e) {
|
|
||||||
return defaultObject;
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,216 +0,0 @@
|
||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import * as assert from 'assert';
|
|
||||||
import { Emitter } from 'vs/base/common/event';
|
|
||||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
|
||||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
|
||||||
import { INotificationService, IPromptChoice, IPromptOptions, Severity } from 'vs/platform/notification/common/notification';
|
|
||||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
|
||||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
|
||||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
|
||||||
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt';
|
|
||||||
import { ExperimentActionType, ExperimentState, IExperiment, IExperimentActionPromptProperties, IExperimentService, LocalizedPromptText } from 'vs/workbench/contrib/experiments/common/experimentService';
|
|
||||||
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-sandbox/experimentService.test';
|
|
||||||
import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
|
|
||||||
import { TestCommandService } from 'vs/editor/test/browser/editorTestServices';
|
|
||||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
|
||||||
|
|
||||||
suite('Experimental Prompts', () => {
|
|
||||||
let instantiationService: TestInstantiationService;
|
|
||||||
let experimentService: TestExperimentService;
|
|
||||||
let experimentalPrompt: ExperimentalPrompts;
|
|
||||||
let commandService: TestCommandService;
|
|
||||||
let onExperimentEnabledEvent: Emitter<IExperiment>;
|
|
||||||
|
|
||||||
let storageData: { [key: string]: any } = {};
|
|
||||||
const promptText = 'Hello there! Can you see this?';
|
|
||||||
const experiment: IExperiment =
|
|
||||||
{
|
|
||||||
id: 'experiment1',
|
|
||||||
enabled: true,
|
|
||||||
raw: undefined,
|
|
||||||
state: ExperimentState.Run,
|
|
||||||
action: {
|
|
||||||
type: ExperimentActionType.Prompt,
|
|
||||||
properties: {
|
|
||||||
promptText,
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
text: 'Yes',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'No'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
suiteSetup(() => {
|
|
||||||
instantiationService = new TestInstantiationService();
|
|
||||||
|
|
||||||
instantiationService.stub(ILifecycleService, new TestLifecycleService());
|
|
||||||
instantiationService.stub(ITelemetryService, NullTelemetryService);
|
|
||||||
|
|
||||||
onExperimentEnabledEvent = new Emitter<IExperiment>();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
setup(() => {
|
|
||||||
storageData = {};
|
|
||||||
instantiationService.stub(IStorageService, <Partial<IStorageService>>{
|
|
||||||
get: (a: string, b: StorageScope, c?: string) => a === 'experiments.experiment1' ? JSON.stringify(storageData) : c,
|
|
||||||
store: (a, b, c, d) => {
|
|
||||||
if (a === 'experiments.experiment1') {
|
|
||||||
storageData = JSON.parse(b + '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
instantiationService.stub(INotificationService, new TestNotificationService());
|
|
||||||
experimentService = instantiationService.createInstance(TestExperimentService);
|
|
||||||
experimentService.onExperimentEnabled = onExperimentEnabledEvent.event;
|
|
||||||
instantiationService.stub(IExperimentService, experimentService);
|
|
||||||
commandService = instantiationService.createInstance(TestCommandService);
|
|
||||||
instantiationService.stub(ICommandService, commandService);
|
|
||||||
});
|
|
||||||
|
|
||||||
teardown(() => {
|
|
||||||
if (experimentService) {
|
|
||||||
experimentService.dispose();
|
|
||||||
}
|
|
||||||
if (experimentalPrompt) {
|
|
||||||
experimentalPrompt.dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Show experimental prompt if experiment should be run. Choosing negative option should mark experiment as complete', () => {
|
|
||||||
|
|
||||||
storageData = {
|
|
||||||
enabled: true,
|
|
||||||
state: ExperimentState.Run
|
|
||||||
};
|
|
||||||
|
|
||||||
instantiationService.stub(INotificationService, {
|
|
||||||
prompt: (a: Severity, b: string, c: IPromptChoice[]) => {
|
|
||||||
assert.strictEqual(b, promptText);
|
|
||||||
assert.strictEqual(c.length, 2);
|
|
||||||
c[1].run();
|
|
||||||
return undefined!;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
experimentalPrompt = instantiationService.createInstance(ExperimentalPrompts);
|
|
||||||
onExperimentEnabledEvent.fire(experiment);
|
|
||||||
|
|
||||||
return Promise.resolve(null).then(result => {
|
|
||||||
assert.strictEqual(storageData['state'], ExperimentState.Complete);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runs experiment command', () => {
|
|
||||||
|
|
||||||
storageData = {
|
|
||||||
enabled: true,
|
|
||||||
state: ExperimentState.Run
|
|
||||||
};
|
|
||||||
|
|
||||||
const stub = instantiationService.stub(ICommandService, 'executeCommand', () => undefined);
|
|
||||||
instantiationService.stub(INotificationService, {
|
|
||||||
prompt: (a: Severity, b: string, c: IPromptChoice[], options: IPromptOptions) => {
|
|
||||||
c[0].run();
|
|
||||||
return undefined!;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
experimentalPrompt = instantiationService.createInstance(ExperimentalPrompts);
|
|
||||||
onExperimentEnabledEvent.fire({
|
|
||||||
...experiment,
|
|
||||||
action: {
|
|
||||||
type: ExperimentActionType.Prompt,
|
|
||||||
properties: {
|
|
||||||
promptText,
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
text: 'Yes',
|
|
||||||
codeCommand: { id: 'greet', arguments: ['world'] }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.resolve(null).then(result => {
|
|
||||||
assert.deepStrictEqual(stub.args[0], ['greet', 'world']);
|
|
||||||
assert.strictEqual(storageData['state'], ExperimentState.Complete);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Show experimental prompt if experiment should be run. Cancelling should mark experiment as complete', () => {
|
|
||||||
|
|
||||||
storageData = {
|
|
||||||
enabled: true,
|
|
||||||
state: ExperimentState.Run
|
|
||||||
};
|
|
||||||
|
|
||||||
instantiationService.stub(INotificationService, {
|
|
||||||
prompt: (a: Severity, b: string, c: IPromptChoice[], options: IPromptOptions) => {
|
|
||||||
assert.strictEqual(b, promptText);
|
|
||||||
assert.strictEqual(c.length, 2);
|
|
||||||
options.onCancel!();
|
|
||||||
return undefined!;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
experimentalPrompt = instantiationService.createInstance(ExperimentalPrompts);
|
|
||||||
onExperimentEnabledEvent.fire(experiment);
|
|
||||||
|
|
||||||
return Promise.resolve(null).then(result => {
|
|
||||||
assert.strictEqual(storageData['state'], ExperimentState.Complete);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test getPromptText', () => {
|
|
||||||
const simpleTextCase: IExperimentActionPromptProperties = {
|
|
||||||
promptText: 'My simple prompt',
|
|
||||||
commands: []
|
|
||||||
};
|
|
||||||
const multipleLocaleCase: IExperimentActionPromptProperties = {
|
|
||||||
promptText: {
|
|
||||||
en: 'My simple prompt for en',
|
|
||||||
de: 'My simple prompt for de',
|
|
||||||
'en-au': 'My simple prompt for Austrailian English',
|
|
||||||
'en-us': 'My simple prompt for US English'
|
|
||||||
},
|
|
||||||
commands: []
|
|
||||||
};
|
|
||||||
const englishUSTextCase: IExperimentActionPromptProperties = {
|
|
||||||
promptText: {
|
|
||||||
'en-us': 'My simple prompt for en'
|
|
||||||
},
|
|
||||||
commands: []
|
|
||||||
};
|
|
||||||
const noEnglishTextCase: IExperimentActionPromptProperties = {
|
|
||||||
promptText: {
|
|
||||||
'de-de': 'My simple prompt for German'
|
|
||||||
},
|
|
||||||
commands: []
|
|
||||||
};
|
|
||||||
|
|
||||||
assert.strictEqual(ExperimentalPrompts.getLocalizedText(simpleTextCase.promptText, 'any-language'), simpleTextCase.promptText);
|
|
||||||
const multipleLocalePromptText = multipleLocaleCase.promptText as LocalizedPromptText;
|
|
||||||
assert.strictEqual(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'en'), multipleLocalePromptText['en']);
|
|
||||||
assert.strictEqual(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'de'), multipleLocalePromptText['de']);
|
|
||||||
assert.strictEqual(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'en-au'), multipleLocalePromptText['en-au']);
|
|
||||||
assert.strictEqual(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'en-gb'), multipleLocalePromptText['en']);
|
|
||||||
assert.strictEqual(ExperimentalPrompts.getLocalizedText(multipleLocaleCase.promptText, 'fr'), multipleLocalePromptText['en']);
|
|
||||||
assert.strictEqual(ExperimentalPrompts.getLocalizedText(englishUSTextCase.promptText, 'fr'), (englishUSTextCase.promptText as LocalizedPromptText)['en-us']);
|
|
||||||
assert.strictEqual(!!ExperimentalPrompts.getLocalizedText(noEnglishTextCase.promptText, 'fr'), false);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,49 +0,0 @@
|
||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
|
||||||
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
|
||||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
|
||||||
import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService';
|
|
||||||
import { isString } from 'vs/base/common/types';
|
|
||||||
import { EXTENSION_IDENTIFIER_REGEX } from 'vs/platform/extensionManagement/common/extensionManagement';
|
|
||||||
|
|
||||||
export class ExperimentalRecommendations extends ExtensionRecommendations {
|
|
||||||
|
|
||||||
private _recommendations: ExtensionRecommendation[] = [];
|
|
||||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@IExperimentService private readonly experimentService: IExperimentService,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch extensions used by others on the same workspace as recommendations
|
|
||||||
*/
|
|
||||||
protected async doActivate(): Promise<void> {
|
|
||||||
const experiments = await this.experimentService.getExperimentsByType(ExperimentActionType.AddToRecommendations);
|
|
||||||
for (const { action, state } of experiments) {
|
|
||||||
if (state === ExperimentState.Run && isNonEmptyArray(action?.properties?.recommendations) && action?.properties?.recommendationReason) {
|
|
||||||
for (const extensionId of action.properties.recommendations) {
|
|
||||||
try {
|
|
||||||
if (isString(extensionId) && EXTENSION_IDENTIFIER_REGEX.test(extensionId)) {
|
|
||||||
this._recommendations.push({
|
|
||||||
extensionId: extensionId.toLowerCase(),
|
|
||||||
reason: {
|
|
||||||
reasonId: ExtensionRecommendationReason.Experimental,
|
|
||||||
reasonText: action.properties.recommendationReason
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {/* ignore */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||||
import { LifecyclePhase, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
import { LifecyclePhase, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||||
import { ExeBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/exeBasedRecommendations';
|
import { ExeBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/exeBasedRecommendations';
|
||||||
import { ExperimentalRecommendations } from 'vs/workbench/contrib/extensions/browser/experimentalRecommendations';
|
|
||||||
import { WorkspaceRecommendations } from 'vs/workbench/contrib/extensions/browser/workspaceRecommendations';
|
import { WorkspaceRecommendations } from 'vs/workbench/contrib/extensions/browser/workspaceRecommendations';
|
||||||
import { FileBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/fileBasedRecommendations';
|
import { FileBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/fileBasedRecommendations';
|
||||||
import { KeymapRecommendations } from 'vs/workbench/contrib/extensions/browser/keymapRecommendations';
|
import { KeymapRecommendations } from 'vs/workbench/contrib/extensions/browser/keymapRecommendations';
|
||||||
|
@ -42,7 +41,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||||
// Recommendations
|
// Recommendations
|
||||||
private readonly fileBasedRecommendations: FileBasedRecommendations;
|
private readonly fileBasedRecommendations: FileBasedRecommendations;
|
||||||
private readonly workspaceRecommendations: WorkspaceRecommendations;
|
private readonly workspaceRecommendations: WorkspaceRecommendations;
|
||||||
private readonly experimentalRecommendations: ExperimentalRecommendations;
|
|
||||||
private readonly configBasedRecommendations: ConfigBasedRecommendations;
|
private readonly configBasedRecommendations: ConfigBasedRecommendations;
|
||||||
private readonly exeBasedRecommendations: ExeBasedRecommendations;
|
private readonly exeBasedRecommendations: ExeBasedRecommendations;
|
||||||
private readonly keymapRecommendations: KeymapRecommendations;
|
private readonly keymapRecommendations: KeymapRecommendations;
|
||||||
|
@ -71,7 +69,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||||
|
|
||||||
this.workspaceRecommendations = instantiationService.createInstance(WorkspaceRecommendations);
|
this.workspaceRecommendations = instantiationService.createInstance(WorkspaceRecommendations);
|
||||||
this.fileBasedRecommendations = instantiationService.createInstance(FileBasedRecommendations);
|
this.fileBasedRecommendations = instantiationService.createInstance(FileBasedRecommendations);
|
||||||
this.experimentalRecommendations = instantiationService.createInstance(ExperimentalRecommendations);
|
|
||||||
this.configBasedRecommendations = instantiationService.createInstance(ConfigBasedRecommendations);
|
this.configBasedRecommendations = instantiationService.createInstance(ConfigBasedRecommendations);
|
||||||
this.exeBasedRecommendations = instantiationService.createInstance(ExeBasedRecommendations);
|
this.exeBasedRecommendations = instantiationService.createInstance(ExeBasedRecommendations);
|
||||||
this.keymapRecommendations = instantiationService.createInstance(KeymapRecommendations);
|
this.keymapRecommendations = instantiationService.createInstance(KeymapRecommendations);
|
||||||
|
@ -101,7 +98,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||||
this.workspaceRecommendations.activate(),
|
this.workspaceRecommendations.activate(),
|
||||||
this.configBasedRecommendations.activate(),
|
this.configBasedRecommendations.activate(),
|
||||||
this.fileBasedRecommendations.activate(),
|
this.fileBasedRecommendations.activate(),
|
||||||
this.experimentalRecommendations.activate(),
|
|
||||||
this.keymapRecommendations.activate(),
|
this.keymapRecommendations.activate(),
|
||||||
this.languageRecommendations.activate(),
|
this.languageRecommendations.activate(),
|
||||||
this.webRecommendations.activate(),
|
this.webRecommendations.activate(),
|
||||||
|
@ -138,7 +134,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||||
const allRecommendations = [
|
const allRecommendations = [
|
||||||
...this.configBasedRecommendations.recommendations,
|
...this.configBasedRecommendations.recommendations,
|
||||||
...this.exeBasedRecommendations.recommendations,
|
...this.exeBasedRecommendations.recommendations,
|
||||||
...this.experimentalRecommendations.recommendations,
|
|
||||||
...this.fileBasedRecommendations.recommendations,
|
...this.fileBasedRecommendations.recommendations,
|
||||||
...this.workspaceRecommendations.recommendations,
|
...this.workspaceRecommendations.recommendations,
|
||||||
...this.keymapRecommendations.recommendations,
|
...this.keymapRecommendations.recommendations,
|
||||||
|
@ -170,7 +165,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
|
||||||
const recommendations = [
|
const recommendations = [
|
||||||
...this.configBasedRecommendations.otherRecommendations,
|
...this.configBasedRecommendations.otherRecommendations,
|
||||||
...this.exeBasedRecommendations.otherRecommendations,
|
...this.exeBasedRecommendations.otherRecommendations,
|
||||||
...this.experimentalRecommendations.recommendations,
|
|
||||||
...this.webRecommendations.recommendations
|
...this.webRecommendations.recommendations
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,6 @@ import { IModelService } from 'vs/editor/common/services/model';
|
||||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||||
import { INotificationService, Severity, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification';
|
import { INotificationService, Severity, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification';
|
||||||
import { NativeURLService } from 'vs/platform/url/common/urlService';
|
import { NativeURLService } from 'vs/platform/url/common/urlService';
|
||||||
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
|
||||||
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-sandbox/experimentService.test';
|
|
||||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||||
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
|
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||||
|
@ -198,7 +196,6 @@ suite('ExtensionRecommendationsService Test', () => {
|
||||||
let prompted: boolean;
|
let prompted: boolean;
|
||||||
const promptedEmitter = new Emitter<void>();
|
const promptedEmitter = new Emitter<void>();
|
||||||
let onModelAddedEvent: Emitter<ITextModel>;
|
let onModelAddedEvent: Emitter<ITextModel>;
|
||||||
let experimentService: TestExperimentService;
|
|
||||||
|
|
||||||
suiteSetup(() => {
|
suiteSetup(() => {
|
||||||
instantiationService = new TestInstantiationService();
|
instantiationService = new TestInstantiationService();
|
||||||
|
@ -283,18 +280,12 @@ suite('ExtensionRecommendationsService Test', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
experimentService = instantiationService.createInstance(TestExperimentService);
|
|
||||||
instantiationService.stub(IExperimentService, experimentService);
|
|
||||||
instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService));
|
instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService));
|
||||||
instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(TestExtensionTipsService));
|
instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(TestExtensionTipsService));
|
||||||
|
|
||||||
onModelAddedEvent = new Emitter<ITextModel>();
|
onModelAddedEvent = new Emitter<ITextModel>();
|
||||||
});
|
});
|
||||||
|
|
||||||
suiteTeardown(() => {
|
|
||||||
experimentService?.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
setup(() => {
|
setup(() => {
|
||||||
instantiationService.stub(IEnvironmentService, <Partial<IEnvironmentService>>{});
|
instantiationService.stub(IEnvironmentService, <Partial<IEnvironmentService>>{});
|
||||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []);
|
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []);
|
||||||
|
|
|
@ -42,8 +42,6 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
||||||
import { Schemas } from 'vs/base/common/network';
|
import { Schemas } from 'vs/base/common/network';
|
||||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||||
import { ProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
import { ProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
||||||
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-sandbox/experimentService.test';
|
|
||||||
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
|
||||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||||
import { TestEnvironmentService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
|
import { TestEnvironmentService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
|
@ -129,7 +127,6 @@ function setupTest() {
|
||||||
instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter<IFormatterChangeEvent>().event });
|
instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter<IFormatterChangeEvent>().event });
|
||||||
|
|
||||||
instantiationService.stub(ILifecycleService, new TestLifecycleService());
|
instantiationService.stub(ILifecycleService, new TestLifecycleService());
|
||||||
instantiationService.stub(IExperimentService, instantiationService.createInstance(TestExperimentService));
|
|
||||||
instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(TestExtensionTipsService));
|
instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(TestExtensionTipsService));
|
||||||
instantiationService.stub(IExtensionRecommendationsService, {});
|
instantiationService.stub(IExtensionRecommendationsService, {});
|
||||||
instantiationService.stub(IURLService, NativeURLService);
|
instantiationService.stub(IURLService, NativeURLService);
|
||||||
|
|
|
@ -33,7 +33,6 @@ import { NativeURLService } from 'vs/platform/url/common/urlService';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||||
import { SinonStub } from 'sinon';
|
import { SinonStub } from 'sinon';
|
||||||
import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
|
||||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||||
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentService';
|
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentService';
|
||||||
import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions';
|
import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions';
|
||||||
|
@ -92,7 +91,6 @@ suite('ExtensionsViews Tests', () => {
|
||||||
|
|
||||||
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);
|
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);
|
||||||
instantiationService.stub(ISharedProcessService, TestSharedProcessService);
|
instantiationService.stub(ISharedProcessService, TestSharedProcessService);
|
||||||
instantiationService.stub(IExperimentService, ExperimentService);
|
|
||||||
|
|
||||||
instantiationService.stub(IExtensionManagementService, <Partial<IExtensionManagementService>>{
|
instantiationService.stub(IExtensionManagementService, <Partial<IExtensionManagementService>>{
|
||||||
onInstallExtension: installEvent.event,
|
onInstallExtension: installEvent.event,
|
||||||
|
@ -174,7 +172,6 @@ suite('ExtensionsViews Tests', () => {
|
||||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(galleryEnabledLanguage));
|
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(galleryEnabledLanguage));
|
||||||
instantiationService.stubPromise(IExtensionGalleryService, 'getCompatibleExtension', galleryEnabledLanguage);
|
instantiationService.stubPromise(IExtensionGalleryService, 'getCompatibleExtension', galleryEnabledLanguage);
|
||||||
instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', [galleryEnabledLanguage]);
|
instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', [galleryEnabledLanguage]);
|
||||||
instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', []);
|
|
||||||
|
|
||||||
instantiationService.stub(IViewDescriptorService, {
|
instantiationService.stub(IViewDescriptorService, {
|
||||||
getViewLocationById(): ViewContainerLocation {
|
getViewLocationById(): ViewContainerLocation {
|
||||||
|
|
|
@ -42,8 +42,6 @@ import { TestContextService } from 'vs/workbench/test/common/workbenchTestServic
|
||||||
import { IProductService } from 'vs/platform/product/common/productService';
|
import { IProductService } from 'vs/platform/product/common/productService';
|
||||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||||
import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
|
import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||||
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
|
||||||
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-sandbox/experimentService.test';
|
|
||||||
import { Schemas } from 'vs/base/common/network';
|
import { Schemas } from 'vs/base/common/network';
|
||||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||||
|
@ -116,7 +114,6 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
||||||
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
|
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
|
||||||
|
|
||||||
instantiationService.stub(ILifecycleService, new TestLifecycleService());
|
instantiationService.stub(ILifecycleService, new TestLifecycleService());
|
||||||
instantiationService.stub(IExperimentService, instantiationService.createInstance(TestExperimentService));
|
|
||||||
instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(TestExtensionTipsService));
|
instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(TestExtensionTipsService));
|
||||||
instantiationService.stub(IExtensionRecommendationsService, {});
|
instantiationService.stub(IExtensionRecommendationsService, {});
|
||||||
|
|
||||||
|
|
|
@ -325,9 +325,6 @@ import 'vs/workbench/contrib/languageDetection/browser/languageDetection.contrib
|
||||||
// Language Status
|
// Language Status
|
||||||
import 'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution';
|
import 'vs/workbench/contrib/languageStatus/browser/languageStatus.contribution';
|
||||||
|
|
||||||
// Experiments
|
|
||||||
import 'vs/workbench/contrib/experiments/browser/experiments.contribution';
|
|
||||||
|
|
||||||
// Send a Smile
|
// Send a Smile
|
||||||
import 'vs/workbench/contrib/feedback/browser/feedback.contribution';
|
import 'vs/workbench/contrib/feedback/browser/feedback.contribution';
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue