Skip to content
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

Merged
merged 10 commits into from
Jul 2, 2020
Merged

Feature toggle #460

merged 10 commits into from
Jul 2, 2020

Conversation

msftrubengu
Copy link
Contributor

@msftrubengu msftrubengu commented Jun 25, 2020

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

@@ -31,7 +32,7 @@ namespace AppInstaller::CLI
};

// Controls the visibility of the field.
enum class Visibility
enum class VisibilityArg
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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

@@ -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
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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

@@ -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()))
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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

@@ -199,16 +204,21 @@ namespace AppInstaller::CLI

if (hasArguments)
{
infoOut << Resource::String::AvailableArguments << std::endl;

bool atLeastOne = false;
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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


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;
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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.
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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";
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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

Copy link
Contributor Author

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)

Copy link
Contributor Author

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>
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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>
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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;
}
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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);
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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;
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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);
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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();
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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);
};
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 26, 2020

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

Copy link
Member

@JohnMcPMS JohnMcPMS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕐

@ghost ghost added the Needs-Author-Feedback Issue needs attention from issue or PR author label Jun 26, 2020
@ghost ghost removed the Needs-Author-Feedback Issue needs attention from issue or PR author label Jun 29, 2020
@msftrubengu msftrubengu marked this pull request as ready for review June 30, 2020 19:29
@msftrubengu msftrubengu requested a review from a team as a code owner June 30, 2020 19:29
@@ -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
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 30, 2020

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

Copy link
Contributor Author

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)
Copy link
Member

@JohnMcPMS JohnMcPMS Jun 30, 2020

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

@ranm-msft
Copy link
Contributor

ranm-msft commented Jul 2, 2020

}

add , at the end (like in line 18) #Resolved


Refers to: doc/Settings.md:37 in 2c407ae. [](commit_id = 2c407ae, deletion_comment = False)

doc/Settings.md Outdated
"experimentalCmd": true,
"experimentalArg": false,
}
Copy link
Contributor

@ranm-msft ranm-msft Jul 2, 2020

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>
Copy link
Contributor

@ranm-msft ranm-msft Jul 2, 2020

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

Copy link
Contributor

@ranm-msft ranm-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Copy link
Member

@JohnMcPMS JohnMcPMS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@msftrubengu msftrubengu merged commit 2750b58 into microsoft:master Jul 2, 2020
@msftrubengu msftrubengu deleted the featuretoggle branch November 15, 2022 00:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants