Skip to content

Commit 3a06c12

Browse files
Merge pull request #55239 from CyrusNajmabadi/treatmentVariables
Add support for the new 'treatment variable' approach to A/B testing.
2 parents d9fe2bb + 0487d72 commit 3a06c12

1 file changed

Lines changed: 48 additions & 36 deletions

File tree

src/VisualStudio/Core/Def/Experimentation/VisualStudioExperimentationService.cs

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
#nullable disable
6-
75
using System;
86
using System.Collections.Generic;
97
using System.Composition;
@@ -13,16 +11,17 @@
1311
using Microsoft.CodeAnalysis.Host.Mef;
1412
using Microsoft.Internal.VisualStudio.Shell.Interop;
1513
using Microsoft.VisualStudio.Shell;
14+
using VSExperimentation = Microsoft.VisualStudio.Experimentation;
1615

1716
namespace Microsoft.VisualStudio.LanguageServices.Experimentation
1817
{
1918
[Export(typeof(VisualStudioExperimentationService))]
2019
[ExportWorkspaceService(typeof(IExperimentationService), ServiceLayer.Host), Shared]
2120
internal class VisualStudioExperimentationService : ForegroundThreadAffinitizedObject, IExperimentationService
2221
{
23-
private readonly object _experimentationServiceOpt;
24-
private readonly MethodInfo _isCachedFlightEnabledInfo;
25-
private readonly IVsFeatureFlags _featureFlags;
22+
private readonly object? _experimentationServiceOpt;
23+
private readonly MethodInfo? _isCachedFlightEnabledInfo;
24+
private readonly IVsFeatureFlags? _featureFlags;
2625

2726
/// <summary>
2827
/// Cache of values we've queried from the underlying VS service. These values are expected to last for the
@@ -36,15 +35,15 @@ internal class VisualStudioExperimentationService : ForegroundThreadAffinitizedO
3635
public VisualStudioExperimentationService(IThreadingContext threadingContext, SVsServiceProvider serviceProvider)
3736
: base(threadingContext)
3837
{
39-
object experimentationServiceOpt = null;
40-
MethodInfo isCachedFlightEnabledInfo = null;
41-
IVsFeatureFlags featureFlags = null;
38+
object? experimentationServiceOpt = null;
39+
MethodInfo? isCachedFlightEnabledInfo = null;
40+
IVsFeatureFlags? featureFlags = null;
4241

4342
threadingContext.JoinableTaskFactory.Run(async () =>
4443
{
4544
try
4645
{
47-
featureFlags = (IVsFeatureFlags)await ((IAsyncServiceProvider)serviceProvider).GetServiceAsync(typeof(SVsFeatureFlags)).ConfigureAwait(false);
46+
featureFlags = (IVsFeatureFlags?)await ((IAsyncServiceProvider)serviceProvider).GetServiceAsync(typeof(SVsFeatureFlags)).ConfigureAwait(false);
4847
experimentationServiceOpt = await ((IAsyncServiceProvider)serviceProvider).GetServiceAsync(typeof(SVsExperimentationService)).ConfigureAwait(false);
4948
if (experimentationServiceOpt != null)
5049
{
@@ -71,8 +70,8 @@ public void EnableExperiment(string experimentName, bool value)
7170
_experimentEnabledMap.Remove(experimentName);
7271
}
7372

74-
var featureFlags2 = (IVsFeatureFlags2)_featureFlags;
75-
featureFlags2.EnableFeatureFlag(experimentName, value);
73+
var featureFlags2 = (IVsFeatureFlags2?)_featureFlags;
74+
featureFlags2?.EnableFeatureFlag(experimentName, value);
7675
}
7776

7877
public bool IsExperimentEnabled(string experimentName)
@@ -101,36 +100,49 @@ public bool IsExperimentEnabled(string experimentName)
101100
private bool IsExperimentEnabledWorker(string experimentName)
102101
{
103102
ThisCanBeCalledOnAnyThread();
104-
if (_isCachedFlightEnabledInfo != null)
103+
104+
// First check feature flags.
105+
try
105106
{
106-
try
107+
// check whether "." exist in the experimentName since it is requirement for featureflag service.
108+
// we do this since RPS complains about resource file being loaded for invalid name exception
109+
// we are not testing all rules but just simple "." check
110+
if (experimentName.IndexOf(".") > 0 && _featureFlags != null && _featureFlags.IsFeatureEnabled(experimentName, defaultValue: false))
111+
return true;
112+
}
113+
catch
114+
{
115+
// featureFlags can throw if given name is in incorrect format which can happen for us
116+
// since we use this for experimentation service as well
117+
}
118+
119+
// Then the legacy 'targetted notification system'.
120+
try
121+
{
122+
if (_isCachedFlightEnabledInfo != null && (bool)_isCachedFlightEnabledInfo.Invoke(_experimentationServiceOpt, new object[] { experimentName }))
123+
return true;
124+
}
125+
catch
126+
{
127+
}
128+
129+
// Finally, the modern 'treatment variable' system.
130+
try
131+
{
132+
if (VSExperimentation.ExperimentationService.Default is VSExperimentation.IExperimentationService3 experimentationService)
107133
{
108-
// check whether "." exist in the experimentName since it is requirement for featureflag service.
109-
// we do this since RPS complains about resource file being loaded for invalid name exception
110-
// we are not testing all rules but just simple "." check
111-
if (experimentName.IndexOf(".") > 0)
134+
// Get from the well known 'VisualStudio' set of treatment variables.
135+
var treatmentVariables = experimentationService.GetCachedTreatmentVariables("VisualStudio");
136+
if (treatmentVariables != null &&
137+
treatmentVariables.TryGetValue(experimentName, out var value) &&
138+
value is true)
112139
{
113-
var enabled = _featureFlags.IsFeatureEnabled(experimentName, defaultValue: false);
114-
if (enabled)
115-
{
116-
return enabled;
117-
}
140+
return true;
118141
}
119142
}
120-
catch
121-
{
122-
// featureFlags can throw if given name is in incorrect format which can happen for us
123-
// since we use this for experimentation service as well
124-
}
125-
126-
try
127-
{
128-
return (bool)_isCachedFlightEnabledInfo.Invoke(_experimentationServiceOpt, new object[] { experimentName });
129-
}
130-
catch
131-
{
132-
133-
}
143+
}
144+
catch
145+
{
134146
}
135147

136148
return false;

0 commit comments

Comments
 (0)