diff --git a/README.md b/README.md index 7199ada..814d120 100644 --- a/README.md +++ b/README.md @@ -48,17 +48,21 @@ in the table and renders the correct response for prometheus. The following metrics will be collected: -| Metric | Labels | TYPE | Help | -|:------------------------------------|:-----------------------|:------|:-----------------------------------------------------------------------------| -| magento_orders_count_total | status, store_code | gauge | All Magento Orders | -| magento_orders_amount_total | status, store_code | gauge | Total amount of all Magento Orders | -| magento_order_items_count_total | status, store_code | gauge | Total count of orderitems | -| magento_cms_block_count_total | store_code | gauge | Total count of available cms blocks | -| magento_cms_page_count_total | store_code | gauge | Total count of available cms pages | -| magento_customer_count_total | store_code | gauge | Total count of available customer | -| magento_cronjob_broken_count_total | | gauge | Broken CronJobs occur when when status is pending but execution_time is set. | -| magento_cronjob_count_total | status, job_code | gauge | Total count of available CronJob Count. | -| magento_indexer_backlog_count_total | isValid, title, status | gauge | Total count of backlog item in indexer. | +| Metric | Labels | TYPE | Help | +|:------------------------------------|:--------------------------------|:------|:-----------------------------------------------------------------------------| +| magento_orders_count_total | status, store_code | gauge | All Magento Orders | +| magento_orders_amount_total | status, store_code | gauge | Total amount of all Magento Orders | +| magento_order_items_count_total | status, store_code | gauge | Total count of orderitems | +| magento_cms_block_count_total | store_code | gauge | Total count of available cms blocks | +| magento_cms_page_count_total | store_code | gauge | Total count of available cms pages | +| magento_customer_count_total | store_code | gauge | Total count of available customer | +| magento_cronjob_broken_count_total | | gauge | Broken CronJobs occur when when status is pending but execution_time is set. | +| magento_cronjob_count_total | status, job_code | gauge | Total count of available CronJob Count. | +| magento_indexer_backlog_count_total | isValid, title, status | gauge | Total count of backlog item in indexer. | +| magento_shipments_count_total | source, store_code | counter| Count of Shipments created by store and source. | +| magento_catalog_category_count_total| status, menu_status, store_code | gauge | Count of Categories by store, status and menu status. | +| magento_store_count_total | status | gauge | Total count of Stores by status. | +| magento_website_count_total | | gauge | Total count websites. | ## Add you own Metric diff --git a/Test/Unit/Aggregator/Category/CategoryCountAggregatorTest.php b/Test/Unit/Aggregator/Category/CategoryCountAggregatorTest.php new file mode 100644 index 0000000..3bb5a02 --- /dev/null +++ b/Test/Unit/Aggregator/Category/CategoryCountAggregatorTest.php @@ -0,0 +1,275 @@ +updateMetricService = $this->createMock(UpdateMetricService::class); + $this->resourceConnection = $this->createMock(ResourceConnection::class); + $this->expressionFactory = $this->createMock(ExpressionFactory::class); + $this->metadataPool = $this->createMock(MetadataPool::class); + + $this->subject = new CategoryCountAggregator( + $this->updateMetricService, + $this->resourceConnection, + $this->metadataPool, + $this->expressionFactory + ); + } + + private function getStatisticData(): array + { + return [ + [ + 'STORE_CODE' => 'default', + 'ACTIVE_IN_MENU' => 50, + 'ACTIVE_NOT_IN_MENU' => 10, + 'NOT_ACTIVE_IN_MENU' => 25, + 'NOT_ACTIVE_NOT_IN_MENU' => 15 + + ], + [ + 'STORE_CODE' => 'base', + 'ACTIVE_IN_MENU' => 18, + 'ACTIVE_NOT_IN_MENU' => 3, + 'NOT_ACTIVE_IN_MENU' => 4, + 'NOT_ACTIVE_NOT_IN_MENU' => 1 + + ], + [ + 'STORE_CODE' => 'eu', + 'ACTIVE_IN_MENU' => 79, + 'ACTIVE_NOT_IN_MENU' => 21, + 'NOT_ACTIVE_IN_MENU' => 15, + 'NOT_ACTIVE_NOT_IN_MENU' => 16 + + ], + ]; + } + + private function getSelectMock(): MockObject + { + $select = $this->createMock(Select::class); + + $select->expects($this->exactly(3))->method('from') + ->withConsecutive([self::TABLE_ATT], [self::TABLE_ATT], [["sg" => self::TABLE_STORE_GROUP]])->willReturn($select); + + $select->expects($this->exactly(4))->method('where') + ->withConsecutive( + ['entity_type_id = ?', 3], + ['attribute_code = ?', 'is_active'], + ['entity_type_id = ?', 3], + ['attribute_code = ?', 'include_in_menu'], + )->willReturn($select); + $select->expects($this->exactly(3)) + ->method('reset') + ->with(Select::COLUMNS) + ->willReturn($select); + + $select->expects($this->exactly(3))->method('joinInner') + ->withConsecutive( + [ + ['s' => self::TABLE_STORE], + 'sg.group_id = s.group_id' + ], + [ + ['cce1' => self::TABLE_CAT_ENT], + 'sg.root_category_id = cce1.entity_id' + ], + [ + ['cce2' => self::TABLE_CAT_ENT], + "cce2.path like CONCAT(cce1.path, '%')" + ] + )->willReturn($select); + + $select->expects($this->exactly(4))->method('joinLeft') + ->withConsecutive( + [ + ['ccei1' => self::TABLE_CAT_ENT_INT], + sprintf( + "cce2.%s = ccei1.%s AND ccei1.attribute_id = %s AND ccei1.store_id = s.store_id", + self::LINK_FIELD, + self::LINK_FIELD, + self::ATTR_ID + ) + ], + [ + ['ccei2' => self::TABLE_CAT_ENT_INT], + sprintf( + "cce2.%s = ccei2.%s AND ccei2.attribute_id = %s AND ccei2.store_id = 0", + self::LINK_FIELD, + self::LINK_FIELD, + self::ATTR_ID + ) + ], + [ + ['ccei3' => self::TABLE_CAT_ENT_INT], + sprintf( + "cce2.%s = ccei3.%s AND ccei3.attribute_id = %s AND ccei3.store_id = s.store_id", + self::LINK_FIELD, + self::LINK_FIELD, + self::ATTR_ID + ) + ], + [ + ['ccei4' => self::TABLE_CAT_ENT_INT], + sprintf( + "cce2.%s = ccei4.%s AND ccei4.attribute_id = %s AND ccei4.store_id = 0", + self::LINK_FIELD, + self::LINK_FIELD, + self::ATTR_ID + ) + ] + )->willReturn($select); + + $expressionMock = $this->createMock(Expression::class); + $this->expressionFactory->expects($this->exactly(4)) + ->method('create') + ->willReturnMap($this->getExpressionsMap($expressionMock)); + $select->expects($this->exactly(3))->method('columns') + ->withConsecutive( + [ + ['attribute_id'] + ], + [ + ['attribute_id'] + ], + [ + [ + 'STORE_CODE' => 's.code', + 'ACTIVE_IN_MENU' => $expressionMock, + 'ACTIVE_NOT_IN_MENU' => $expressionMock, + 'NOT_ACTIVE_IN_MENU' => $expressionMock, + 'NOT_ACTIVE_NOT_IN_MENU' => $expressionMock + ] + ] + )->willReturn($select); + + $select->expects($this->once())->method('group')->with('s.code'); + + return $select; + } + + private function getExpressionsMap(Expression $expressionMock): array + { + $expression = 'COUNT(( + %s IF(ccei1.value IS NULL, ccei2.value, ccei1.value) AND + %s IF(ccei3.value IS NULL, ccei4.value, ccei3.value) + ) or null)'; + + return [ + [['expression' => sprintf($expression, '', '')], $expressionMock], + [['expression' => sprintf($expression, '', 'NOT')], $expressionMock], + [['expression' => sprintf($expression, 'NOT', '')], $expressionMock], + [['expression' => sprintf($expression, 'NOT', 'NOT')], $expressionMock], + ]; + } + + private function getTableNamesMap(): array + { + return [ + ['eav_attribute', self::TABLE_ATT], + ['catalog_category_entity_int', self::TABLE_CAT_ENT_INT], + ['catalog_category_entity', self::TABLE_CAT_ENT], + ['store', self::TABLE_STORE], + ['store_group', self::TABLE_STORE_GROUP], + ]; + } + + public function testAggregate(): void + { + $connection = $this->createMock(AdapterInterface::class); + $statisticData = $this->getStatisticData(); + $this->resourceConnection->expects($this->once())->method('getConnection')->willReturn($connection); + $connection->expects($this->exactly(10)) + ->method('getTableName') + ->willReturnMap($this->getTableNamesMap()); + $entityMetadata = $this->createMock(EntityMetadataInterface::class); + $entityMetadata->expects($this->once())->method('getLinkField')->willReturn(self::LINK_FIELD); + $this->metadataPool->expects($this->once())->method('getMetadata') + ->with(CategoryInterface::class)->willReturn($entityMetadata); + + $select = $this->getSelectMock(); + $connection->expects($this->exactly(3))->method('select')->willReturn($select); + $connection->expects($this->exactly(2))->method('fetchOne')->willReturn(self::ATTR_ID); + $connection->expects($this->once()) + ->method('fetchAll') + ->with($select) + ->willReturn($statisticData); + + $this->updateMetricService->expects($this->exactly(4 * count($statisticData))) + ->method('update') + ->withConsecutive(...$this->getUpdateMetricsArguments($statisticData)); + + $this->subject->aggregate(); + } + + private function getUpdateMetricsArguments(array $statisticData): array + { + $arguments = []; + + foreach ($statisticData as $datum) { + $label = ['store_code' => $datum['STORE_CODE']]; + $arguments[] = [ + self::METRIC_CODE, + $datum['ACTIVE_IN_MENU'], + array_merge(['status' => 'enabled', 'menu_status' => 'enabled'], $label) + ]; + $arguments[] = [ + self::METRIC_CODE, + $datum['ACTIVE_NOT_IN_MENU'], + array_merge(['status' => 'enabled', 'menu_status' => 'disabled'], $label) + ]; + $arguments[] = [ + self::METRIC_CODE, + $datum['NOT_ACTIVE_IN_MENU'], + array_merge(['status' => 'disabled', 'menu_status' => 'enabled'], $label) + ]; + $arguments[] = [ + self::METRIC_CODE, + $datum['NOT_ACTIVE_NOT_IN_MENU'], + array_merge(['status' => 'disabled', 'menu_status' => 'disabled'], $label) + ]; + } + + return $arguments; + } +} diff --git a/Test/Unit/Aggregator/Shipment/ShipmentCountAggregatorTest.php b/Test/Unit/Aggregator/Shipment/ShipmentCountAggregatorTest.php new file mode 100644 index 0000000..50eb373 --- /dev/null +++ b/Test/Unit/Aggregator/Shipment/ShipmentCountAggregatorTest.php @@ -0,0 +1,134 @@ +updateMetricService = $this->createMock(UpdateMetricService::class); + $this->resourceConnection = $this->createMock(ResourceConnection::class); + + $this->subject = new ShipmentCountAggregator( + $this->updateMetricService, + $this->resourceConnection + ); + } + + private function getStatisticData(): array + { + return [ + [ + 'SHIPMENT_COUNT' => 111, + 'STORE_CODE' => 'default', + 'SOURCE_CODE' => 'default' + ], + [ + 'SHIPMENT_COUNT' => 222, + 'STORE_CODE' => 'default', + 'SOURCE_CODE' => 'eu' + ], + [ + 'SHIPMENT_COUNT' => 333, + 'STORE_CODE' => 'nl', + 'SOURCE_CODE' => 'eu' + ] + ]; + } + + private function getSelectMock(): MockObject + { + $select = $this->createMock(Select::class); + $select->expects($this->once()) + ->method('from') + ->with(['ss' => self::TABLE_SHIP]) + ->willReturn($select); + + $select->expects($this->exactly(2)) + ->method('joinInner') + ->withConsecutive( + [ + ['iss' => self::TABLE_INV_SHIP], + 'ss.entity_id = iss.shipment_id', + ['source_code'] + ], + [ + ['s' => self::TABLE_STORE], + 'ss.store_id = s.store_id', + ['code'] + ] + )->willReturn($select); + $select->expects($this->once())->method('reset')->with(Select::COLUMNS)->willReturn($select); + $select->expects($this->once())->method('group')->with(['s.code', 'iss.source_code']); + $select->expects($this->once()) + ->method('columns') + ->with( + [ + 'SHIPMENT_COUNT' => 'COUNT(ss.entity_id)', + 'STORE_CODE' => 's.code', + 'SOURCE_CODE' => 'iss.source_code' + ] + )->willReturn($select); + + return $select; + } + + public function testAggregate(): void + { + $connection = $this->createMock(AdapterInterface::class); + $statisticData = $this->getStatisticData(); + $select = $this->getSelectMock(); + + $this->resourceConnection->expects($this->once()) + ->method('getConnection') + ->with('sales') + ->willReturn($connection); + $connection->expects($this->once())->method('select')->willReturn($select); + $connection->expects($this->once())->method('fetchAll')->with($select)->willReturn($statisticData); + $connection->expects($this->exactly(3)) + ->method('getTableName') + ->willReturnMap( + [ + ['sales_shipment', self::TABLE_SHIP], + ['inventory_shipment_source', self::TABLE_INV_SHIP], + ['store', self::TABLE_STORE] + ] + ); + + $params = []; + foreach ($statisticData as $datum) { + $params[] = [ + self::METRIC_CODE, + $datum['SHIPMENT_COUNT'], + ['source' => $datum['SOURCE_CODE'], 'store_code' => $datum['STORE_CODE']] + ]; + } + + $this->updateMetricService->expects($this->exactly(3)) + ->method('update') + ->withConsecutive(...$params); + + $this->subject->aggregate(); + } +} diff --git a/Test/Unit/Aggregator/Store/StoreCountAggregatorTest.php b/Test/Unit/Aggregator/Store/StoreCountAggregatorTest.php index 4e03271..4f4d2b5 100644 --- a/Test/Unit/Aggregator/Store/StoreCountAggregatorTest.php +++ b/Test/Unit/Aggregator/Store/StoreCountAggregatorTest.php @@ -4,6 +4,7 @@ namespace RunAsRoot\PrometheusExporter\Test\Unit\Aggregator\Module; +use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Api\StoreRepositoryInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -12,6 +13,8 @@ final class StoreCountAggregatorTest extends TestCase { + private const METRIC_CODE = 'magento_store_count_total'; + /** @var StoreCountAggregator */ private $sut; @@ -21,7 +24,7 @@ final class StoreCountAggregatorTest extends TestCase /** @var MockObject|StoreRepositoryInterface */ private $storeRepository; - protected function setUp() + protected function setUp(): void { $this->updateMetricService = $this->createMock(UpdateMetricService::class); $this->storeRepository = $this->createMock(StoreRepositoryInterface::class); @@ -34,15 +37,26 @@ protected function setUp() public function testAggregate(): void { + $store1 = $this->createMock(StoreInterface::class); + $store2 = $this->createMock(StoreInterface::class); + $store3 = $this->createMock(StoreInterface::class); + + $store1->expects($this->once())->method('getIsActive')->willReturn(1); + $store2->expects($this->once())->method('getIsActive')->willReturn(0); + $store3->expects($this->once())->method('getIsActive')->willReturn(1); + $this->storeRepository ->expects($this->once()) ->method('getList') - ->willReturn(['a', 'b', 'c']); + ->willReturn([$store1, $store2, $store3]); $this->updateMetricService - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('update') - ->with(...['magento_store_count_total', '3']); + ->withConsecutive( + [self::METRIC_CODE, '2', ['status' => 'enabled']], + [self::METRIC_CODE, '1', ['status' => 'disabled']], + ); $this->sut->aggregate(); } diff --git a/Test/Unit/Aggregator/Store/WebsiteCountAggregatorTest.php b/Test/Unit/Aggregator/Store/WebsiteCountAggregatorTest.php new file mode 100644 index 0000000..1f6bdf1 --- /dev/null +++ b/Test/Unit/Aggregator/Store/WebsiteCountAggregatorTest.php @@ -0,0 +1,50 @@ +updateMetricService = $this->createMock(UpdateMetricService::class); + $this->websiteRepository = $this->createMock(WebsiteRepositoryInterface::class); + + $this->sut = new WebsiteCountAggregator( + $this->updateMetricService, + $this->websiteRepository + ); + } + + public function testAggregate(): void + { + $this->websiteRepository + ->expects($this->once()) + ->method('getList') + ->willReturn(['a', 'b', 'c', 'd', '3']); + + $this->updateMetricService + ->expects($this->once()) + ->method('update') + ->with(self::METRIC_CODE, '5'); + + $this->sut->aggregate(); + } +} diff --git a/src/Aggregator/Category/CategoryCountAggregator.php b/src/Aggregator/Category/CategoryCountAggregator.php new file mode 100644 index 0000000..81ead54 --- /dev/null +++ b/src/Aggregator/Category/CategoryCountAggregator.php @@ -0,0 +1,196 @@ +updateMetricService = $updateMetricService; + $this->resourceConnection = $resourceConnection; + $this->metadataPool = $metadataPool; + $this->expressionFactory = $expressionFactory; + } + + public function getCode(): string + { + return self::METRIC_CODE; + } + + public function getHelp(): string + { + return 'Magento 2 Categories count by status and store code.'; + } + + public function getType(): string + { + return 'gauge'; + } + + public function aggregate(): bool + { + $connection = $this->resourceConnection->getConnection(); + + $data = $connection->fetchAll($this->getSelect($connection)); + + foreach ($data as $datum) { + $labels = ['store_code' => $datum['STORE_CODE']]; + + $this->updateMetrics( + (string)$datum['ACTIVE_IN_MENU'], + array_merge($labels, ['status' => 'enabled', 'menu_status' => 'enabled']) + ); + $this->updateMetrics( + (string)$datum['ACTIVE_NOT_IN_MENU'], + array_merge($labels, ['status' => 'enabled', 'menu_status' => 'disabled']) + ); + $this->updateMetrics( + (string)$datum['NOT_ACTIVE_IN_MENU'], + array_merge($labels, ['status' => 'disabled', 'menu_status' => 'enabled']) + ); + $this->updateMetrics( + (string)$datum['NOT_ACTIVE_NOT_IN_MENU'], + array_merge($labels, ['status' => 'disabled', 'menu_status' => 'disabled']) + ); + } + + return true; + } + + private function updateMetrics(string $count, array $labels): void + { + $this->updateMetricService->update(self::METRIC_CODE, $count, $labels); + } + + /** + * SQL example: + * select s.code, + * COUNT( (IF (ccei1.value IS NULL, ccei2.value, ccei1.value) AND IF (ccei3.value IS NULL, ccei4.value, + * ccei3.value)) or null) as active_in_menu, COUNT( (IF (ccei1.value IS NULL, ccei2.value, ccei1.value) AND NOT IF + * (ccei3.value IS NULL, ccei4.value, ccei3.value)) or null) as active_not_in_menu, COUNT( (NOT IF (ccei1.value IS + * NULL, ccei2.value, ccei1.value) AND IF (ccei3.value IS NULL, ccei4.value, ccei3.value)) or null) as + * disabled_in_menu, COUNT( (NOT IF (ccei1.value IS NULL, ccei2.value, ccei1.value) AND NOT IF (ccei3.value IS + * NULL, ccei4.value, ccei3.value)) or null) as disabled_not_in_menu from store_group sg inner join store s on + * s.group_id = sg.group_id inner join catalog_category_entity cce1 on sg.root_category_id = cce1.entity_id inner + * join catalog_category_entity cce2 on cce2.path like CONCAT(cce1.path, '%') left join catalog_category_entity_int + * ccei1 on ccei1.entity_id = cce2.entity_id and ccei1.attribute_id = 32 and ccei1.store_id = s.store_id left join + * catalog_category_entity_int ccei2 on ccei2.entity_id = cce2.entity_id and ccei2.attribute_id = 32 and + * ccei2.store_id = 0 left join catalog_category_entity_int ccei3 on ccei3.entity_id = cce2.entity_id and ccei3.attribute_id = 601 and ccei3.store_id = s.store_id left join catalog_category_entity_int ccei4 on ccei4.entity_id = cce2.entity_id and ccei4.attribute_id = 601 and ccei4.store_id = 0 group by s.code; + * + * + * @param AdapterInterface $connection + * + * @return Select + * @throws \Exception + */ + private function getSelect(AdapterInterface $connection): Select + { + $linkField = $this->metadataPool->getMetadata(CategoryInterface::class)->getLinkField(); + $isActive = $this->getIsActiveAttributeId($connection); + $isInMenu = $this->getIsInMenuAttributeId($connection); + $expression = 'COUNT(( + %s IF(ccei1.value IS NULL, ccei2.value, ccei1.value) AND + %s IF(ccei3.value IS NULL, ccei4.value, ccei3.value) + ) or null)'; + + $activeInMenu = $this->expressionFactory->create(['expression' => sprintf($expression, '', '')]); + $activeNotInMenu = $this->expressionFactory->create(['expression' => sprintf($expression, '', 'NOT')]); + $notActiveInMenu = $this->expressionFactory->create(['expression' => sprintf($expression, 'NOT', '')]); + $notActiveNotInMenu = $this->expressionFactory->create( + ['expression' => sprintf($expression, 'NOT', 'NOT')] + ); + + $select = $connection->select(); + + $select->from(['sg' => $connection->getTableName('store_group')]) + ->joinInner( + ['s' => $connection->getTableName('store')], + 'sg.group_id = s.group_id' + )->joinInner( + ['cce1' => $connection->getTableName('catalog_category_entity')], + 'sg.root_category_id = cce1.entity_id' + )->joinInner( + ['cce2' => $connection->getTableName('catalog_category_entity')], + "cce2.path like CONCAT(cce1.path, '%')" + )->joinLeft( + ['ccei1' => $connection->getTableName('catalog_category_entity_int')], + "cce2.$linkField = ccei1.$linkField AND " . + "ccei1.attribute_id = $isActive AND ccei1.store_id = s.store_id" + )->joinLeft( + ['ccei2' => $connection->getTableName('catalog_category_entity_int')], + "cce2.$linkField = ccei2.$linkField AND " . + "ccei2.attribute_id = $isActive AND ccei2.store_id = 0" + )->joinLeft( + ['ccei3' => $connection->getTableName('catalog_category_entity_int')], + "cce2.$linkField = ccei3.$linkField AND " . + "ccei3.attribute_id = $isInMenu AND ccei3.store_id = s.store_id" + )->joinLeft( + ['ccei4' => $connection->getTableName('catalog_category_entity_int')], + "cce2.$linkField = ccei4.$linkField AND " . + "ccei4.attribute_id = $isInMenu AND ccei4.store_id = 0" + )->reset(Select::COLUMNS)->columns( + [ + 'STORE_CODE' => 's.code', + 'ACTIVE_IN_MENU' => $activeInMenu, + 'ACTIVE_NOT_IN_MENU' => $activeNotInMenu, + 'NOT_ACTIVE_IN_MENU' => $notActiveInMenu, + 'NOT_ACTIVE_NOT_IN_MENU' => $notActiveNotInMenu + ] + )->group('s.code'); + + return $select; + } + + private function getIsActiveAttributeId(AdapterInterface $connection): int + { + return $this->getAttributeId($connection, 'is_active'); + } + + private function getIsInMenuAttributeId(AdapterInterface $connection): int + { + return $this->getAttributeId($connection, 'include_in_menu'); + } + + private function getAttributeId(AdapterInterface $connection, string $code): int + { + $select = $connection->select(); + + $select->from($connection->getTableName('eav_attribute')) + ->where('entity_type_id = ?', self::CATEGORY_ENTITY_ID) + ->where('attribute_code = ?', $code) + ->reset(Select::COLUMNS) + ->columns(['attribute_id']); + + return (int)$connection->fetchOne($select); + } +} diff --git a/src/Aggregator/Shipment/ShipmentCountAggregator.php b/src/Aggregator/Shipment/ShipmentCountAggregator.php new file mode 100644 index 0000000..2a5f7fa --- /dev/null +++ b/src/Aggregator/Shipment/ShipmentCountAggregator.php @@ -0,0 +1,88 @@ +updateMetricService = $updateMetricService; + $this->resourceConnection = $resourceConnection; + } + + public function getCode(): string + { + return self::METRIC_CODE; + } + + public function getHelp(): string + { + return 'Magento 2 Shipments count by store and source.'; + } + + public function getType(): string + { + return 'counter'; + } + + public function aggregate(): bool + { + $connection = $this->resourceConnection->getConnection('sales'); + + $statistic = $connection->fetchAll($this->getSelect($connection)); + + foreach ($statistic as $result) { + $count = $result['SHIPMENT_COUNT'] ?? 0; + $store = $result['STORE_CODE'] ?? ''; + $source = $result['SOURCE_CODE'] ?? ''; + + $labels = ['source' => $source, 'store_code' => $store]; + + $this->updateMetricService->update(self::METRIC_CODE, (string)$count, $labels); + } + + return true; + } + + private function getSelect(AdapterInterface $connection): Select + { + $select = $connection->select(); + + $select->from(['ss' => $connection->getTableName('sales_shipment')]) + ->joinInner( + ['iss' => $connection->getTableName('inventory_shipment_source')], + 'ss.entity_id = iss.shipment_id', + ['source_code'] + )->joinInner( + ['s' => $connection->getTableName('store')], + 'ss.store_id = s.store_id', + ['code'] + )->reset(Select::COLUMNS)->columns( + [ + 'SHIPMENT_COUNT' => 'COUNT(ss.entity_id)', + 'STORE_CODE' => 's.code', + 'SOURCE_CODE' => 'iss.source_code' + ] + )->group(['s.code', 'iss.source_code']); + + return $select; + } +} diff --git a/src/Aggregator/Store/StoreCountAggregator.php b/src/Aggregator/Store/StoreCountAggregator.php index 5af418b..8d2f849 100644 --- a/src/Aggregator/Store/StoreCountAggregator.php +++ b/src/Aggregator/Store/StoreCountAggregator.php @@ -12,9 +12,9 @@ class StoreCountAggregator implements MetricAggregatorInterface { private const METRIC_CODE = 'magento_store_count_total'; - private $updateMetricService; + private UpdateMetricService $updateMetricService; - private $storeRepository; + private StoreRepositoryInterface $storeRepository; public function __construct( UpdateMetricService $updateMetricService, @@ -31,7 +31,7 @@ public function getCode(): string public function getHelp(): string { - return 'Magento 2 Store Count'; + return 'Magento 2 Store Count by status.'; } public function getType(): string @@ -42,8 +42,17 @@ public function getType(): string public function aggregate(): bool { $storeList = $this->storeRepository->getList(); - $storeCount = (string)count($storeList); - return $this->updateMetricService->update(self::METRIC_CODE, $storeCount); + $active = 0; + $disabled = 0; + + foreach ($storeList as $store) { + $store->getIsActive() ? $active++ : $disabled++; + } + + $this->updateMetricService->update(self::METRIC_CODE, (string)$active, ['status' => 'enabled']); + $this->updateMetricService->update(self::METRIC_CODE, (string)$disabled, ['status' => 'disabled']); + + return true; } } diff --git a/src/Aggregator/Store/WebsiteCountAggregator.php b/src/Aggregator/Store/WebsiteCountAggregator.php new file mode 100644 index 0000000..2935b1c --- /dev/null +++ b/src/Aggregator/Store/WebsiteCountAggregator.php @@ -0,0 +1,54 @@ +updateMetricService = $updateMetricService; + $this->websiteRepository = $websiteRepository; + } + + public function getCode(): string + { + return self::METRIC_CODE; + } + + public function getHelp(): string + { + return 'Magento 2 Website Count.'; + } + + public function getType(): string + { + return 'gauge'; + } + + public function aggregate(): bool + { + $websiteList = $this->websiteRepository->getList(); + $websiteCount = (string)count($websiteList); + + return $this->updateMetricService->update(self::METRIC_CODE, $websiteCount); + } +} diff --git a/src/Console/Command/CollectMetricsCommand.php b/src/Console/Command/CollectMetricsCommand.php index e298f02..bfc4fad 100644 --- a/src/Console/Command/CollectMetricsCommand.php +++ b/src/Console/Command/CollectMetricsCommand.php @@ -4,6 +4,9 @@ namespace RunAsRoot\PrometheusExporter\Console\Command; +use Magento\Framework\App\Area; +use Magento\Framework\App\State; +use Magento\Framework\Exception\LocalizedException; use RunAsRoot\PrometheusExporter\Cron\AggregateMetricsCron; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -17,11 +20,20 @@ class CollectMetricsCommand extends Command private $aggregateMetricsCron; - public function __construct(AggregateMetricsCron $aggregateMetricsCron) - { + private State $state; + + /** + * @param AggregateMetricsCron $aggregateMetricsCron + * @param State $state + */ + public function __construct( + AggregateMetricsCron $aggregateMetricsCron, + State $state + ) { parent::__construct(); $this->aggregateMetricsCron = $aggregateMetricsCron; + $this->state = $state; } protected function configure() @@ -35,7 +47,18 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { + try { + $this->state->getAreaCode(); + } catch (LocalizedException $exception) { + $this->state->setAreaCode(Area::AREA_CRONTAB); + } + $onlyOption = $input->getOption('only'); - $output->write($this->aggregateMetricsCron->executeOnly($onlyOption), true); + + if ($onlyOption) { + $output->write($this->aggregateMetricsCron->executeOnly($onlyOption), true); + } else { + $this->aggregateMetricsCron->execute(); + } } } diff --git a/src/etc/di.xml b/src/etc/di.xml index 91ed18e..3664098 100644 --- a/src/etc/di.xml +++ b/src/etc/di.xml @@ -8,6 +8,9 @@ + + RunAsRoot\PrometheusExporter\Aggregator\Category\CategoryCountAggregator + RunAsRoot\PrometheusExporter\Aggregator\Cms\CmsBlockCountAggregator RunAsRoot\PrometheusExporter\Aggregator\Cms\CmsPagesCountAggregator @@ -36,6 +39,9 @@ RunAsRoot\PrometheusExporter\Aggregator\Order\OrderItemAmountAggregator RunAsRoot\PrometheusExporter\Aggregator\Order\OrderItemCountAggregator + + RunAsRoot\PrometheusExporter\Aggregator\Shipment\ShipmentCountAggregator + RunAsRoot\PrometheusExporter\Aggregator\Payment\ActivePaymentMethodsCountAggregator @@ -47,6 +53,7 @@ RunAsRoot\PrometheusExporter\Aggregator\Store\StoreCountAggregator + RunAsRoot\PrometheusExporter\Aggregator\Store\WebsiteCountAggregator RunAsRoot\PrometheusExporter\Aggregator\User\AdminUserCountAggregator