Experiments.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Globalization; 6 using System.Text.Json; 7 8 using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events; 9 using Microsoft.PowerToys.Telemetry; 10 using Microsoft.VariantAssignment.Client; 11 using Microsoft.VariantAssignment.Contract; 12 using Windows.System.Profile; 13 14 namespace AllExperiments 15 { 16 // The dependencies required to build this project are only available in the official build pipeline and are internal to Microsoft. 17 // However, this project is not required to build a test version of the application. 18 public class Experiments 19 { 20 public enum ExperimentState 21 { 22 Enabled, 23 Disabled, 24 NotLoaded, 25 } 26 27 #pragma warning disable SA1401 // Need to use LandingPageExperiment as a static property in OobeShellPage.xaml.cs 28 #pragma warning disable CA2211 // Non-constant fields should not be visible 29 public static ExperimentState LandingPageExperiment = ExperimentState.NotLoaded; 30 #pragma warning restore CA2211 31 #pragma warning restore SA1401 32 33 public async Task<bool> EnableLandingPageExperimentAsync() 34 { 35 if (Experiments.LandingPageExperiment != ExperimentState.NotLoaded) 36 { 37 return Experiments.LandingPageExperiment == ExperimentState.Enabled; 38 } 39 40 Experiments varServ = new Experiments(); 41 await varServ.VariantAssignmentProvider_Initialize(); 42 var landingPageExperiment = varServ.IsExperiment; 43 44 Experiments.LandingPageExperiment = landingPageExperiment ? ExperimentState.Enabled : ExperimentState.Disabled; 45 46 return landingPageExperiment; 47 } 48 49 private async Task VariantAssignmentProvider_Initialize() 50 { 51 IsExperiment = false; 52 string jsonFilePath = CreateFilePath(); 53 54 var vaSettings = new VariantAssignmentClientSettings 55 { 56 Endpoint = new Uri("https://default.exp-tas.com/exptas77/a7a397e7-6fbe-4f21-a4e9-3f542e4b000e-exppowertoys/api/v1/tas"), 57 EnableCaching = true, 58 ResponseCacheTime = TimeSpan.FromMinutes(5), 59 }; 60 61 try 62 { 63 var vaClient = vaSettings.GetTreatmentAssignmentServiceClient(); 64 var vaRequest = GetVariantAssignmentRequest(); 65 using var variantAssignments = await vaClient.GetVariantAssignmentsAsync(vaRequest).ConfigureAwait(false); 66 67 if (variantAssignments.AssignedVariants.Count != 0) 68 { 69 var dataVersion = variantAssignments.DataVersion; 70 var featureVariables = variantAssignments.GetFeatureVariables(); 71 var assignmentContext = variantAssignments.GetAssignmentContext(); 72 var featureFlagValue = featureVariables[0].GetStringValue(); 73 74 var experimentGroup = string.Empty; 75 string json = File.ReadAllText(jsonFilePath); 76 var jsonDictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(json); 77 78 if (jsonDictionary != null) 79 { 80 if (!jsonDictionary.TryGetValue("dataversion", out object? value)) 81 { 82 value = dataVersion; 83 jsonDictionary.Add("dataversion", value); 84 } 85 86 if (!jsonDictionary.ContainsKey("variantassignment")) 87 { 88 jsonDictionary.Add("variantassignment", featureFlagValue); 89 } 90 else 91 { 92 var jsonDataVersion = value.ToString(); 93 if (jsonDataVersion != null && int.Parse(jsonDataVersion, CultureInfo.InvariantCulture) < dataVersion) 94 { 95 jsonDictionary["dataversion"] = dataVersion; 96 jsonDictionary["variantassignment"] = featureFlagValue; 97 } 98 } 99 100 experimentGroup = jsonDictionary["variantassignment"].ToString(); 101 102 string output = JsonSerializer.Serialize(jsonDictionary); 103 File.WriteAllText(jsonFilePath, output); 104 } 105 106 if (experimentGroup == "alternate" && AssignmentUnit != string.Empty) 107 { 108 IsExperiment = true; 109 } 110 111 PowerToysTelemetry.Log.WriteEvent(new OobeVariantAssignmentEvent() { AssignmentContext = assignmentContext, ClientID = AssignmentUnit }); 112 } 113 } 114 catch (HttpRequestException ex) 115 { 116 string json = File.ReadAllText(jsonFilePath); 117 var jsonDictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(json); 118 119 if (jsonDictionary != null) 120 { 121 if (jsonDictionary.TryGetValue("variantassignment", out object? value)) 122 { 123 if (value.ToString() == "alternate" && AssignmentUnit != string.Empty) 124 { 125 IsExperiment = true; 126 } 127 } 128 else 129 { 130 jsonDictionary["variantassignment"] = "current"; 131 } 132 } 133 134 string output = JsonSerializer.Serialize(jsonDictionary); 135 File.WriteAllText(jsonFilePath, output); 136 137 Logger.LogError("Error getting to TAS endpoint", ex); 138 } 139 catch (Exception ex) 140 { 141 Logger.LogError("Error getting variant assignments for experiment", ex); 142 } 143 } 144 145 public bool IsExperiment { get; set; } 146 147 private string? AssignmentUnit { get; set; } 148 149 private VariantAssignmentRequest GetVariantAssignmentRequest() 150 { 151 var jsonFilePath = CreateFilePath(); 152 try 153 { 154 if (!File.Exists(jsonFilePath)) 155 { 156 AssignmentUnit = Guid.NewGuid().ToString(); 157 var data = new Dictionary<string, string>() 158 { 159 ["clientid"] = AssignmentUnit, 160 }; 161 string jsonData = JsonSerializer.Serialize(data); 162 File.WriteAllText(jsonFilePath, jsonData); 163 } 164 else 165 { 166 string json = File.ReadAllText(jsonFilePath); 167 var jsonDictionary = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(json); 168 if (jsonDictionary != null) 169 { 170 AssignmentUnit = jsonDictionary["clientid"]?.ToString(); 171 } 172 } 173 } 174 catch (Exception ex) 175 { 176 Logger.LogError("Error creating/getting AssignmentUnit", ex); 177 } 178 179 var attrNames = new List<string> { "FlightRing", "c:InstallLanguage" }; 180 var attrData = AnalyticsInfo.GetSystemPropertiesAsync(attrNames).AsTask().GetAwaiter().GetResult(); 181 182 var flightRing = string.Empty; 183 var installLanguage = string.Empty; 184 185 if (attrData.ContainsKey("FlightRing")) 186 { 187 flightRing = attrData["FlightRing"]; 188 } 189 190 if (attrData.ContainsKey("InstallLanguage")) 191 { 192 installLanguage = attrData["InstallLanguage"]; 193 } 194 195 return new VariantAssignmentRequest 196 { 197 Parameters = 198 { 199 { "installLanguage", installLanguage }, 200 { "flightRing", flightRing }, 201 { "clientid", AssignmentUnit }, 202 }, 203 }; 204 } 205 206 private string CreateFilePath() 207 { 208 var exeDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); 209 var settingsPath = @"Microsoft\PowerToys\experimentation.json"; 210 var filePath = Path.Combine(exeDir, settingsPath); 211 return filePath; 212 } 213 } 214 }