From 3dee363a66e746568edc8530930f54f85af5b07f Mon Sep 17 00:00:00 2001 From: Quentin Devos <4972091+Okhoshi@users.noreply.github.com> Date: Fri, 21 Jul 2023 15:15:09 +0200 Subject: [PATCH 1/2] Create MetricsBucket interface to make MetricsSender usable externally Signed-off-by: Quentin Devos <4972091+Okhoshi@users.noreply.github.com> --- src/Metrics/DefaultMetricsBucket.php | 82 ++++++++++++++++++++++ src/Metrics/DefaultMetricsHandler.php | 12 ++-- src/Metrics/MetricsBucket.php | 78 +------------------- tests/Metrics/DefaultMetricsSenderTest.php | 6 +- tests/Metrics/MetricsBucketTest.php | 8 +-- 5 files changed, 97 insertions(+), 89 deletions(-) create mode 100755 src/Metrics/DefaultMetricsBucket.php mode change 100755 => 100644 src/Metrics/MetricsBucket.php diff --git a/src/Metrics/DefaultMetricsBucket.php b/src/Metrics/DefaultMetricsBucket.php new file mode 100755 index 00000000..7d3a0902 --- /dev/null +++ b/src/Metrics/DefaultMetricsBucket.php @@ -0,0 +1,82 @@ + + */ + private array $toggles = []; + + public function __construct( + private readonly DateTimeInterface $startDate, + private ?DateTimeInterface $endDate = null, + ) { + } + + public function addToggle(MetricsBucketToggle $toggle): self + { + $this->toggles[] = $toggle; + + return $this; + } + + public function getStartDate(): DateTimeInterface + { + return $this->startDate; + } + + public function setEndDate(?DateTimeInterface $endDate): self + { + $this->endDate = $endDate; + + return $this; + } + + /** + * @return array + */ + #[ArrayShape(['start' => 'string', 'stop' => 'string', 'toggles' => 'array'])] + public function jsonSerialize(): array + { + $togglesArray = []; + + if ($this->endDate === null) { + throw new LogicException('Cannot serialize incomplete bucket'); + } + + foreach ($this->toggles as $toggle) { + $featureName = $toggle->getFeature()->getName(); + if (!isset($togglesArray[$featureName])) { + $togglesArray[$featureName] = [ + 'yes' => 0, + 'no' => 0, + ]; + } + + $updateField = $toggle->isSuccess() ? 'yes' : 'no'; + ++$togglesArray[$featureName][$updateField]; + + if ($toggle->getVariant() !== null) { + $variant = $toggle->getVariant(); + $togglesArray[$featureName]['variants'][$variant->getName()] ??= 0; + ++$togglesArray[$featureName]['variants'][$variant->getName()]; + } + } + + return [ + 'start' => $this->startDate->format('c'), + 'stop' => $this->endDate->format('c'), + 'toggles' => $togglesArray, + ]; + } +} diff --git a/src/Metrics/DefaultMetricsHandler.php b/src/Metrics/DefaultMetricsHandler.php index cd4de9a6..4050f823 100755 --- a/src/Metrics/DefaultMetricsHandler.php +++ b/src/Metrics/DefaultMetricsHandler.php @@ -31,22 +31,22 @@ public function handleMetrics(Feature $feature, bool $successful, Variant $varia } } - private function getOrCreateBucket(): MetricsBucket + private function getOrCreateBucket(): DefaultMetricsBucket { $cache = $this->configuration->getCache(); $bucket = null; if ($cache->has(CacheKey::METRICS_BUCKET)) { $bucket = $cache->get(CacheKey::METRICS_BUCKET); - assert($bucket instanceof MetricsBucket || $bucket === null); + assert($bucket instanceof DefaultMetricsBucket || $bucket === null); } - $bucket ??= new MetricsBucket(new DateTimeImmutable()); + $bucket ??= new DefaultMetricsBucket(new DateTimeImmutable()); return $bucket; } - private function shouldSend(MetricsBucket $bucket): bool + private function shouldSend(DefaultMetricsBucket $bucket): bool { $bucketStartDate = $bucket->getStartDate(); $nowMilliseconds = (int) (microtime(true) * 1000); @@ -58,7 +58,7 @@ private function shouldSend(MetricsBucket $bucket): bool return $diff >= $this->configuration->getMetricsInterval(); } - private function send(MetricsBucket $bucket): void + private function send(DefaultMetricsBucket $bucket): void { $bucket->setEndDate(new DateTimeImmutable()); $this->metricsSender->sendMetrics($bucket); @@ -68,7 +68,7 @@ private function send(MetricsBucket $bucket): void } } - private function store(MetricsBucket $bucket): void + private function store(DefaultMetricsBucket $bucket): void { $cache = $this->configuration->getCache(); $cache->set(CacheKey::METRICS_BUCKET, $bucket); diff --git a/src/Metrics/MetricsBucket.php b/src/Metrics/MetricsBucket.php old mode 100755 new mode 100644 index 9706f0d5..93cb7b79 --- a/src/Metrics/MetricsBucket.php +++ b/src/Metrics/MetricsBucket.php @@ -2,81 +2,7 @@ namespace Unleash\Client\Metrics; -use DateTimeInterface; -use JetBrains\PhpStorm\ArrayShape; -use JsonSerializable; -use LogicException; - -/** - * @internal - */ -final class MetricsBucket implements JsonSerializable +interface MetricsBucket { - /** - * @var array - */ - private array $toggles = []; - - public function __construct( - private readonly DateTimeInterface $startDate, - private ?DateTimeInterface $endDate = null, - ) { - } - - public function addToggle(MetricsBucketToggle $toggle): self - { - $this->toggles[] = $toggle; - - return $this; - } - - public function getStartDate(): DateTimeInterface - { - return $this->startDate; - } - - public function setEndDate(?DateTimeInterface $endDate): MetricsBucket - { - $this->endDate = $endDate; - - return $this; - } - - /** - * @return array - */ - #[ArrayShape(['start' => 'string', 'stop' => 'string', 'toggles' => 'array'])] - public function jsonSerialize(): array - { - $togglesArray = []; - - if ($this->endDate === null) { - throw new LogicException('Cannot serialize incomplete bucket'); - } - - foreach ($this->toggles as $toggle) { - $featureName = $toggle->getFeature()->getName(); - if (!isset($togglesArray[$featureName])) { - $togglesArray[$featureName] = [ - 'yes' => 0, - 'no' => 0, - ]; - } - - $updateField = $toggle->isSuccess() ? 'yes' : 'no'; - ++$togglesArray[$featureName][$updateField]; - - if ($toggle->getVariant() !== null) { - $variant = $toggle->getVariant(); - $togglesArray[$featureName]['variants'][$variant->getName()] ??= 0; - ++$togglesArray[$featureName]['variants'][$variant->getName()]; - } - } - return [ - 'start' => $this->startDate->format('c'), - 'stop' => $this->endDate->format('c'), - 'toggles' => $togglesArray, - ]; - } -} +} \ No newline at end of file diff --git a/tests/Metrics/DefaultMetricsSenderTest.php b/tests/Metrics/DefaultMetricsSenderTest.php index 89fd09fa..7934ff39 100755 --- a/tests/Metrics/DefaultMetricsSenderTest.php +++ b/tests/Metrics/DefaultMetricsSenderTest.php @@ -9,7 +9,7 @@ use Unleash\Client\DTO\DefaultFeature; use Unleash\Client\DTO\DefaultVariant; use Unleash\Client\Metrics\DefaultMetricsSender; -use Unleash\Client\Metrics\MetricsBucket; +use Unleash\Client\Metrics\DefaultMetricsBucket; use Unleash\Client\Metrics\MetricsBucketToggle; use Unleash\Client\Tests\AbstractHttpClientTest; @@ -39,7 +39,7 @@ public function testSendMetrics() 'Authorization' => 'test', ]); - $bucket = new MetricsBucket(new DateTimeImmutable(), new DateTimeImmutable()); + $bucket = new DefaultMetricsBucket(new DateTimeImmutable(), new DateTimeImmutable()); $bucket ->addToggle(new MetricsBucketToggle(new DefaultFeature('test', true, []), true, null)); @@ -65,7 +65,7 @@ public function testSendMetrics() public function testSendMetricsFailure() { $this->pushResponse(new TransferException()); - $bucket = new MetricsBucket(new DateTimeImmutable(), new DateTimeImmutable()); + $bucket = new DefaultMetricsBucket(new DateTimeImmutable(), new DateTimeImmutable()); $this->instance->sendMetrics($bucket); } } diff --git a/tests/Metrics/MetricsBucketTest.php b/tests/Metrics/MetricsBucketTest.php index d1eb77da..180695bb 100755 --- a/tests/Metrics/MetricsBucketTest.php +++ b/tests/Metrics/MetricsBucketTest.php @@ -5,20 +5,20 @@ use DateTimeImmutable; use LogicException; use PHPUnit\Framework\TestCase; -use Unleash\Client\Metrics\MetricsBucket; +use Unleash\Client\Metrics\DefaultMetricsBucket; final class MetricsBucketTest extends TestCase { public function testJsonSerialize() { - $instance = new MetricsBucket(new DateTimeImmutable(), new DateTimeImmutable()); + $instance = new DefaultMetricsBucket(new DateTimeImmutable(), new DateTimeImmutable()); self::assertIsArray($instance->jsonSerialize()); - $instance = new MetricsBucket(new DateTimeImmutable()); + $instance = new DefaultMetricsBucket(new DateTimeImmutable()); $instance->setEndDate(new DateTimeImmutable()); self::assertIsArray($instance->jsonSerialize()); - $instance = new MetricsBucket(new DateTimeImmutable()); + $instance = new DefaultMetricsBucket(new DateTimeImmutable()); $this->expectException(LogicException::class); $instance->jsonSerialize(); } From 919f5502656bf86138069a5ba070851d703f78cf Mon Sep 17 00:00:00 2001 From: Quentin Devos <4972091+Okhoshi@users.noreply.github.com> Date: Sat, 22 Jul 2023 16:18:52 +0200 Subject: [PATCH 2/2] Introduce MetricsBucketToggle interface Signed-off-by: Quentin Devos <4972091+Okhoshi@users.noreply.github.com> --- src/Metrics/DefaultMetricsBucket.php | 13 +++++++-- src/Metrics/DefaultMetricsBucketToggle.php | 34 ++++++++++++++++++++++ src/Metrics/DefaultMetricsHandler.php | 2 +- src/Metrics/DefaultMetricsSender.php | 2 +- src/Metrics/MetricsBucket.php | 15 ++++++++-- src/Metrics/MetricsBucketToggle.php | 27 +++-------------- tests/Metrics/DefaultMetricsSenderTest.php | 8 ++--- 7 files changed, 68 insertions(+), 33 deletions(-) create mode 100755 src/Metrics/DefaultMetricsBucketToggle.php mode change 100755 => 100644 src/Metrics/MetricsBucketToggle.php diff --git a/src/Metrics/DefaultMetricsBucket.php b/src/Metrics/DefaultMetricsBucket.php index 7d3a0902..a23d7897 100755 --- a/src/Metrics/DefaultMetricsBucket.php +++ b/src/Metrics/DefaultMetricsBucket.php @@ -4,13 +4,12 @@ use DateTimeInterface; use JetBrains\PhpStorm\ArrayShape; -use JsonSerializable; use LogicException; /** * @internal */ -final class DefaultMetricsBucket implements MetricsBucket, JsonSerializable +final class DefaultMetricsBucket implements MetricsBucket { /** * @var array @@ -30,6 +29,11 @@ public function addToggle(MetricsBucketToggle $toggle): self return $this; } + public function setStartDate(DateTimeInterface $date): self + { + throw new LogicException('Start date cannot be modified'); + } + public function getStartDate(): DateTimeInterface { return $this->startDate; @@ -42,6 +46,11 @@ public function setEndDate(?DateTimeInterface $endDate): self return $this; } + public function getEndDate(): ?DateTimeInterface + { + return $this->endDate; + } + /** * @return array */ diff --git a/src/Metrics/DefaultMetricsBucketToggle.php b/src/Metrics/DefaultMetricsBucketToggle.php new file mode 100755 index 00000000..b9ab8047 --- /dev/null +++ b/src/Metrics/DefaultMetricsBucketToggle.php @@ -0,0 +1,34 @@ +feature; + } + + public function isSuccess(): bool + { + return $this->success; + } + + public function getVariant(): ?Variant + { + return $this->variant; + } +} diff --git a/src/Metrics/DefaultMetricsHandler.php b/src/Metrics/DefaultMetricsHandler.php index 4050f823..ed4ab0b9 100755 --- a/src/Metrics/DefaultMetricsHandler.php +++ b/src/Metrics/DefaultMetricsHandler.php @@ -23,7 +23,7 @@ public function handleMetrics(Feature $feature, bool $successful, Variant $varia } $bucket = $this->getOrCreateBucket(); - $bucket->addToggle(new MetricsBucketToggle($feature, $successful, $variant)); + $bucket->addToggle(new DefaultMetricsBucketToggle($feature, $successful, $variant)); if ($this->shouldSend($bucket)) { $this->send($bucket); } else { diff --git a/src/Metrics/DefaultMetricsSender.php b/src/Metrics/DefaultMetricsSender.php index 45f618a2..adcd8c55 100755 --- a/src/Metrics/DefaultMetricsSender.php +++ b/src/Metrics/DefaultMetricsSender.php @@ -29,7 +29,7 @@ public function sendMetrics(MetricsBucket $bucket): void ->withBody(new StringStream(json_encode([ 'appName' => $this->configuration->getAppName(), 'instanceId' => $this->configuration->getInstanceId(), - 'bucket' => $bucket->jsonSerialize(), + 'bucket' => $bucket, ], JSON_THROW_ON_ERROR))); foreach ($this->configuration->getHeaders() as $name => $value) { $request = $request->withHeader($name, $value); diff --git a/src/Metrics/MetricsBucket.php b/src/Metrics/MetricsBucket.php index 93cb7b79..d932e825 100644 --- a/src/Metrics/MetricsBucket.php +++ b/src/Metrics/MetricsBucket.php @@ -2,7 +2,18 @@ namespace Unleash\Client\Metrics; -interface MetricsBucket +use DateTimeInterface; +use JsonSerializable; + +interface MetricsBucket extends JsonSerializable { + public function getStartDate(): DateTimeInterface; + + public function setStartDate(DateTimeInterface $date): self; + + public function getEndDate(): ?DateTimeInterface; + + public function setEndDate(?DateTimeInterface $date): self; -} \ No newline at end of file + public function addToggle(MetricsBucketToggle $toggle): self; +} diff --git a/src/Metrics/MetricsBucketToggle.php b/src/Metrics/MetricsBucketToggle.php old mode 100755 new mode 100644 index d884b980..7f45d9d4 --- a/src/Metrics/MetricsBucketToggle.php +++ b/src/Metrics/MetricsBucketToggle.php @@ -5,30 +5,11 @@ use Unleash\Client\DTO\Feature; use Unleash\Client\DTO\Variant; -/** - * @internal - */ -final class MetricsBucketToggle +interface MetricsBucketToggle { - public function __construct( - private readonly Feature $feature, - private readonly bool $success, - private readonly ?Variant $variant = null, - ) { - } + public function getFeature(): Feature; - public function getFeature(): Feature - { - return $this->feature; - } + public function isSuccess(): bool; - public function isSuccess(): bool - { - return $this->success; - } - - public function getVariant(): ?Variant - { - return $this->variant; - } + public function getVariant(): ?Variant; } diff --git a/tests/Metrics/DefaultMetricsSenderTest.php b/tests/Metrics/DefaultMetricsSenderTest.php index 7934ff39..20f4ccb0 100755 --- a/tests/Metrics/DefaultMetricsSenderTest.php +++ b/tests/Metrics/DefaultMetricsSenderTest.php @@ -8,9 +8,9 @@ use Unleash\Client\Configuration\UnleashConfiguration; use Unleash\Client\DTO\DefaultFeature; use Unleash\Client\DTO\DefaultVariant; -use Unleash\Client\Metrics\DefaultMetricsSender; use Unleash\Client\Metrics\DefaultMetricsBucket; -use Unleash\Client\Metrics\MetricsBucketToggle; +use Unleash\Client\Metrics\DefaultMetricsBucketToggle; +use Unleash\Client\Metrics\DefaultMetricsSender; use Unleash\Client\Tests\AbstractHttpClientTest; final class DefaultMetricsSenderTest extends AbstractHttpClientTest @@ -41,7 +41,7 @@ public function testSendMetrics() $bucket = new DefaultMetricsBucket(new DateTimeImmutable(), new DateTimeImmutable()); $bucket - ->addToggle(new MetricsBucketToggle(new DefaultFeature('test', true, []), true, null)); + ->addToggle(new DefaultMetricsBucketToggle(new DefaultFeature('test', true, []), true, null)); $this->pushResponse([], 1, 202); $this->instance->sendMetrics($bucket); @@ -53,7 +53,7 @@ public function testSendMetrics() $this->configuration->setMetricsEnabled(true); $bucket - ->addToggle(new MetricsBucketToggle( + ->addToggle(new DefaultMetricsBucketToggle( new DefaultFeature('tet', true, []), true, new DefaultVariant('test', true)