microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dev/v-peq/add-security-validation-tests

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/services/experimentService/experimentService.ts

151lines · modecode

1// 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 vscode from "vscode";
5import { TelemetryHelper } from "../../../common/telemetryHelper";
6import { Telemetry } from "../../../common/telemetry";
7import { ExtensionConfigManager } from "../../extensionConfigManager";
8import { IConfig, retryDownloadConfig } from "../remoteConfigHelper";
9import { IExperiment } from "./IExperiment";
10
11export enum ExperimentStatuses {
12 ENABLED = "enabled",
13 DISABLED = "disabled",
14 FAILED = "failed",
15}
16
17export interface ExperimentConfig extends IConfig {
18 experimentName: string;
19 popCoveragePercent: number;
20 enabled: boolean;
21}
22
23export interface ExperimentParameters extends ExperimentConfig {
24 [key: string]: any;
25 extensionId?: string;
26}
27
28export interface ExperimentResult {
29 resultStatus: ExperimentStatuses;
30 updatedExperimentParameters: ExperimentParameters;
31 error?: Error;
32}
33
34export class ExperimentService implements vscode.Disposable {
35 private static instance: ExperimentService | null;
36
37 private readonly endpointURL: string;
38 private downloadedExperimentsConfig: Array<ExperimentConfig> | null;
39 private experimentsInstances!: Map<string, IExperiment>;
40 private downloadConfigRequest: Promise<ExperimentConfig[]>;
41 private cancellationTokenSource: vscode.CancellationTokenSource;
42
43 public static create(): ExperimentService {
44 if (!ExperimentService.instance) {
45 ExperimentService.instance = new ExperimentService();
46 }
47
48 return ExperimentService.instance;
49 }
50
51 public async runExperiments(): Promise<void> {
52 if (!this.downloadedExperimentsConfig) {
53 this.downloadedExperimentsConfig = await this.downloadConfigRequest;
54 this.experimentsInstances = await this.initializeExperimentsInstances();
55 }
56
57 const experimentResults: Array<ExperimentResult> = await Promise.all(
58 this.downloadedExperimentsConfig.map(expConfig => this.executeExperiment(expConfig)),
59 );
60
61 this.sendExperimentTelemetry(experimentResults);
62 }
63
64 public dispose(): void {
65 this.cancellationTokenSource.cancel();
66 this.cancellationTokenSource.dispose();
67 ExperimentService.instance = null;
68 }
69
70 private constructor() {
71 this.endpointURL =
72 "https://microsoft.github.io/vscode-react-native/experiments/experimentsConfig.json";
73 this.cancellationTokenSource = new vscode.CancellationTokenSource();
74 this.downloadedExperimentsConfig = null;
75
76 this.downloadConfigRequest = retryDownloadConfig<ExperimentConfig[]>(
77 this.endpointURL,
78 this.cancellationTokenSource,
79 );
80 }
81
82 private async executeExperiment(expConfig: ExperimentConfig): Promise<ExperimentResult> {
83 const curExperimentParameters = ExtensionConfigManager.config.get(expConfig.experimentName);
84 const expInstance = this.experimentsInstances.get(expConfig.experimentName);
85
86 let expResult: ExperimentResult;
87 if (expInstance && expConfig.enabled) {
88 try {
89 expResult = await expInstance.run(expConfig, curExperimentParameters);
90 ExtensionConfigManager.config.set(
91 expConfig.experimentName,
92 expResult.updatedExperimentParameters,
93 );
94 } catch (err) {
95 expResult = {
96 resultStatus: ExperimentStatuses.FAILED,
97 updatedExperimentParameters: expConfig,
98 error: err as Error,
99 };
100 }
101 } else {
102 expResult = {
103 resultStatus: ExperimentStatuses.DISABLED,
104 updatedExperimentParameters: expConfig,
105 };
106 }
107
108 return expResult;
109 }
110
111 private async initializeExperimentsInstances(): Promise<Map<string, IExperiment>> {
112 const expInstances = new Map<string, IExperiment>();
113
114 if (this.downloadedExperimentsConfig) {
115 for (const expConfig of this.downloadedExperimentsConfig) {
116 try {
117 const expClass = await import(`./experiments/${expConfig.experimentName}`);
118 expInstances.set(expConfig.experimentName, new expClass.default());
119 } catch (err) {
120 expConfig.enabled = false;
121 }
122 }
123 }
124
125 return expInstances;
126 }
127
128 private sendExperimentTelemetry(experimentsResults: ExperimentResult[]): void {
129 const runExperimentsEvent = TelemetryHelper.createTelemetryEvent("runExperiments");
130
131 experimentsResults.forEach(expResult => {
132 if (expResult.resultStatus === ExperimentStatuses.FAILED && expResult.error) {
133 TelemetryHelper.addTelemetryEventErrorProperty(
134 runExperimentsEvent,
135 expResult.error,
136 undefined,
137 `${expResult.updatedExperimentParameters.experimentName}.`,
138 );
139 } else {
140 TelemetryHelper.addTelemetryEventProperty(
141 runExperimentsEvent,
142 expResult.updatedExperimentParameters.experimentName,
143 expResult.resultStatus,
144 false,
145 );
146 }
147 });
148
149 Telemetry.send(runExperimentsEvent);
150 }
151}
152