/ src / common / AllExperiments / Experiments.cs
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  }