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 flags improvements #32923

Merged

Conversation

boherm
Copy link
Member

@boherm boherm commented Jun 16, 2023

Questions Answers
Branch? develop
Description? This is an improvement of feature flags => See the description below or related issue.
Type? improvement
Category? CO
BC breaks? no
Deprecations? no
How to test? See the description below. Running campaign https://github.com/jolelievre/ga.tests.ui.pr/actions/runs/5485295067
Fixed ticket? Fixes #32698
Related PRs
Sponsor company

What I have done?

  • First of all, I have add DotEnv support for admin and console entry point.
  • Now, we have some Handlers class aim to use Feature Flags:
    • Database Layer
    • Env Layer
    • Query Layer
    • DotEnv Layer
  • On top of that, we have a FeatureFlagManager aim to instanciate the used handler by the feature flags, in function of priority set in type column. exemple: If a feature flag has env,query,dotenv,db in type column, this class will check if it can be possible to get a value from environnement, if not, it check if we can retrieve value by query, if not it check if we can use dotenv file, and if not, we use DB (that can be always used!)
  • I added type display in New & experimental features page, and the handler used is in bold for each feature flags.
  • I added type display in prestashop:feature-flags list command, and the handler used is surrounded by brackets.
  • I added a interface called FeatureFlagStateCheckerInterface to retrieve feature flag states simply.

Database Layer (type: db)

  • It's the same behaviour than before!

DotEnv Layer (type dotenv)

  • We use .env files in function of current environnement (dev, prod, etc...).
  • Feature flags can be edited as well in the New & experimental features page, and the Handler will localise the .env file used to set this feature flag.
  • The variable name in dotenv files must be: PS_FF_{UPPERCASE FEATURE FLAG NAME}.

Env Layer (type env)

  • Cannot be editable!
  • The variable name in env must be: PS_FF_{UPPERCASE FEATURE FLAG NAME}.

Query Layer (type query)

  • Cannot be editable and only in ModeDev!
  • The variable name in query string must be: PS_FF_{UPPERCASE FEATURE FLAG NAME}.

How to test?

  • By default, on a fresh install, we pass all feature flags in db type only.
  • But, you can edit PREFIX_feature_flag table to add env,query,dotenv,db on the one you want.
  • If you want to test Env Layer =>
    • In vhost of your installation, you can add: SetEnv PS_FF_{FEATURE_FLAG_NAME} true.
    • See in New & experimental features page the value of this feature flag and the handler used!
  • If you want to test Query Layer =>
    • Add in your query string: PS_FF_{FEATURE_FLAG_NAME}=true.
    • See in New & experimental features page the value of this feature flag and the handler used!
  • If you want to test DotEnv Layer =>
    • You can create a .env file in project root, and add PS_FF_{FEATURE_FLAG_NAME}=true in it.
    • See in New & experimental features page the value of this feature flag and the handler used!
    • Disable this feature flag from admin page, and check if your .env file has changed.

@boherm boherm requested a review from a team as a code owner June 16, 2023 08:05
@prestonBot prestonBot added develop Branch Improvement Type: Improvement labels Jun 16, 2023
@boherm boherm force-pushed the #32698-feature-flags-improvements branch 3 times, most recently from 6740a71 to 4aab36d Compare June 16, 2023 10:32
Copy link
Contributor

@matthieu-rolland matthieu-rolland left a comment

Choose a reason for hiding this comment

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

looks good to me 👍

Copy link
Contributor

@M0rgan01 M0rgan01 left a comment

Choose a reason for hiding this comment

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

Nice improvements 👍

