-
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
Feature toggle #460
Feature toggle #460
Conversation
src/AppInstallerCLICore/Argument.h
Outdated
@@ -31,7 +32,7 @@ namespace AppInstaller::CLI | |||
}; | |||
|
|||
// Controls the visibility of the field. | |||
enum class Visibility | |||
enum class VisibilityArg |
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.
VisibilityArg [](start = 15, length = 13)
I would suggest moving it under Argument rather than just smooshing names together. #Closed
src/AppInstallerCLICore/Argument.cpp
Outdated
@@ -91,4 +94,14 @@ namespace AppInstaller::CLI | |||
args.push_back(ForType(Args::Type::RetroStyle)); | |||
args.push_back(ForType(Args::Type::VerboseLogs)); | |||
} | |||
|
|||
void Argument::OutputEnableMessage(Execution::Context& context) const |
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.
OutputEnableMessage [](start = 19, length = 19)
This should not be here, at least as a direct output function. If I'm guessing at its use, then it should generated a string to be part of the CommandException generated by parsing. #Closed
src/AppInstallerCLICore/Command.cpp
Outdated
@@ -162,8 +164,11 @@ namespace AppInstaller::CLI | |||
|
|||
for (const auto& command : commands) | |||
{ | |||
size_t fillChars = (maxCommandNameLength - command->Name().length()) + 2; | |||
infoOut << " "_liv << Execution::HelpCommandEmphasis << command->Name() << Utility::LocIndString{ std::string(fillChars, ' ') } << command->ShortDescription() << std::endl; | |||
if ((command->Visibility() == VisibilityCmd::Show) && User().isEnabled(command->Feature())) |
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.
if ((command->Visibility() == VisibilityCmd::Show) && User().isEnabled(command->Feature())) [](start = 16, length = 91)
Might be better to encode the feature check into Command::Visibility (and same for Argument::Visibility) #Closed
src/AppInstallerCLICore/Command.cpp
Outdated
@@ -199,16 +204,21 @@ namespace AppInstaller::CLI | |||
|
|||
if (hasArguments) | |||
{ | |||
infoOut << Resource::String::AvailableArguments << std::endl; | |||
|
|||
bool atLeastOne = false; |
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.
bool atLeastOne = false; [](start = 16, length = 24)
Shouldn't be needed if the above code takes Visibility into account properly (as it does not right now). #Closed
src/AppInstallerCLICore/Command.cpp
Outdated
|
||
auto featureInfo = UserSettings::GetFeatureInfo(m_feature); | ||
context.Reporter.Warn() << Resource::String::CommandDisabled << " " << m_name << std::endl; | ||
context.Reporter.Warn() << Resource::String::ExperimentalFeatureName << " " << std::get<0>(featureInfo) << std::endl; |
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.
context.Reporter.Warn() [](start = 8, length = 23)
No need to get a new output object, just use:
Warn() <<
stuff << endl <<
more stuff << endl;
Also, it probably is not a great idea to try and inject multiple strings into the middle of localized text like this. Much better to have a single localized string and value after it. Something like (and get PMs to weigh in too probably):
This command is a work in progress, and may be changed dramatically or removed altogether in the future. To enable it, edit your settings ('winget settings') to include the experimental feature: <feature name string>
``` #Closed
struct FeaturesCommand final : public Command | ||
{ | ||
// This command is used as an example on how experimental features can be used. | ||
// To enable this command set ExperimentalCmd = true in the settings file. |
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.
Copy pasta #Closed
|
||
std::string FeaturesCommand::HelpLink() const | ||
{ | ||
return "https://aka.ms/winget-seattings"; |
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.
https://aka.ms/winget-seattings [](start = 16, length = 31)
Incorrect copy pasta #Closed
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 was intentional. Are we saying we will have a special md file/microsoft docs page for this hidden command? I was thinking settings.md will have in the experimentalFeatures entry something like "to know which experimental features are available run winget features"
In reply to: 446445866 [](ancestors = 446445866)
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 guess the link can point to #experimentalFeatures to be clear
In reply to: 447183011 [](ancestors = 447183011,446445866)
</data> | ||
<data name="FeaturesMessage" xml:space="preserve"> | ||
<value>The following experimental features are in progress. | ||
They can be configured through the settings file (winget settings).</value> |
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.
winget settings [](start = 50, length = 15)
I think putting the ' ' quotes around it makes it look more like a command to run. #Closed
<value>If you want to try this feature, please go to winget settings and enable it. See</value> | ||
</data> | ||
<data name="FeaturesCommandLongDescription" xml:space="preserve"> | ||
<value>Shows the status of experimental features. Experimental features can be turn on via winget settings.</value> |
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.
winget settings [](start = 95, length = 15)
You will have to put a "lock" on this, see this elsewhere in the file:
{Locked="VirtualTerminal"} #Closed
{ | ||
// Better work hard to get some out there! | ||
context.Reporter.Info() << Resource::String::NoExperimentalFeaturesMessage << std::endl; | ||
} |
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.
Feels like everything should be inside this if/else #Closed
else | ||
{ | ||
// Here we warn the user that the argument exists and is not enabled. | ||
Argument::ForType(Execution::Args::Type::ExperimentalArg).OutputEnableMessage(context); |
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 type of thing should be done either at parsing or by Command::Execute. #Closed
template<> | ||
std::optional<bool> GetValue(const Json::Value& node) | ||
{ | ||
std::optional<bool> value = std::nullopt; |
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::nullopt [](start = 33, length = 15)
This is the default constructor's behavior already. #Closed
@@ -106,6 +157,8 @@ namespace AppInstaller::Settings | |||
} | |||
|
|||
static std::filesystem::path SettingsFilePath(); | |||
static std::tuple<std::string, std::string, std::string> GetFeatureInfo(ExperimentalFeature feature); |
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::tuple<std::string, std::string, std::string> [](start = 15, length = 49)
Don't use a tuple, take the time to create a struct with 3 names so that we can actually read the code. #Closed
@@ -106,6 +157,8 @@ namespace AppInstaller::Settings | |||
} | |||
|
|||
static std::filesystem::path SettingsFilePath(); | |||
static std::tuple<std::string, std::string, std::string> GetFeatureInfo(ExperimentalFeature feature); | |||
static std::vector<std::tuple<std::string, std::string, std::string>> GetFeaturesInfo(); |
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.
static std::vector<std::tuple<std::string, std::string, std::string>> GetFeaturesInfo(); [](start = 8, length = 88)
I don't really agree with tacking the features directly into the UserSettings. It is definitely a layer on top of them, and so it should be a separate type. #Closed
|
||
// Not needed | ||
// static std::optional<value_t> Validate(const json_t& value); | ||
}; |
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.
Special case sense is tingling...
And confirmed by looking at the cpp. These should be individual values, treated the same as any other. The features type should layer on top and handle the mapping from feature enum to setting enum. #Closed
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.
🕐
@@ -271,3 +271,44 @@ TEST_CASE("SettingAutoUpdateIntervalInMinutes", "[settings]") | |||
REQUIRE(userSettingTest.GetWarnings().size() == 1); | |||
} | |||
} | |||
|
|||
// Test one experimental feature in usersettingstest context because there's no good way to test ExperimentalFeature |
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.
ExperimentalFeature [](start = 97, length = 19)
I think that you could have tests, but you would either need your Reload function back, or a test hook for features that allows you to inject your own settings object to be the one it uses. #WontFix
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 kind of gave up after seeing all the amounts of changes i was doing plus me not wanting to mess with the constness of UserSettings::Instance
In reply to: 447942903 [](ancestors = 447942903)
{ | ||
// Commented out on purpose these are just to provide example on how experimental features work. | ||
// GetFeature(Feature::ExperimentalCmd), | ||
// GetFeature(Feature::ExperimentalArg) |
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.
You can do this using the make_integer_sequence and folding, even if you want to exclude some you can use a Max that isn't at the end (these test ones are after). #Pending
doc/Settings.md
Outdated
"experimentalCmd": true, | ||
"experimentalArg": false, | ||
} |
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.
} [](start = 3, length = 1)
add coma #Resolved
@@ -383,6 +412,9 @@ | |||
<data name="TagArgumentDescription" xml:space="preserve"> | |||
<value>Filter results by tag</value> | |||
</data> | |||
<data name="ThankYou" xml:space="preserve"> | |||
<value>Thank you from using winget</value> |
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.
from [](start = 21, length = 4)
for #Resolved
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 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.
Change
Implementation for #364
Experimental features enable allow features to be distributed to early adopters for their feedback without the consequences of affecting already released functionalities.
Developers
The first in the workflow of a developer when implementing a new feature is to add a new Feature in ExperimentalFeature.h and follow the instruction in the comment. For commands or arguments, the disable protection is already in place. If the feature requires additional checks use
ExperimentalFeatures::isEnabled(<your feature>)
.When a feature is ready to be release, the enum value must be removed and all the reference to it. By always using the feature enum value we guarantee a cleaner removal.
To provide guidance, winget includes two test experimental features.
Users
To enable an experimental feature, go to the settings file and enable it under the experimentalFeatures property. Settings.md must have information about all the existing experimental feature (there are currently none).
To see all the available features, a user can execute the hidden
winget features
and see the status, as well as a link to the spec for the feature.Validation
Implement new unittest and test locally the experimental test features
Microsoft Reviewers: Open in CodeFlow