-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding the client code for supporting experiments #5034
base: master
Are you sure you want to change the base?
Conversation
@@ -25,6 +25,12 @@ If you disable this setting, users will not be able to change settings for the W | |||
If you enable or do not configure this setting, users will be able to enable experimental features for the Windows Package Manager. | |||
|
|||
If you disable this setting, users will not be able to enable experimental features for the Windows Package Manager.</string> | |||
<string id="EnableExperiments">Enable Windows Package Manager Experiments</string> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding @RDMacLachlan to help with the strings to be used in the adml file
|
||
namespace AppInstaller::Experiment | ||
{ | ||
bool IsEnabled(const std::string& experimentKey); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the internal API will make it easy to do lookups by string. May be better to do an enum if we're going to end up doing a switch anyways.
AICLI_LOG(Core, Info, << | ||
"Experiment " << Experiment::GetExperiment(key).Name() << | ||
" is disabled due to group policy: " << TogglePolicy::GetPolicy(TogglePolicy::Policy::Experiments).RegValueName()); | ||
return { false, ExperimentToggleSource::Policy }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like you're intending for all experiments to have a default value of "disabled". I think the API we'll use allows us to have the control group be "enabled" and the experiment "disabled". We should at least add a comment somewhere we'll see while adding a new experiment to remind us to not add experiments like that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please provide more details about this comment? I’m not entirely clear on what the comment should mention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should consider the meaning of the boolean associated with an experiment not "enabled" or "disabled", but rather, "In the experimental group?" That makes the default state of "false" mean "Not in the experimental group", and thus more directly maps to group policy that disables running experiments. A comment on ExperimentState::IsEnabled
indicating this semantic and counter-indicating use of the "inverted" experiment would be sufficient.
@@ -25,6 +25,12 @@ If you disable this setting, users will not be able to change settings for the W | |||
If you enable or do not configure this setting, users will be able to enable experimental features for the Windows Package Manager. | |||
|
|||
If you disable this setting, users will not be able to enable experimental features for the Windows Package Manager.</string> | |||
<string id="EnableExperiments">Enable Windows Package Manager Experiments</string> | |||
<string id="EnableExperimentsExplanation">This policy controls whether users can enable experiments in the Windows Package Manager. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may need to expand on what an experiment is, and how it differs from an experimental feature, so that IT admins can make the decision
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 this is a placeholder text I added and will be replaced by PM's suggestion 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use "Experimentation" rather than "Experiments"
The policy controls whether users can have experiments run in WinGet on their device.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the policy control:
- experimentation all up
- experiment state being driven externally
- user control over experiment state
This string seems to indicate only 3, but my expectation would be either 1 or 2.
std::string_view GetWingetCommunitySource() | ||
{ | ||
if (Settings::Experiment::GetState(Settings::Experiment::Key::CDN).IsEnabled()) | ||
{ | ||
return s_Source_WingetCommunityExperimental_Arg; | ||
} | ||
|
||
return s_Source_WingetCommunityDefault_Arg; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do some validation when a user tries to add one of the default sources. For example, to block it if the group policy restricts the default source. This may allow for an unintended way to bypass that validation. E.g., somebody has the experiment and the group policy, which means we block adding the cdn2.winget... source, but they can still add cdn.winget...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think the following should cover it:
- Remove
GetWellKnownSourceArg
- Update
CheckForWellKnownSourceMatch
to
a. be case insensitive
b. handle both DNS entries
c. allow for not checking certain values (ex. name), maybe by passing an emptystring_view
- Replace use of
GetWellKnownSourceArg
inGetPolicyBlockingUserSource
with the updatedCheckForWellKnownSourceMatch
It didn't look like any other paths were relevant there.
@@ -278,7 +290,7 @@ namespace AppInstaller::Repository | |||
|
|||
std::optional<WellKnownSource> CheckForWellKnownSourceMatch(std::string_view name, std::string_view arg, std::string_view type) | |||
{ | |||
if (name == s_Source_WingetCommunityDefault_Name && arg == s_Source_WingetCommunityDefault_Arg && type == Microsoft::PreIndexedPackageSourceFactory::Type()) | |||
if (name == s_Source_WingetCommunityDefault_Name && arg == GetWingetCommunitySource() && type == Microsoft::PreIndexedPackageSourceFactory::Type()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my previous comment. Not sure if this is the only place that needs something like this
if (name == s_Source_WingetCommunityDefault_Name && arg == GetWingetCommunitySource() && type == Microsoft::PreIndexedPackageSourceFactory::Type()) | |
if (name == s_Source_WingetCommunityDefault_Name && (arg == s_Source_WingetCommunityDefault_Arg || arg == s_Source_WingetCommunityExperimental_Arg) && type == Microsoft::PreIndexedPackageSourceFactory::Type()) |
@@ -25,6 +25,12 @@ If you disable this setting, users will not be able to change settings for the W | |||
If you enable or do not configure this setting, users will be able to enable experimental features for the Windows Package Manager. | |||
|
|||
If you disable this setting, users will not be able to enable experimental features for the Windows Package Manager.</string> | |||
<string id="EnableExperiments">Enable Windows Package Manager Experiments</string> | |||
<string id="EnableExperimentsExplanation">This policy controls whether users can enable experiments in the Windows Package Manager. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the policy control:
- experimentation all up
- experiment state being driven externally
- user control over experiment state
This string seems to indicate only 3, but my expectation would be either 1 or 2.
@@ -11,19 +11,22 @@ | |||
|
|||
#define AICLI_TraceLoggingStringView(_sv_,_name_) TraceLoggingCountedUtf8String(_sv_.data(), static_cast<ULONG>(_sv_.size()), _name_) | |||
#define AICLI_TraceLoggingWStringView(_sv_,_name_) TraceLoggingCountedWideString(_sv_.data(), static_cast<ULONG>(_sv_.size()), _name_) | |||
#define AICLI_TraceLoggingJsonWString(_sv_, _name_) TraceLoggingPackedFieldEx(_sv_.c_str(), static_cast<ULONG>((_sv_.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, _name_) | |||
#define AICLI_TraceLoggingJsonString(_sv_, _name_) TraceLoggingPackedFieldEx(_sv_.c_str(), static_cast<ULONG>((_sv_.size() + 1) * sizeof(char)), TlgInUNICODESTRING, TlgOutJSON, _name_) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are several things wrong with this:
- It should be
TlgInANSISTRING
for 1 byte characters - It should be
TlgOutUTF8
for UTF-8 encoding - It can't be both
TlgOutUTF8
andTlgOutJSON
as they are not flags
So ultimately you need to just get rid of it and force conversion to UTF-16 and use of AICLI_TraceLoggingJsonWString
.
Settings::ExperimentState TelemetryTraceLogger::GetExperimentState(Experiment::Key key) | ||
{ | ||
#ifndef AICLI_DISABLE_TEST_HOOKS | ||
m_summary.Experiments.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we will never be in a state of having two experiments during testing? Make an explicit test hook function to clear it rather than always doing it.
@@ -838,7 +870,8 @@ namespace AppInstaller::Logging | |||
AICLI_TraceLoggingStringView(m_summary.RepairExecutionType, "RepairExecutionType"), | |||
TraceLoggingUInt32(m_summary.RepairErrorCode, "RepairErrorCode"), | |||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage | PDT_SoftwareSetupAndInventory), | |||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); | |||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), | |||
AICLI_TraceLoggingJsonString(experimentsJson, "ExperimentsJson")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment above on the new macros for why we can't do that here. As to why we do generally; the telemetry system provides us with both options so we use whatever format we already have rather than converting.
@@ -77,6 +80,19 @@ namespace AppInstaller::Logging | |||
return "unknown"sv; | |||
} | |||
} | |||
|
|||
std::string GetExperimentsJson(const std::map<Experiment::Key, ExperimentState>& experiments) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please create an ExperimentStateCache
so that this code and the lookup can live in Experiment.cpp
.
@@ -278,6 +304,7 @@ namespace AppInstaller::Settings | |||
WINGET_VALIDATE_PASS_THROUGH(UninstallPurgePortablePackage) | |||
WINGET_VALIDATE_PASS_THROUGH(NetworkWingetAlternateSourceURL) | |||
WINGET_VALIDATE_PASS_THROUGH(MaxResumes) | |||
WINGET_VALIDATE_PASS_THROUGH(Experiments) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think as long as it converted there is no more validation to do.
std::string_view GetWingetCommunitySource() | ||
{ | ||
if (Settings::Experiment::GetState(Settings::Experiment::Key::CDN).IsEnabled()) | ||
{ | ||
return s_Source_WingetCommunityExperimental_Arg; | ||
} | ||
|
||
return s_Source_WingetCommunityDefault_Arg; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think the following should cover it:
- Remove
GetWellKnownSourceArg
- Update
CheckForWellKnownSourceMatch
to
a. be case insensitive
b. handle both DNS entries
c. allow for not checking certain values (ex. name), maybe by passing an emptystring_view
- Replace use of
GetWellKnownSourceArg
inGetPolicyBlockingUserSource
with the updatedCheckForWellKnownSourceMatch
It didn't look like any other paths were relevant there.
template<> | ||
std::optional<std::map<std::string, bool>> GetValue(const Json::Value& node) | ||
{ | ||
std::map<std::string, bool> result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move into inner scope.
doc/admx/DesktopAppInstaller.admx
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine there is already a policy to disable experimentation across Windows as a whole. I found AllowExperimentation but I'm not sure if it does what I think.
If there is such a policy, I think we would be better off not having our own and just pointing people to that one. If somebody disables it system-wide, then our policy won't do anything because the infrastructure we use will be disabled. The only case where it would be useful to have both is if somebody wants to have experimentation in Windows as a whole, but not on winget specifically, which I don't think is a common scenario
@@ -25,6 +25,12 @@ If you disable this setting, users will not be able to change settings for the W | |||
If you enable or do not configure this setting, users will be able to enable experimental features for the Windows Package Manager. | |||
|
|||
If you disable this setting, users will not be able to enable experimental features for the Windows Package Manager.</string> | |||
<string id="EnableExperiments">Enable Windows Package Manager Experiments</string> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest the policy be called "Allow" instead of "Enable" even if it is not consistent with the other policies.
To me, "Enable" means that the experiments will be turned on but that's not what happens. What happens is that experiments may be turned on depending on the control/treatment groups; which is better described by "Allow"
It probably should also mention that they are Microsoft-run experiments for new features/changes
bool m_isEnabled; | ||
}; | ||
|
||
struct Experiment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we make experiments part of the experimental features?
Experimental features already have most of the things we may want: settings to be turned on/off, GPO to disable them wholesale, an API to check the state, a command to show the current state. The only thing we would need to add is the option that they may be set remotely for an experiment if there is no local override.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we might still want a new GP for "allow experimental feature state to be changed remotely", but other than that it was my original thought that all experiments would likely have a feature. I forget the reasons why I softened on that, but I do think that there may be some value to separating them as they are slightly different semantics. One potential large reason is that if we use EFs exclusively, then a disabled GP for EFs also disables experiments. Moving experiments out to their own thing means an admin can disable in-development features while still allowing for experiments.
Typically, an EF is work-in-progress and will eventually be released. Using an experiment on an EF is more akin to controlled feature rollout. I think it will probably happen that we hook that up so that an EF can be controlled by an experiment and thus we get CFR.
On the other hand, a true experiment is typically a small configuration or code change that is itself not risky from a local functionality perspective (the impact, such as changing the CDN could have major impacts on the overall system of course). We might be testing something that we want to make permanent like where the DNS for the CDN points, or we might be checking to see if customer satisfaction numbers are higher if the default progress style is rainbow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as they are slightly different semantics
If users are going to come across the two concepts, do we think the difference is clear enough for them to understand?
an admin can disable in-development features while still allowing for experiments
Do we expect there will be admins wanting to do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want users to come across experiments really; the major reason for the control via settings is to enable us to test.
As to admins wanting to do that; I'm not sure. But if we are not going to build any user facing interfaces around them, then I see the minor additional cost as acceptable.
" is disabled due to group policy: " << TogglePolicy::GetPolicy(TogglePolicy::Policy::Experiments).RegValueName()); | ||
return { false, ExperimentToggleSource::Policy }; | ||
} | ||
|
||
auto experiments = userSettings.Get<Setting::Experiments>(); | ||
if (key == Experiment::Key::None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this back to first. While I'm not sure that we need a None
, if we have one then checks against it should not end up logging about group policy being disabled.
} | ||
|
||
auto experiments = userSettings.Get<Setting::Experiments>(); | ||
auto experiment = Experiment::GetExperiment(key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like you did this, but only to uses after the existing experiment
local was created. Move the local earlier so that it can be truly constructed once.
Experiment(std::string_view name, std::string_view jsonName, std::string_view link, std::string key) : | ||
m_name(name), m_jsonName(jsonName), m_link(link), m_key(key) {} | ||
Experiment(std::string name, std::string jsonName, std::string link, std::string key) : | ||
m_name(std::move(name)), m_jsonName(jsonName), m_link(std::move(link)), m_key(std::move((key))) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m_name(std::move(name)), m_jsonName(jsonName), m_link(std::move(link)), m_key(std::move((key))) {} | |
m_name(std::move(name)), m_jsonName(std::move(jsonName)), m_link(std::move(link)), m_key(std::move((key))) {} |
Accompanied by making m_jsonName
not a view.
|
||
static ExperimentState GetState(Key feature); | ||
static ExperimentState GetStateInternal(Key feature); | ||
static Experiment GetExperiment(Key key); | ||
static std::vector<Experiment> GetAllExperiments(); | ||
|
||
std::string_view Name() const { return m_name; } | ||
std::string Name() const { return m_name; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::string Name() const { return m_name; } | |
const std::string& Name() const { return m_name; } |
And the rest as const&
.
std::string GetKey() const { return m_key; } | ||
|
||
private: | ||
std::string_view m_name; | ||
std::string m_name; | ||
Utility::LocIndView m_jsonName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not need to be a localized value, does it?
Adding the client-side code for supporting running experiments in winget-cli.
Group policy:
User settings:
"experiments": Enable the user to control whether or not an experiment should run or not.
I have signed the Contributor License Agreement.
This pull request is related to an issue.
Microsoft Reviewers: Open in CodeFlow