.gitignore Outdated Show resolved Hide resolved
src/Core/FeatureFlag/Handler/AbstractHandler.php Outdated Show resolved Hide resolved
src/Core/FeatureFlag/Handler/DbHandler.php Outdated Show resolved Hide resolved
src/Core/FeatureFlag/Handler/DotEnvHandler.php Outdated Show resolved Hide resolved
src/Core/FeatureFlag/Handler/EnvHandler.php Outdated Show resolved Hide resolved
src/Core/FeatureFlag/Handler/HandlerFactory.php Outdated Show resolved Hide resolved
src/Core/FeatureFlag/Handler/AbstractHandler.php Outdated Show resolved Hide resolved
src/PrestaShopBundle/Command/FeatureFlagCommand.php Outdated Show resolved Hide resolved
@boherm boherm force-pushed the #32698-feature-flags-improvements branch 2 times, most recently from 73fefbc to f320ce1 Compare June 23, 2023 15:38
@boherm boherm marked this pull request as ready for review June 23, 2023 15:39
@boherm boherm force-pushed the #32698-feature-flags-improvements branch from f320ce1 to a622bb4 Compare June 23, 2023 16:00
@boherm boherm closed this Jun 23, 2023
@boherm boherm reopened this Jun 23, 2023
@boherm boherm force-pushed the #32698-feature-flags-improvements branch 4 times, most recently from b616f07 to 7c01599 Compare June 23, 2023 16:51
@boherm boherm force-pushed the #32698-feature-flags-improvements branch from 7c01599 to 15ecae8 Compare June 23, 2023 17:17
@boherm boherm requested review from M0rgan01 and lartist June 23, 2023 17:46
@jolelievre jolelievre removed the QA ✔️ Status: check done, code approved label Jul 7, 2023
@kpodemski
Copy link
Contributor

What's going on there @jolelievre ? QA approved, no merge and further reviews? 🤔 Is it ok to merge it or not?

@jolelievre
Copy link
Contributor

@kpodemski not ready to be merged no, I performed the QA by dev but I didn't see there was only one approval And @lartist made relevant comments since then, so still in progress

Copy link
Contributor

@matks matks left a comment

Choose a reason for hiding this comment

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

👍

