From d895ff9bc73764dc5982b3d0be48fe9e1c2072f0 Mon Sep 17 00:00:00 2001 From: Norby Baruani Date: Wed, 18 Oct 2023 09:59:17 +0200 Subject: [PATCH] refactor with TimestreamPayloadBuilder --- src/Builder/TimestreamPayloadBuilder.php | 127 ++++++++++++++++ tests/Feature/PayloadWriterFeatureTest.php | 28 ++-- tests/Unit/WriterUnitTest.php | 165 +++++++++++++++++++-- 3 files changed, 295 insertions(+), 25 deletions(-) create mode 100644 src/Builder/TimestreamPayloadBuilder.php diff --git a/src/Builder/TimestreamPayloadBuilder.php b/src/Builder/TimestreamPayloadBuilder.php new file mode 100644 index 0000000..620e418 --- /dev/null +++ b/src/Builder/TimestreamPayloadBuilder.php @@ -0,0 +1,127 @@ +measureName = $measureName; + + return $this; + } + + public function setMeasureValue(mixed $value): self + { + $this->measureValue = $value; + + return $this; + } + + public function setMeasureValueType(ValueTypeEnum $type): self + { + $this->measureValueType = $type; + + return $this; + } + + public function setVersion(int $version): self + { + $this->version = $version; + + return $this; + } + + public function setMultiMeasuresValues(string $name, mixed $value, ?ValueTypeEnum $type = null): self + { + $this->measureValues[] = [ + 'Name' => $name, + 'Value' => $value, + 'Type' => $type?->value ?? ValueTypeEnum::VARCHAR()->value, + ]; + + return $this; + } + + public function setDimensions(string $name, mixed $value): self + { + $this->dimensions[] = [ + 'Name' => $name, + 'Value' => $value, + ]; + + return $this; + } + + public function setTime(Carbon $carbon): self + { + $this->time = $carbon; + + return $this; + } + + private function getPreciseTime(Carbon $time): string + { + return (string) $time->getPreciseTimestamp(3); + } + + public function toRecords(): array + { + return [$this->toArray()]; + } + + public static function make(string $measureName): self + { + return new self($measureName); + } + + public function toArray(): array + { + $metric = [ + 'MeasureName' => $this->measureName, + 'MeasureValue' => (string) $this->measureValue, + ]; + + if ($this->time) { + $metric['Time'] = $this->getPreciseTime($this->time); + } + + if ($this->measureValueType) { + $metric['MeasureValueType'] = $this->measureValueType->value; + } + + if ($this->measureValues) { + $metric['MeasureValues'] = $this->measureValues; + $metric['MeasureValueType'] = 'MULTI'; + unset($metric['MeasureValue']); + } + + if ($this->dimensions) { + $metric['Dimensions'] = $this->dimensions; + } + + if ($this->version) { + $metric['Version'] = $this->version; + } + + return $metric; + } +} \ No newline at end of file diff --git a/tests/Feature/PayloadWriterFeatureTest.php b/tests/Feature/PayloadWriterFeatureTest.php index 0ff86ba..23b0ed2 100644 --- a/tests/Feature/PayloadWriterFeatureTest.php +++ b/tests/Feature/PayloadWriterFeatureTest.php @@ -4,25 +4,25 @@ use Illuminate\Support\Arr; use Illuminate\Support\Carbon; -use NorbyBaru\AwsTimestream\Builder\CommonPayloadBuilder; -use NorbyBaru\AwsTimestream\Builder\PayloadBuilder; -use NorbyBaru\AwsTimestream\Dto\TimestreamWriterDto; -use NorbyBaru\AwsTimestream\Enum\ValueTypeEnum; use NorbyBaru\AwsTimestream\Tests\TestCase; use NorbyBaru\AwsTimestream\TimestreamService; +use NorbyBaru\AwsTimestream\Enum\ValueTypeEnum; +use NorbyBaru\AwsTimestream\Dto\TimestreamWriterDto; +use NorbyBaru\AwsTimestream\Builder\CommonPayloadBuilder; +use NorbyBaru\AwsTimestream\Builder\TimestreamPayloadBuilder; class PayloadWriterFeatureTest extends TestCase { /** * Writing of Multi-measure attributes */ - public function test_it_should_ingest_multi_measure_records() + public function it_should_ingest_multi_measure_records() { $filePath = __DIR__ . "/../Fixtures/data/sample.csv"; $records = []; foreach ($this->readCSV($filePath) as $index => $row) { $data = explode(";", $row[0]); - $payload = PayloadBuilder::make(measureName: 'metric'); + $payload = TimestreamPayloadBuilder::make(measureName: 'metric'); $payload ->setDimensions(name: $data[0], value: $data[1]) @@ -37,7 +37,7 @@ public function test_it_should_ingest_multi_measure_records() $payload->setTime(Carbon::now()->subMilliseconds($index * 50)); $records = [ ...$records, - ...$payload->toArray(), + ...$payload->toRecords(), ]; if (count($records) === 100) { @@ -57,19 +57,19 @@ public function test_it_should_ingest_multi_measure_records() */ public function test_it_should_ingest_single_measure_record() { - $payload = PayloadBuilder::make(measureName: 'device') + $payload = TimestreamPayloadBuilder::make(measureName: 'device') ->setMeasureValue(value: $this->faker->randomDigit) ->setDimensions(name: "mac_address", value: $this->faker->macAddress) ->setDimensions(name: "ref", value: $this->faker->uuid) ->setTime(Carbon::now()); - $timestreamWriter = TimestreamWriterDto::make($payload->toArray())->forTable('test'); + $timestreamWriter = TimestreamWriterDto::make($payload->toRecords())->forTable('test'); /** @var TimestreamService */ $timestreamService = app(TimestreamService::class); $result = $timestreamService->write($timestreamWriter); - $this->assertAwsResults($result, count($payload->toArray())); + $this->assertAwsResults($result, count($payload->toRecords())); } /** @@ -78,14 +78,14 @@ public function test_it_should_ingest_single_measure_record() public function test_it_should_batch_ingest_data() { $payloads = [ - ...PayloadBuilder::make(measureName: 'cpu_usage') + ...TimestreamPayloadBuilder::make(measureName: 'cpu_usage') ->setMeasureValue(value: $this->faker->randomFloat(5, 1, 100)) ->setDimensions(name: "ref", value: $this->faker->uuid) - ->toArray(), - ...PayloadBuilder::make(measureName: 'memory_usage') + ->toRecords(), + ...TimestreamPayloadBuilder::make(measureName: 'memory_usage') ->setMeasureValue(value: $this->faker->randomFloat(5, 1, 100)) ->setDimensions(name: "ref", value: $this->faker->uuid) - ->toArray(), + ->toRecords(), ]; $common = CommonPayloadBuilder::make() diff --git a/tests/Unit/WriterUnitTest.php b/tests/Unit/WriterUnitTest.php index 8b5440b..9d1796f 100644 --- a/tests/Unit/WriterUnitTest.php +++ b/tests/Unit/WriterUnitTest.php @@ -3,10 +3,13 @@ namespace NorbyBaru\AwsTimestream\Tests\Unit; use Illuminate\Support\Carbon; -use NorbyBaru\AwsTimestream\Contract\PayloadBuilderContract; -use NorbyBaru\AwsTimestream\Dto\TimestreamWriterDto; use NorbyBaru\AwsTimestream\Tests\TestCase; use NorbyBaru\AwsTimestream\TimestreamBuilder; +use NorbyBaru\AwsTimestream\Enum\ValueTypeEnum; +use NorbyBaru\AwsTimestream\Dto\TimestreamWriterDto; +use NorbyBaru\AwsTimestream\Builder\CommonPayloadBuilder; +use NorbyBaru\AwsTimestream\Contract\PayloadBuilderContract; +use NorbyBaru\AwsTimestream\Builder\TimestreamPayloadBuilder; class WriterUnitTest extends TestCase { @@ -23,6 +26,15 @@ public function test_it_should_return_instance_of_payload_builder() ); $this->assertInstanceOf(PayloadBuilderContract::class, $payload); + + $payload = TimestreamPayloadBuilder::make(measureName: 'device') + ->setMeasureValue(value: $this->faker->randomDigit) + ->setMeasureValueType(type: ValueTypeEnum::DOUBLE()) + ->setTime(Carbon::now()) + ->setDimensions(name: 'mac_address', value: $this->faker->macAddress) + ->setDimensions(name: 'ref', value: $this->faker->uuid); + + $this->assertInstanceOf(TimestreamPayloadBuilder::class, $payload); } public function test_it_should_return_correct_payload_builder_structure() @@ -44,6 +56,22 @@ public function test_it_should_return_correct_payload_builder_structure() $this->assertArrayHasKey('MeasureValue', $payload[0]); $this->assertArrayHasKey('MeasureValueType', $payload[0]); $this->assertArrayHasKey('Time', $payload[0]); + + $payload = TimestreamPayloadBuilder::make(measureName: 'device') + ->setMeasureValue(value: $this->faker->randomDigit) + ->setMeasureValueType(type: ValueTypeEnum::DOUBLE()) + ->setTime(Carbon::now()) + ->setDimensions(name: 'mac_address', value: $this->faker->macAddress) + ->setDimensions(name: 'ref', value: $this->faker->uuid) + ->toRecords(); + + $this->assertIsArray($payload); + $this->assertCount(1, $payload); + $this->assertArrayHasKey('Dimensions', $payload[0]); + $this->assertArrayHasKey('MeasureName', $payload[0]); + $this->assertArrayHasKey('MeasureValue', $payload[0]); + $this->assertArrayHasKey('MeasureValueType', $payload[0]); + $this->assertArrayHasKey('Time', $payload[0]); } public function test_it_should_return_accurate_payload_values() @@ -63,6 +91,22 @@ public function test_it_should_return_accurate_payload_values() $this->assertEquals('DOUBLE', $payload[0]['MeasureValueType']); $this->assertCount(count($metrics['dimensions']), $payload[0]['Dimensions']); $this->assertEquals($metrics['time']->getPreciseTimestamp(3), $payload[0]['Time']); + + $measureValue = $this->faker->randomDigit; + $now = Carbon::now(); + $payload = TimestreamPayloadBuilder::make(measureName: 'device') + ->setMeasureValue(value: $measureValue) + ->setMeasureValueType(type: ValueTypeEnum::DOUBLE()) + ->setTime($now) + ->setDimensions(name: 'mac_address', value: $this->faker->macAddress) + ->setDimensions(name: 'ref', value: $this->faker->uuid) + ->toRecords(); + + $this->assertEquals('device', $payload[0]['MeasureName']); + $this->assertEquals($measureValue, $payload[0]['MeasureValue']); + $this->assertEquals(ValueTypeEnum::DOUBLE()->value, $payload[0]['MeasureValueType']); + $this->assertCount(2, $payload[0]['Dimensions']); + $this->assertEquals($now->getPreciseTimestamp(3), $payload[0]['Time']); } public function test_it_should_return_correct_writer_dto_structure() @@ -84,6 +128,22 @@ public function test_it_should_return_correct_writer_dto_structure() $this->assertArrayHasKey('DatabaseName', $payload); $this->assertArrayHasKey('Records', $payload); $this->assertArrayHasKey('TableName', $payload); + + $payload = TimestreamPayloadBuilder::make(measureName: 'device') + ->setMeasureValue(value: $this->faker->randomDigit) + ->setMeasureValueType(type: ValueTypeEnum::DOUBLE()) + ->setTime(Carbon::now()) + ->setDimensions(name: 'mac_address', value: $this->faker->macAddress) + ->setDimensions(name: 'ref', value: $this->faker->uuid) + ->toRecords(); + + $timestreamWriter = TimestreamWriterDto::make($payload)->forTable('test'); + $this->assertInstanceOf(TimestreamWriterDto::class, $timestreamWriter); + + $payload = $timestreamWriter->toArray(); + $this->assertArrayHasKey('DatabaseName', $payload); + $this->assertArrayHasKey('Records', $payload); + $this->assertArrayHasKey('TableName', $payload); } public function test_it_should_return_correct_data_for_batch_ingestion() @@ -121,6 +181,32 @@ public function test_it_should_return_correct_data_for_batch_ingestion() $this->assertCount(count($metrics[$index]['dimensions']), $data['Dimensions']); $this->assertEquals($metrics[$index]['time']->getPreciseTimestamp(3), $data['Time']); } + + $payloads = [ + ...TimestreamPayloadBuilder::make(measureName: 'cpu_usage') + ->setMeasureValue(value: $this->faker->randomFloat(5, 1, 100)) + ->setMeasureValueType(type: ValueTypeEnum::DOUBLE()) + ->setDimensions(name: "ref", value: $this->faker->uuid) + ->setTime(Carbon::now()) + ->toRecords(), + ...TimestreamPayloadBuilder::make(measureName: 'memory_usage') + ->setMeasureValue(value: $this->faker->randomFloat(5, 1, 100)) + ->setMeasureValueType(type: ValueTypeEnum::DOUBLE()) + ->setDimensions(name: "ref", value: $this->faker->uuid) + ->setTime(Carbon::now()) + ->toRecords(), + ]; + + $this->assertIsArray($payloads); + $this->assertCount(2, $payloads); + + foreach ($payloads as $index => $data) { + $this->assertEquals($payloads[$index]['MeasureName'], $data['MeasureName']); + $this->assertEquals($payloads[$index]['MeasureValue'], $data['MeasureValue']); + $this->assertEquals($payloads[$index]['MeasureValueType'], $data['MeasureValueType']); + $this->assertCount(count($payloads[$index]['Dimensions']), $data['Dimensions']); + $this->assertEquals($payloads[$index]['Time'], $data['Time']); + } } public function test_it_should_return_correct_dto_structure_for_batch_ingestion_data() @@ -153,6 +239,30 @@ public function test_it_should_return_correct_dto_structure_for_batch_ingestion_ $this->assertArrayHasKey('DatabaseName', $payload); $this->assertArrayHasKey('Records', $payload); $this->assertArrayHasKey('TableName', $payload); + + + $payload = [ + ...TimestreamPayloadBuilder::make(measureName: 'cpu_usage') + ->setMeasureValue(value: $this->faker->randomFloat(5, 1, 100)) + ->setMeasureValueType(type: ValueTypeEnum::DOUBLE()) + ->setDimensions(name: "ref", value: $this->faker->uuid) + ->setTime(Carbon::now()) + ->toRecords(), + ...TimestreamPayloadBuilder::make(measureName: 'memory_usage') + ->setMeasureValue(value: $this->faker->randomFloat(5, 1, 100)) + ->setMeasureValueType(type: ValueTypeEnum::DOUBLE()) + ->setDimensions(name: "ref", value: $this->faker->uuid) + ->setTime(Carbon::now()) + ->toRecords(), + ]; + + $timestreamWriter = TimestreamWriterDto::make($payload)->forTable('test'); + $this->assertInstanceOf(TimestreamWriterDto::class, $timestreamWriter); + + $payload = $timestreamWriter->toArray(); + $this->assertArrayHasKey('DatabaseName', $payload); + $this->assertArrayHasKey('Records', $payload); + $this->assertArrayHasKey('TableName', $payload); } public function test_it_should_include_common_attributes_data() @@ -193,19 +303,52 @@ public function test_it_should_include_common_attributes_data() $this->assertArrayHasKey('DimensionValueType', $firstCommon); $this->assertArrayHasKey('Name', $firstCommon); $this->assertArrayHasKey('Value', $firstCommon); + + $payload = [ + ...TimestreamPayloadBuilder::make(measureName: 'cpu_usage') + ->setMeasureValue(value: $this->faker->randomFloat(5, 1, 100)) + ->setDimensions(name: "ref", value: $this->faker->uuid) + ->toRecords(), + ...TimestreamPayloadBuilder::make(measureName: 'memory_usage') + ->setMeasureValue(value: $this->faker->randomFloat(5, 1, 100)) + ->setDimensions(name: "ref", value: $this->faker->uuid) + ->toRecords(), + ]; + + $common = CommonPayloadBuilder::make() + ->setCommonDimensions(name: 'processor', value: $this->faker->linuxProcessor) + ->setCommonDimensions(name: 'mac_address', value: $this->faker->macAddress) + ->setCommonMeasureValueType(ValueTypeEnum::DOUBLE()) + ->setCommonTime(Carbon::now()) + ->toArray(); + + $timestreamWriter = TimestreamWriterDto::make($payload, $common, 'test'); + $payload = $timestreamWriter->toArray(); + + $this->assertArrayHasKey('CommonAttributes', $payload); + $commonAttributes = $payload['CommonAttributes']; + + $this->assertArrayHasKey('MeasureValueType', $commonAttributes); + $this->assertArrayHasKey('Time', $commonAttributes); + $this->assertArrayHasKey('Dimensions', $commonAttributes); + $this->assertCount(count($common), $commonAttributes); + $this->assertCount(2, $commonAttributes['Dimensions']); + + $firstCommon = $commonAttributes['Dimensions'][0]; + $this->assertArrayHasKey('DimensionValueType', $firstCommon); + $this->assertArrayHasKey('Name', $firstCommon); + $this->assertArrayHasKey('Value', $firstCommon); } public function test_it_should_ingest_to_correct_database_name_and_table_name() { - $metrics = $this->generateMetrics(); - - $payload = TimestreamBuilder::payload( - $metrics['measure_name'], - $metrics['measure_value'], - $metrics['time'], - 'DOUBLE', - $metrics['dimensions'] - )->toArray(); + $payload = TimestreamPayloadBuilder::make(measureName: 'device') + ->setMeasureValue(value: $this->faker->randomDigit) + ->setMeasureValueType(type: ValueTypeEnum::DOUBLE()) + ->setTime(Carbon::now()) + ->setDimensions(name: 'mac_address', value: $this->faker->macAddress) + ->setDimensions(name: 'ref', value: $this->faker->uuid) + ->toRecords(); $alias = 'test'; $timestreamWriter = TimestreamWriterDto::make($payload)->forTable($alias);