microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.1.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/experimentService/experimentService.ts

186lines · modeblame

88671796RedMickey5 years ago1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as Configstore from "configstore";
5import * as https from "https";
6import * as vscode from "vscode";
7import { IExperiment } from "./IExperiment";
8import { PromiseUtil } from "../../common/node/promise";
9import { TelemetryHelper } from "../../common/telemetryHelper";
10import { Telemetry } from "../../common/telemetry";
11
12export enum ExperimentStatuses {
13ENABLED = "enabled",
14DISABLED = "disabled",
15FAILED = "failed",
16}
17
18export interface ExperimentConfig {
19experimentName: string;
20popCoveragePercent: number;
21enabled: boolean;
22}
23
24export interface ExperimentParameters extends ExperimentConfig {
25[key: string]: any;
26extensionId?: string;
27}
28
29export interface ExperimentResult {
30resultStatus: ExperimentStatuses;
31updatedExperimentParameters: ExperimentParameters;
32error?: Error;
33}
34
35export class ExperimentService implements vscode.Disposable {
36private static instance: ExperimentService;
37
38private readonly endpointURL: string;
39private readonly configName: string;
40private config: Configstore;
41private downloadedExperimentsConfig: Array<ExperimentConfig> | null;
42private experimentsInstances: Map<string, IExperiment>;
43private downloadConfigRequest: Promise<ExperimentConfig[]>;
44private cancellationTokenSource: vscode.CancellationTokenSource;
45
46public static create () {
47if (!ExperimentService.instance) {
48ExperimentService.instance = new ExperimentService();
49}
50
51return ExperimentService.instance;
52}
53
54public async runExperiments(): Promise<void> {
55if (!this.downloadedExperimentsConfig) {
56this.downloadedExperimentsConfig = await this.downloadConfigRequest;
57this.experimentsInstances = await this.initializeExperimentsInstances();
58}
59
60let experimentResults: Array<ExperimentResult> = await Promise.all(
61this.downloadedExperimentsConfig.map(expConfig => this.executeExperiment(expConfig))
62);
63
64this.sendExperimentTelemetry(experimentResults);
65}
66
67public dispose() {
68this.cancellationTokenSource.cancel();
69this.cancellationTokenSource.dispose();
70}
71
72private constructor() {
73this.endpointURL = "https://microsoft.github.io/vscode-react-native/experiments/experimentsConfig.json";
74this.configName = "reactNativeToolsConfig";
75
76this.config = new Configstore(this.configName);
77this.cancellationTokenSource = new vscode.CancellationTokenSource();
78this.downloadedExperimentsConfig = null;
79
80this.downloadConfigRequest = this.retryDownloadExperimentsConfig();
81}
82
83private async executeExperiment(expConfig: ExperimentConfig): Promise<ExperimentResult> {
84let curExperimentParameters = this.config.get(expConfig.experimentName);
85let expInstance = this.experimentsInstances.get(expConfig.experimentName);
86
87let expResult: ExperimentResult;
88if (expInstance && expConfig.enabled) {
89try {
90expResult = await expInstance.run(expConfig, curExperimentParameters);
91this.config.set(expConfig.experimentName, expResult.updatedExperimentParameters);
92} catch (err) {
93expResult = {
94resultStatus: ExperimentStatuses.FAILED,
95updatedExperimentParameters: expConfig,
96error: err,
97};
98}
99} else {
100expResult = {
101resultStatus: ExperimentStatuses.DISABLED,
102updatedExperimentParameters: expConfig,
103};
104}
105
106return expResult;
107}
108
109private async retryDownloadExperimentsConfig(retryCount = 60): Promise<ExperimentConfig[]> {
110try {
111return await this.downloadExperimentsConfig();
112} catch (err) {
113if (retryCount < 1 || this.cancellationTokenSource.token.isCancellationRequested) {
114throw err;
115}
116
259c018fYuri Skorokhodov5 years ago117await PromiseUtil.delay(2000);
88671796RedMickey5 years ago118return await this.retryDownloadExperimentsConfig(--retryCount);
119}
120}
121
122private async initializeExperimentsInstances(): Promise<Map<string, IExperiment>> {
123let expInstances = new Map<string, IExperiment>();
124
125if (this.downloadedExperimentsConfig) {
126for (let expConfig of this.downloadedExperimentsConfig) {
127try {
128let expClass = await import(`./experiments/${expConfig.experimentName}`);
129expInstances.set(
130expConfig.experimentName,
131new expClass.default()
132);
133} catch (err) {
134expConfig.enabled = false;
135}
136}
137}
138
139return expInstances;
140}
141
142private downloadExperimentsConfig(): Promise<ExperimentConfig[]> {
143return new Promise<ExperimentConfig[]>((resolve, reject) => {
144https.get(this.endpointURL, (response) => {
145let data = "";
146response.setEncoding("utf8");
147response.on("data", (chunk: string) => (data += chunk));
148response.on("end", () => {
149try {
150resolve(JSON.parse(data));
151} catch (err) {
152reject(err);
153}
154});
155response.on("error", reject);
156}).on("error", reject);
157});
158}
159
160private sendExperimentTelemetry(experimentsResults: ExperimentResult[]): void {
161const runExperimentsEvent = TelemetryHelper.createTelemetryEvent("runExperiments");
162
163experimentsResults.forEach(expResult => {
164if (
165expResult.resultStatus === ExperimentStatuses.FAILED
166&& expResult.error
167) {
168TelemetryHelper.addTelemetryEventErrorProperty(
169runExperimentsEvent,
170expResult.error,
171undefined,
172`${expResult.updatedExperimentParameters.experimentName}.`
173);
174} else {
175TelemetryHelper.addTelemetryEventProperty(
176runExperimentsEvent,
177expResult.updatedExperimentParameters.experimentName,
178expResult.resultStatus,
179false
180);
181}
182});
183
184Telemetry.send(runExperimentsEvent);
185}
186}