Skip to content

Commit 59d8b8f

Browse files
authored
fix: Support new flag values (#137)
1 parent 4d6a6b0 commit 59d8b8f

File tree

15 files changed

+398
-25
lines changed

15 files changed

+398
-25
lines changed

src/LaunchDarkly/Impl/Events/EventFactory.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public function newEvalEvent(
4444
'kind' => 'feature',
4545
'creationDate' => Util::currentTimeUnixMillis(),
4646
'key' => $flag->getKey(),
47+
'samplingRatio' => $flag->getSamplingRatio(),
48+
'excludeFromSummaries' => $flag->getExcludeFromSummaries(),
4749
'context' => $context,
4850
'variation' => $detail->getVariationIndex(),
4951
'value' => $detail->getValue(),
@@ -75,6 +77,8 @@ public function newDefaultEvent(FeatureFlag $flag, LDContext $context, Evaluatio
7577
'kind' => 'feature',
7678
'creationDate' => Util::currentTimeUnixMillis(),
7779
'key' => $flag->getKey(),
80+
'samplingRatio' => $flag->getSamplingRatio(),
81+
'excludeFromSummaries' => $flag->getExcludeFromSummaries(),
7882
'context' => $context,
7983
'value' => $detail->getValue(),
8084
'default' => $detail->getValue(),
@@ -124,7 +128,7 @@ public function newIdentifyEvent(LDContext $context): array
124128
'context' => $context
125129
];
126130
}
127-
131+
128132
/**
129133
* @return mixed[]
130134
*/

src/LaunchDarkly/Impl/Events/EventProcessor.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace LaunchDarkly\Impl\Events;
66

7+
use LaunchDarkly\Impl\Util;
78
use LaunchDarkly\Integrations\Curl;
89
use LaunchDarkly\Subsystems\EventPublisher;
910

@@ -50,6 +51,11 @@ public function enqueue(array $event): bool
5051
return false;
5152
}
5253

54+
$samplingRatio = $event['samplingRatio'] ?? 1;
55+
if (is_int($samplingRatio) && !Util::sample($samplingRatio)) {
56+
return false;
57+
}
58+
5359
$this->_queue[] = $event;
5460

5561
return true;

src/LaunchDarkly/Impl/Events/EventSerializer.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,13 @@ private function filterEvent(array $e): array
5050
{
5151
$ret = [];
5252
foreach ($e as $key => $value) {
53-
if ($key == 'context') {
53+
if ($key == 'samplingRatio' && $value === 1) {
54+
// 1 is the default case, so we don't have to include it in the final output.
55+
continue;
56+
} elseif ($key == 'excludeFromSummaries' && $value === false) {
57+
// false is the default case, so we don't have to include it in the final output.
58+
continue;
59+
} elseif ($key == 'context') {
5460
$ret[$key] = $this->serializeContext($value);
5561
} else {
5662
$ret[$key] = $value;

src/LaunchDarkly/Impl/Model/FeatureFlag.php

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class FeatureFlag
3434
protected bool $_trackEventsFallthrough = false;
3535
protected ?int $_debugEventsUntilDate = null;
3636
protected bool $_clientSide = false;
37+
protected ?int $_samplingRatio = null;
38+
protected bool $_excludeFromSummaries = false;
39+
protected ?MigrationSettings $_migrationSettings = null;
3740

3841
// Note, trackEvents and debugEventsUntilDate are not used in EventProcessor, because
3942
// the PHP client doesn't do summary events. However, we need to capture them in case
@@ -55,7 +58,10 @@ public function __construct(
5558
bool $trackEvents,
5659
bool $trackEventsFallthrough,
5760
?int $debugEventsUntilDate,
58-
bool $clientSide
61+
bool $clientSide,
62+
?int $samplingRatio,
63+
bool $excludeFromSummaries,
64+
?MigrationSettings $migrationSettings,
5965
) {
6066
$this->_key = $key;
6167
$this->_version = $version;
@@ -73,6 +79,9 @@ public function __construct(
7379
$this->_trackEventsFallthrough = $trackEventsFallthrough;
7480
$this->_debugEventsUntilDate = $debugEventsUntilDate;
7581
$this->_clientSide = $clientSide;
82+
$this->_samplingRatio = $samplingRatio;
83+
$this->_excludeFromSummaries = $excludeFromSummaries;
84+
$this->_migrationSettings = $migrationSettings;
7685
}
7786

7887
/**
@@ -82,8 +91,14 @@ public function __construct(
8291
*/
8392
public static function getDecoder(): \Closure
8493
{
85-
return fn ($v) =>
86-
new FeatureFlag(
94+
return function ($v) {
95+
$migrationSettings = null;
96+
97+
if (is_array($v['migration'] ?? null)) {
98+
$migrationSettings = call_user_func(MigrationSettings::getDecoder(), $v['migration']);
99+
}
100+
101+
return new FeatureFlag(
87102
$v['key'],
88103
$v['version'],
89104
$v['on'],
@@ -99,8 +114,12 @@ public static function getDecoder(): \Closure
99114
!!($v['trackEvents'] ?? false),
100115
!!($v['trackEventsFallthrough'] ?? false),
101116
$v['debugEventsUntilDate'] ?? null,
102-
!!($v['clientSide'] ?? false)
117+
!!($v['clientSide'] ?? false),
118+
$v['samplingRatio'] ?? null,
119+
!!($v['excludeFromSummaries'] ?? false),
120+
$migrationSettings,
103121
);
122+
};
104123
}
105124

106125
public static function decode(array $v): self
@@ -192,4 +211,19 @@ public function getVersion(): int
192211
{
193212
return $this->_version;
194213
}
214+
215+
public function getSamplingRatio(): int
216+
{
217+
return $this->_samplingRatio ?? 1;
218+
}
219+
220+
public function getExcludeFromSummaries(): bool
221+
{
222+
return $this->_excludeFromSummaries;
223+
}
224+
225+
public function getMigrationSettings(): ?MigrationSettings
226+
{
227+
return $this->_migrationSettings;
228+
}
195229
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LaunchDarkly\Impl\Model;
6+
7+
/**
8+
* Internal data model class that describes a feature flag's migration settings.
9+
*
10+
* Application code should never need to reference the data model directly.
11+
*
12+
* @ignore
13+
* @internal
14+
*/
15+
class MigrationSettings
16+
{
17+
public function __construct(private readonly ?int $checkRatio = null)
18+
{
19+
}
20+
21+
public function getCheckRatio(): int
22+
{
23+
return $this->checkRatio ?? 1;
24+
}
25+
26+
public static function getDecoder(): \Closure
27+
{
28+
return fn (array $v) => new MigrationSettings($v['checkRatio'] ?? null);
29+
}
30+
}

src/LaunchDarkly/Impl/Util.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@
2121
*/
2222
class Util
2323
{
24+
public static function sample(int $ratio): bool
25+
{
26+
if ($ratio === 0) {
27+
return false;
28+
}
29+
30+
if ($ratio === 1) {
31+
return true;
32+
}
33+
34+
$rand = mt_rand() / mt_getrandmax();
35+
36+
return $rand < (1 / $ratio);
37+
}
38+
2439
public static function adjustBaseUri(string $uri): string
2540
{
2641
if (substr($uri, strlen($uri) - 1, 1) == '/') {

src/LaunchDarkly/Integrations/TestData/FlagBuilder.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class FlagBuilder
2323
protected array $_variations;
2424
protected ?int $_offVariation;
2525
protected ?int $_fallthroughVariation;
26+
protected ?MigrationSettingsBuilder $_migrationSettingsBuilder;
27+
protected ?int $_samplingRatio;
28+
protected bool $_excludeFromSummaries;
2629

2730
// In _targets, each key is a context kind, and the value is another associative array where the key is a
2831
// variation index and the value is an array of context keys.
@@ -39,6 +42,9 @@ public function __construct(string $key)
3942
$this->_fallthroughVariation = null;
4043
$this->_targets = [];
4144
$this->_rules = [];
45+
$this->_samplingRatio = null;
46+
$this->_excludeFromSummaries = false;
47+
$this->_migrationSettingsBuilder = null;
4248
}
4349

4450
/**
@@ -71,6 +77,9 @@ public function copy(): FlagBuilder
7177
$to->_targets[$k] = $v;
7278
}
7379
$to->_rules = $this->_rules;
80+
$to->_samplingRatio = $this->_samplingRatio;
81+
$to->_excludeFromSummaries = $this->_excludeFromSummaries;
82+
$to->_migrationSettingsBuilder = $this->_migrationSettingsBuilder;
7483

7584
return $to;
7685
}
@@ -138,6 +147,30 @@ public function fallthroughVariation(bool|int $variation): FlagBuilder
138147
return $this;
139148
}
140149

150+
public function migrationSettings(MigrationSettingsBuilder $builder): FlagBuilder
151+
{
152+
$this->_migrationSettingsBuilder = $builder;
153+
return $this;
154+
}
155+
156+
/**
157+
* Control the rate at which events from this flag will be sampled.
158+
*/
159+
public function samplingRatio(int $samplingRatio): FlagBuilder
160+
{
161+
$this->_samplingRatio = $samplingRatio;
162+
return $this;
163+
}
164+
165+
/**
166+
* Control whether or not this flag should should be included in flag summary counts.
167+
*/
168+
public function excludeFromSummaries(bool $excludeFromSummaries): FlagBuilder
169+
{
170+
$this->_excludeFromSummaries = $excludeFromSummaries;
171+
return $this;
172+
}
173+
141174
/**
142175
* Specifies the off variation for a boolean flag or index of variation.
143176
* This is the variation that is returned whenever targeting is off.
@@ -484,6 +517,11 @@ public function build(int $version): array
484517
$baseFlagObject['rules'][] = $rule->build($idx);
485518
}
486519

520+
$migrationSettings = $this->_migrationSettingsBuilder?->build() ?? [];
521+
if (!empty($migrationSettings)) {
522+
$baseFlagObject['migration'] = $migrationSettings;
523+
}
524+
487525
$baseFlagObject['deleted'] = false;
488526

489527
return $baseFlagObject;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LaunchDarkly\Integrations\TestData;
6+
7+
class MigrationSettingsBuilder
8+
{
9+
protected ?int $checkRatio = null;
10+
11+
public function setCheckRatio(int $checkRatio): MigrationSettingsBuilder
12+
{
13+
$this->checkRatio = $checkRatio;
14+
return $this;
15+
}
16+
17+
/**
18+
* Creates an associative array representation of the migration settings
19+
*
20+
* @return array the array representation of the migration settings
21+
*/
22+
public function build(): array
23+
{
24+
return [
25+
"checkRatio" => $this->checkRatio,
26+
];
27+
}
28+
}

src/LaunchDarkly/Migrations/Migrator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace LaunchDarkly\Migrations;
66

77
use LaunchDarkly\Impl\Migrations\Executor;
8+
use LaunchDarkly\Impl\Util;
89
use LaunchDarkly\LDClient;
910
use LaunchDarkly\LDContext;
1011

@@ -94,8 +95,7 @@ public function write(
9495

9596
private function readBoth(Executor $authoritative, Executor $nonauthoritative, OpTracker $tracker): OperationResult
9697
{
97-
// TODO(sc-219378): Add sampling to limit to 50% chance
98-
if ($this->executionOrder == ExecutionOrder::RANDOM) {
98+
if ($this->executionOrder == ExecutionOrder::RANDOM && Util::sample(2)) {
9999
$nonauthoritativeResult = $nonauthoritative->run();
100100
$authoritativeResult = $authoritative->run();
101101
} else {

src/LaunchDarkly/Migrations/OpTracker.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function __construct(
3838
private EvaluationDetail $detail,
3939
private Stage $default_stage
4040
) {
41-
$this->consistentRatio = 1; # TODO(sc-219378): This needs to get set from the flag once we support those fields
41+
$this->consistentRatio = $flag?->getMigrationSettings()?->getCheckRatio() ?? 1;
4242
}
4343

4444

@@ -77,7 +77,10 @@ public function invoked(Origin $origin): OpTracker
7777
*/
7878
public function consistent(callable $isConsistent): OpTracker
7979
{
80-
# TODO(sc-219378): Add sampling support
80+
if (!Util::sample($this->consistentRatio)) {
81+
return $this;
82+
}
83+
8184
try {
8285
$this->consistent = boolval($isConsistent());
8386
} catch (Exception $e) {
@@ -136,14 +139,15 @@ public function build(): array|string
136139

137140
$event = [
138141
'kind' => 'migration_op',
142+
'samplingRatio' => 1,
139143
'creationDate' => Util::currentTimeUnixMillis(),
140144
'contextKeys' => $this->context->getKeys(),
141145
'operation' => $this->operation->value,
142146
'evaluation' => [
143147
'key' => $this->key,
144148
'value' => $this->detail->getValue(),
145149
'default' => $this->default_stage->value,
146-
'reason' => $this->detail->getReason(),
150+
'reason' => $this->detail->getReason()->jsonSerialize(),
147151
],
148152

149153
'measurements' => [
@@ -156,6 +160,7 @@ public function build(): array|string
156160

157161
if ($this->flag) {
158162
$event['evaluation']['version'] = $this->flag->getVersion();
163+
$event['samplingRatio'] = $this->flag->getSamplingRatio();
159164
}
160165

161166
if ($this->detail->getVariationIndex() !== null) {

0 commit comments

Comments
 (0)