*/
public function isReadonly(): bool
{
// It's always editable via DB layer!
Copy link
Contributor

Choose a reason for hiding this comment

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

:trollface: what if database is read only

Comment on lines +124 to +136
if ($pathDotenv = $this->locateDotEnvFile($featureFlagName)) {
file_put_contents(
$pathDotenv,
preg_replace(
"/({$this->getVarName($featureFlagName)})=(.*)/",
"$1={$this->boolLabel($status)}",
file_get_contents($pathDotenv)
)
);

return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there is a few other PHP classes in prestashop that perform this operation "modify a key/value file row". Maybe this can be abstracted

private function setStatus(string $featureFlagName, bool $status): void
{
    $this->myAwesomeFileManager->modifyLineValue($this->getVarName($featureFlagName), $this->boolLabel($status));

https://github.com/PrestaShop/PrestaShop/blob/develop/src/Adapter/Debug/DebugMode.php#L191

@boherm boherm force-pushed the #32698-feature-flags-improvements branch 4 times, most recently from c479cca to 4aa6c6c Compare July 10, 2023 15:48
@@ -38,6 +39,7 @@
define('PS_ADMIN_DIR', _PS_ADMIN_DIR_);
}

//define('_PS_MODE_DEV_', false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
//define('_PS_MODE_DEV_', false);

/**
* Type consts
*/
public const TYPE_DEFAULT = 'env,query,dotenv,db';
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public const TYPE_DEFAULT = 'env,query,dotenv,db';
public const TYPE_DEFAULT = 'env,dotenv,db';

$filesToCheck = [".env.$env.local", ".env.$env", '.env'];

foreach ($filesToCheck as $file) {
$path = $this->configuration->get('_PS_ROOT_DIR_') . '/' . $file;
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be injected in the constructor and in the DI you can inject the const

Comment on lines +124 to +136
if ($pathDotenv = $this->locateDotEnvFile($featureFlagName)) {
file_put_contents(
$pathDotenv,
preg_replace(
"/({$this->getVarName($featureFlagName)})=(.*)/",
"$1={$this->boolLabel($status)}",
file_get_contents($pathDotenv)
)
);

return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a very specific code, it's not really a good practice in the first place so moving this into a dedicated util class would incite doing this in other places We don't want to encourage this kind of thing so keeping this only in this context seems safer

Here we allow it only because it can only impact .env variables related to feature flags, since it's based on a prefix it can't modify any other future variable

{
public function __construct(
private EnvironmentInterface $environment,
private ?Request $request = null
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private ?Request $request = null
private ?RequestStack $requestStack = null

You can then use $requestStack->getMainRequest() which can be null, so in this case this layer becomes unusabled

{
private const FEATURE_FLAG_TEST = 'feature_flag_test';
private const VAR_FEATURE_FLAG_TEST = 'PS_FF_FEATURE_FLAG_TEST';
private const DOTENV_PATH = _PS_ROOT_DIR_ . '/.env.unit.local';
Copy link
Contributor

Choose a reason for hiding this comment

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

You can inject another folder that is part of test resources

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private const DOTENV_PATH = _PS_ROOT_DIR_ . '/.env.unit.local';
private const DOTENV_PATH = _PS_ROOT_DIR_ . '/tests/Resources/env/.env.unit.local';

yield ['no'];
}

public function createLayer(): QueryLayer
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public function createLayer(): QueryLayer
private function createLayer(): QueryLayer

$this->resetEnv();
}

private function resetEnv(): void
Copy link
Contributor

Choose a reason for hiding this comment

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

Can be moved at the end of the file as well


private function modeDev(bool $status): void
{
static::$environment = new Environment($status, 'unit');
Copy link
Contributor

Choose a reason for hiding this comment

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

You should use some newly constructed mocks in each test functions instead of relying on static fields

@@ -57,6 +57,8 @@ public function testAssertFeatureFlagProperties()
$this->assertEquals('A.B.C', $featureFlag->getDescriptionDomain());
$this->assertEquals('a b c d', $featureFlag->getLabelWording());
$this->assertEquals('A.B.L', $featureFlag->getLabelDomain());
$this->assertEquals('env,query,dotenv,db', $featureFlag->getType());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
$this->assertEquals('env,query,dotenv,db', $featureFlag->getType());
$this->assertEquals('env,dotenv,db', $featureFlag->getType());

Copy link
Contributor

Choose a reason for hiding this comment

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

Unless you force the value in your test entity which is also fine here

private function resetEnv(): void
{
unset($_ENV[self::VAR_FEATURE_FLAG_TEST]);
$_ENV['SYMFONY_DOTENV_VARS'] = static::$save_dotenv_vars;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
$_ENV['SYMFONY_DOTENV_VARS'] = static::$save_dotenv_vars;
$_ENV['SYMFONY_DOTENV_VARS'] = static::$save_dotenv_vars;
unlink(self::DOTENV_PATH);

{
private const FEATURE_FLAG_TEST = 'feature_flag_test';
private const VAR_FEATURE_FLAG_TEST = 'PS_FF_FEATURE_FLAG_TEST';
private const DOTENV_PATH = _PS_ROOT_DIR_ . '/.env.unit.local';
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private const DOTENV_PATH = _PS_ROOT_DIR_ . '/.env.unit.local';
private const DOTENV_PATH = _PS_ROOT_DIR_ . '/tests/Resources/env/.env.unit.local';

.env Outdated
@@ -0,0 +1 @@
PS_FF_PRODUCT_PAGE_V2=true
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be empty (maybe add a comment to explain why this file is added following the Symfony convention, but we have no default values to force for now)

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm more infavor of using a .env.dist file though, it follows a convention that is widely used in many other projects so I don't really understand why we should use .env as our dist file
Especially since DotEnv does handle the .env.dist file as its default fallback

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok after comparing a few projects it seems like Symfony projects mostly follow the .env convention It feels kind of weird but I guess we'd better follow the framework conventions

@boherm boherm force-pushed the #32698-feature-flags-improvements branch from 4aa6c6c to 2895629 Compare July 10, 2023 17:08
@boherm boherm force-pushed the #32698-feature-flags-improvements branch from 2895629 to 21b71ab Compare July 10, 2023 17:10
Copy link
Contributor

@jolelievre jolelievre left a comment

Choose a reason for hiding this comment

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

Thanks @boherm

@jolelievre jolelievre added the QA ✔️ Status: check done, code approved label Jul 11, 2023
@jolelievre
Copy link
Contributor

Validated by QA by dev waiting for https://github.com/jolelievre/ga.tests.ui.pr/actions/runs/5519072374 before merge

@jolelievre jolelievre merged commit 2f9be0d into PrestaShop:develop Jul 11, 2023
20 checks passed
@boherm boherm deleted the #32698-feature-flags-improvements branch July 11, 2023 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
develop Branch Improvement Type: Improvement Key feature Notable feature to be highlighted Needs autoupgrade PR Needs documentation Needs an update of the developer documentation QA ✔️ Status: check done, code approved
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Improvement of Feature Flags
10 participants