diff --git a/app/Console/Commands/FeatureEnableCommand.php b/app/Console/Commands/FeatureEnableCommand.php index 8f07b108..7d721131 100644 --- a/app/Console/Commands/FeatureEnableCommand.php +++ b/app/Console/Commands/FeatureEnableCommand.php @@ -11,7 +11,7 @@ class FeatureEnableCommand extends Command { use ResolvesFeatures; - protected $signature = 'feature:enable {feature : The feature name (class name or string-based feature)} {target : User email or "all" for everyone}'; + protected $signature = 'feature:enable {feature : The feature name (class name or string-based feature)} {target : User email, "all" for everyone, or a percentage like "25%" for a random rollout}'; protected $description = 'Enable a feature for a specific user or all users'; @@ -36,6 +36,10 @@ class FeatureEnableCommand extends Command return self::SUCCESS; } + if (preg_match('/^(\d{1,3})%$/', $target, $matches)) { + return $this->enableForPercentage($featureClass, $featureName, (int) $matches[1]); + } + $user = User::where('email', $target)->first(); if (! $user) { @@ -49,4 +53,23 @@ class FeatureEnableCommand extends Command return self::SUCCESS; } + + private function enableForPercentage(string $featureClass, string $featureName, int $percentage): int + { + if ($percentage < 1 || $percentage > 100) { + $this->error('Percentage must be between 1 and 100.'); + + return self::FAILURE; + } + + $count = (int) ceil(User::count() * $percentage / 100); + + User::inRandomOrder() + ->take($count) + ->each(fn (User $user) => Feature::for($user)->activate($featureClass)); + + $this->info("Feature '{$featureName}' enabled for {$count} users (~{$percentage}% of current users)."); + + return self::SUCCESS; + } } diff --git a/tests/Feature/Console/FeatureEnableCommandTest.php b/tests/Feature/Console/FeatureEnableCommandTest.php new file mode 100644 index 00000000..7b1f6264 --- /dev/null +++ b/tests/Feature/Console/FeatureEnableCommandTest.php @@ -0,0 +1,24 @@ +count(10)->create(); + + $this->artisan('feature:enable', ['feature' => 'AiConsentSettings', 'target' => '40%']) + ->expectsOutputToContain('enabled for 4 users') + ->assertSuccessful(); + + $enabled = $users->filter(fn (User $user) => Feature::for($user)->active(AiConsentSettings::class)); + + expect($enabled)->toHaveCount(4); +}); + +test('rejects an out-of-range percentage', function () { + User::factory()->create(); + + $this->artisan('feature:enable', ['feature' => 'AiConsentSettings', 'target' => '0%']) + ->assertFailed(); +});