diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php index 79f69ab5da8..6b5e0681139 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php @@ -28,11 +28,11 @@ public function execute() )->markAsRead( $notificationId ); - $this->messageManager->addSuccess(__('The message has been marked as Read.')); + $this->messageManager->addSuccessMessage(__('The message has been marked as Read.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __("We couldn't mark the notification as Read because of an error.") ); diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php index 9e61b8ff4b8..9ae4a7cdac0 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php @@ -23,7 +23,7 @@ public function execute() { $ids = $this->getRequest()->getParam('notification'); if (!is_array($ids)) { - $this->messageManager->addError(__('Please select messages.')); + $this->messageManager->addErrorMessage(__('Please select messages.')); } else { try { foreach ($ids as $id) { @@ -32,13 +32,13 @@ public function execute() $model->setIsRead(1)->save(); } } - $this->messageManager->addSuccess( + $this->messageManager->addSuccessMessage( __('A total of %1 record(s) have been marked as Read.', count($ids)) ); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __("We couldn't mark the notification as Read because of an error.") ); diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php index 94c7d955f59..06659b8452c 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php @@ -23,7 +23,7 @@ public function execute() { $ids = $this->getRequest()->getParam('notification'); if (!is_array($ids)) { - $this->messageManager->addError(__('Please select messages.')); + $this->messageManager->addErrorMessage(__('Please select messages.')); } else { try { foreach ($ids as $id) { @@ -32,11 +32,14 @@ public function execute() $model->setIsRemove(1)->save(); } } - $this->messageManager->addSuccess(__('Total of %1 record(s) have been removed.', count($ids))); + $this->messageManager->addSuccessMessage(__('Total of %1 record(s) have been removed.', count($ids))); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __("We couldn't remove the messages because of an error.")); + $this->messageManager->addExceptionMessage( + $e, + __("We couldn't remove the messages because of an error.") + ); } } $this->_redirect('adminhtml/*/'); diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php index 17f911339cb..f0724a9587c 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php @@ -31,11 +31,14 @@ public function execute() try { $model->setIsRemove(1)->save(); - $this->messageManager->addSuccess(__('The message has been removed.')); + $this->messageManager->addSuccessMessage(__('The message has been removed.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __("We couldn't remove the messages because of an error.")); + $this->messageManager->addExceptionMessage( + $e, + __("We couldn't remove the messages because of an error.") + ); } $this->_redirect('adminhtml/*/'); diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php index 7ddd5e3bb2a..9047cc6481e 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricing.php @@ -5,6 +5,7 @@ */ namespace Magento\AdvancedPricingImportExport\Model\Export; +use Magento\ImportExport\Model\Export; use Magento\Store\Model\Store; use Magento\CatalogImportExport\Model\Import\Product as ImportProduct; use Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing as ImportAdvancedPricing; @@ -79,6 +80,11 @@ class AdvancedPricing extends \Magento\CatalogImportExport\Model\Export\Product ImportAdvancedPricing::COL_TIER_PRICE_TYPE => '' ]; + /** + * @var string[] + */ + private $websiteCodesMap = []; + /** * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\Eav\Model\Config $config @@ -255,36 +261,131 @@ public function filterAttributeCollection(\Magento\Eav\Model\ResourceModel\Entit */ protected function getExportData() { + if ($this->_passTierPrice) { + return []; + } + $exportData = []; try { - $rawData = $this->collectRawData(); - $productIds = array_keys($rawData); - if (isset($productIds)) { - if (!$this->_passTierPrice) { - $exportData = array_merge( - $exportData, - $this->getTierPrices($productIds, ImportAdvancedPricing::TABLE_TIER_PRICE) - ); + $productsByStores = $this->loadCollection(); + if (!empty($productsByStores)) { + $linkField = $this->getProductEntityLinkField(); + $productLinkIds = []; + + foreach ($productsByStores as $product) { + $productLinkIds[array_pop($product)[$linkField]] = true; + } + $productLinkIds = array_keys($productLinkIds); + $tierPricesData = $this->fetchTierPrices($productLinkIds); + $exportData = $this->prepareExportData( + $productsByStores, + $tierPricesData + ); + if (!empty($exportData)) { + asort($exportData); } } - if ($exportData) { - $exportData = $this->correctExportData($exportData); - } - if (isset($exportData)) { - asort($exportData); - } - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->_logger->critical($e); } + return $exportData; } + /** + * Creating export-formatted row from tier price. + * + * @param array $tierPriceData Tier price information. + * + * @return array Formatted for export tier price information. + */ + private function createExportRow(array $tierPriceData): array + { + //List of columns to display in export row. + $exportRow = $this->templateExportData; + + foreach (array_keys($exportRow) as $keyTemplate) { + if (array_key_exists($keyTemplate, $tierPriceData)) { + if (in_array($keyTemplate, $this->_priceWebsite)) { + //If it's website column then getting website code. + $exportRow[$keyTemplate] = $this->_getWebsiteCode( + $tierPriceData[$keyTemplate] + ); + } elseif (in_array($keyTemplate, $this->_priceCustomerGroup)) { + //If it's customer group column then getting customer + //group name by ID. + $exportRow[$keyTemplate] = $this->_getCustomerGroupById( + $tierPriceData[$keyTemplate], + $tierPriceData[ImportAdvancedPricing::VALUE_ALL_GROUPS] + ); + unset($exportRow[ImportAdvancedPricing::VALUE_ALL_GROUPS]); + } elseif ($keyTemplate + === ImportAdvancedPricing::COL_TIER_PRICE + ) { + //If it's price column then getting value and type + //of tier price. + $exportRow[$keyTemplate] + = $tierPriceData[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE] + ? $tierPriceData[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE] + : $tierPriceData[ImportAdvancedPricing::COL_TIER_PRICE]; + $exportRow[ImportAdvancedPricing::COL_TIER_PRICE_TYPE] + = $this->tierPriceTypeValue($tierPriceData); + } else { + //Any other column just goes as is. + $exportRow[$keyTemplate] = $tierPriceData[$keyTemplate]; + } + } + } + + return $exportRow; + } + + /** + * Prepare data for export. + * + * @param array $productsData Products to export. + * @param array $tierPricesData Their tier prices. + * + * @return array Export rows to display. + */ + private function prepareExportData( + array $productsData, + array $tierPricesData + ): array { + //Assigning SKUs to tier prices data. + $productLinkIdToSkuMap = []; + foreach ($productsData as $productData) { + $productLinkIdToSkuMap[$productData[Store::DEFAULT_STORE_ID][$this->getProductEntityLinkField()]] + = $productData[Store::DEFAULT_STORE_ID]['sku']; + } + + //Adding products' SKUs to tier price data. + $linkedTierPricesData = []; + foreach ($tierPricesData as $tierPriceData) { + $sku = $productLinkIdToSkuMap[$tierPriceData['product_link_id']]; + $linkedTierPricesData[] = array_merge( + $tierPriceData, + [ImportAdvancedPricing::COL_SKU => $sku] + ); + } + + //Formatting data for export. + $customExportData = []; + foreach ($linkedTierPricesData as $row) { + $customExportData[] = $this->createExportRow($row); + } + + return $customExportData; + } + /** * Correct export data. * * @param array $exportData * @return array * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @deprecated + * @see prepareExportData */ protected function correctExportData($exportData) { @@ -327,16 +428,83 @@ protected function correctExportData($exportData) /** * Check type for tier price. * - * @param string $tierPricePercentage + * @param array $tierPriceData * @return string */ - private function tierPriceTypeValue($tierPricePercentage) + private function tierPriceTypeValue(array $tierPriceData): string { - return $tierPricePercentage + return $tierPriceData[ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE] ? ImportAdvancedPricing::TIER_PRICE_TYPE_PERCENT : ImportAdvancedPricing::TIER_PRICE_TYPE_FIXED; } + /** + * Load tier prices for given products. + * + * @param string[] $productIds Link IDs of products to find tier prices for. + * + * @return array Tier prices data. + * + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function fetchTierPrices(array $productIds): array + { + if (empty($productIds)) { + throw new \InvalidArgumentException( + 'Can only load tier prices for specific products' + ); + } + + $pricesTable = ImportAdvancedPricing::TABLE_TIER_PRICE; + $exportFilter = null; + $priceFromFilter = null; + $priceToFilter = null; + if (isset($this->_parameters[Export::FILTER_ELEMENT_GROUP])) { + $exportFilter = $this->_parameters[Export::FILTER_ELEMENT_GROUP]; + } + $productEntityLinkField = $this->getProductEntityLinkField(); + $selectFields = [ + ImportAdvancedPricing::COL_TIER_PRICE_WEBSITE => 'ap.website_id', + ImportAdvancedPricing::VALUE_ALL_GROUPS => 'ap.all_groups', + ImportAdvancedPricing::COL_TIER_PRICE_CUSTOMER_GROUP => 'ap.customer_group_id', + ImportAdvancedPricing::COL_TIER_PRICE_QTY => 'ap.qty', + ImportAdvancedPricing::COL_TIER_PRICE => 'ap.value', + ImportAdvancedPricing::COL_TIER_PRICE_PERCENTAGE_VALUE => 'ap.percentage_value', + 'product_link_id' => 'ap.' .$productEntityLinkField, + ]; + if ($exportFilter && array_key_exists('tier_price', $exportFilter)) { + if (!empty($exportFilter['tier_price'][0])) { + $priceFromFilter = $exportFilter['tier_price'][0]; + } + if (!empty($exportFilter['tier_price'][1])) { + $priceToFilter = $exportFilter['tier_price'][1]; + } + } + + $select = $this->_connection->select() + ->from( + ['ap' => $this->_resource->getTableName($pricesTable)], + $selectFields + ) + ->where( + 'ap.'.$productEntityLinkField.' IN (?)', + $productIds + ); + + if ($priceFromFilter !== null) { + $select->where('ap.value >= ?', $priceFromFilter); + } + if ($priceToFilter !== null) { + $select->where('ap.value <= ?', $priceToFilter); + } + if ($priceFromFilter || $priceToFilter) { + $select->orWhere('ap.percentage_value IS NOT NULL'); + } + + return $this->_connection->fetchAll($select); + } + /** * Get tier prices. * @@ -345,6 +513,8 @@ private function tierPriceTypeValue($tierPricePercentage) * @return array|bool * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @deprecated + * @see fetchTierPrices */ protected function getTierPrices(array $listSku, $table) { @@ -413,40 +583,51 @@ protected function getTierPrices(array $listSku, $table) } /** - * Get Website code + * Get Website code. * * @param int $websiteId + * * @return string */ - protected function _getWebsiteCode($websiteId) + protected function _getWebsiteCode(int $websiteId): string { - $storeName = ($websiteId == 0) - ? ImportAdvancedPricing::VALUE_ALL_WEBSITES - : $this->_storeManager->getWebsite($websiteId)->getCode(); - $currencyCode = ''; - if ($websiteId == 0) { - $currencyCode = $this->_storeManager->getWebsite($websiteId)->getBaseCurrencyCode(); - } - if ($storeName && $currencyCode) { - return $storeName . ' [' . $currencyCode . ']'; - } else { - return $storeName; + if (!array_key_exists($websiteId, $this->websiteCodesMap)) { + $storeName = ($websiteId == 0) + ? ImportAdvancedPricing::VALUE_ALL_WEBSITES + : $this->_storeManager->getWebsite($websiteId)->getCode(); + $currencyCode = ''; + if ($websiteId == 0) { + $currencyCode = $this->_storeManager->getWebsite($websiteId) + ->getBaseCurrencyCode(); + } + + if ($storeName && $currencyCode) { + $code = $storeName.' ['.$currencyCode.']'; + } else { + $code = $storeName; + } + $this->websiteCodesMap[$websiteId] = $code; } + + return $this->websiteCodesMap[$websiteId]; } /** - * Get Customer Group By Id + * Get Customer Group By Id. + * + * @param int $groupId + * @param int $allGroups * - * @param int $customerGroupId - * @param null $allGroups * @return string */ - protected function _getCustomerGroupById($customerGroupId, $allGroups = null) - { - if ($allGroups) { + protected function _getCustomerGroupById( + int $groupId, + int $allGroups = 0 + ): string { + if ($allGroups !== 0) { return ImportAdvancedPricing::VALUE_ALL_GROUPS; } else { - return $this->_groupRepository->getById($customerGroupId)->getCode(); + return $this->_groupRepository->getById($groupId)->getCode(); } } diff --git a/app/code/Magento/AsynchronousOperations/Api/BulkStatusInterface.php b/app/code/Magento/AsynchronousOperations/Api/BulkStatusInterface.php new file mode 100644 index 00000000000..88db2d6d801 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Api/BulkStatusInterface.php @@ -0,0 +1,36 @@ +getData(self::BULK_UUID); + } + + /** + * @inheritDoc + */ + public function setBulkUuid($bulkUuid) + { + return $this->setData(self::BULK_UUID, $bulkUuid); + } + + /** + * @inheritDoc + */ + public function getRequestItems() + { + return $this->getData(self::REQUEST_ITEMS); + } + + /** + * @inheritDoc + */ + public function setRequestItems($requestItems) + { + return $this->setData(self::REQUEST_ITEMS, $requestItems); + } + + /** + * @inheritdoc + */ + public function setErrors($isErrors = false) + { + return $this->setData(self::ERRORS, $isErrors); + } + + /** + * @inheritdoc + */ + public function isErrors() + { + return $this->getData(self::ERRORS); + } + + /** + * @inheritDoc + */ + public function getExtensionAttributes() + { + return $this->getData(self::EXTENSION_ATTRIBUTES_KEY); + } + + /** + * @inheritDoc + */ + public function setExtensionAttributes( + \Magento\AsynchronousOperations\Api\Data\AsyncResponseExtensionInterface $extensionAttributes + ) { + return $this->setData(self::EXTENSION_ATTRIBUTES_KEY, $extensionAttributes); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkOperationsStatus.php b/app/code/Magento/AsynchronousOperations/Model/BulkOperationsStatus.php new file mode 100644 index 00000000000..5fc164ec833 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkOperationsStatus.php @@ -0,0 +1,150 @@ +operationCollectionFactory = $operationCollection; + $this->bulkStatus = $bulkStatus; + $this->bulkDetailedFactory = $bulkDetailedFactory; + $this->bulkShortFactory = $bulkShortFactory; + $this->entityManager = $entityManager; + } + + /** + * @inheritDoc + */ + public function getFailedOperationsByBulkId($bulkUuid, $failureType = null) + { + return $this->bulkStatus->getFailedOperationsByBulkId($bulkUuid, $failureType); + } + + /** + * @inheritDoc + */ + public function getOperationsCountByBulkIdAndStatus($bulkUuid, $status) + { + return $this->bulkStatus->getOperationsCountByBulkIdAndStatus($bulkUuid, $status); + } + + /** + * @inheritDoc + */ + public function getBulksByUser($userId) + { + return $this->bulkStatus->getBulksByUser($userId); + } + + /** + * @inheritDoc + */ + public function getBulkStatus($bulkUuid) + { + return $this->bulkStatus->getBulkStatus($bulkUuid); + } + + /** + * @inheritDoc + */ + public function getBulkDetailedStatus($bulkUuid) + { + $bulkSummary = $this->bulkDetailedFactory->create(); + + /** @var \Magento\AsynchronousOperations\Api\Data\DetailedBulkOperationsStatusInterface $bulk */ + $bulk = $this->entityManager->load($bulkSummary, $bulkUuid); + + if ($bulk->getBulkId() === null) { + throw new NoSuchEntityException( + __( + 'Bulk uuid %bulkUuid not exist', + ['bulkUuid' => $bulkUuid] + ) + ); + } + $operations = $this->operationCollectionFactory->create()->addFieldToFilter('bulk_uuid', $bulkUuid)->getItems(); + $bulk->setOperationsList($operations); + + return $bulk; + } + + /** + * @inheritDoc + */ + public function getBulkShortStatus($bulkUuid) + { + $bulkSummary = $this->bulkShortFactory->create(); + + /** @var \Magento\AsynchronousOperations\Api\Data\BulkOperationsStatusInterface $bulk */ + $bulk = $this->entityManager->load($bulkSummary, $bulkUuid); + if ($bulk->getBulkId() === null) { + throw new NoSuchEntityException( + __( + 'Bulk uuid %bulkUuid not exist', + ['bulkUuid' => $bulkUuid] + ) + ); + } + $operations = $this->operationCollectionFactory->create()->addFieldToFilter('bulk_uuid', $bulkUuid)->getItems(); + $bulk->setOperationsList($operations); + + return $bulk; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkStatus/CalculatedStatusSql.php b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/CalculatedStatusSql.php index dda3ed3b673..7bdf8a5b7d4 100644 --- a/app/code/Magento/AsynchronousOperations/Model/BulkStatus/CalculatedStatusSql.php +++ b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/CalculatedStatusSql.php @@ -6,7 +6,6 @@ namespace Magento\AsynchronousOperations\Model\BulkStatus; -use Magento\AsynchronousOperations\Api\Data\OperationInterface; use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; class CalculatedStatusSql diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Detailed.php b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Detailed.php new file mode 100644 index 00000000000..334abb33fd1 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Detailed.php @@ -0,0 +1,31 @@ +getData(self::OPERATIONS_LIST); + } + + /** + * @inheritDoc + */ + public function setOperationsList($operationStatusList) + { + return $this->setData(self::OPERATIONS_LIST, $operationStatusList); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Short.php b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Short.php new file mode 100644 index 00000000000..c6aa99e6720 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/BulkStatus/Short.php @@ -0,0 +1,31 @@ +getData(self::OPERATIONS_LIST); + } + + /** + * @inheritDoc + */ + public function setOperationsList($operationStatusList) + { + return $this->setData(self::OPERATIONS_LIST, $operationStatusList); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ConfigInterface.php b/app/code/Magento/AsynchronousOperations/Model/ConfigInterface.php new file mode 100644 index 00000000000..de0f89a7165 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ConfigInterface.php @@ -0,0 +1,60 @@ + self::SYSTEM_TOPIC_NAME, + CommunicationConfig::TOPIC_IS_SYNCHRONOUS => false, + CommunicationConfig::TOPIC_REQUEST => OperationInterface::class, + CommunicationConfig::TOPIC_REQUEST_TYPE => CommunicationConfig::TOPIC_REQUEST_TYPE_CLASS, + CommunicationConfig::TOPIC_RESPONSE => null, + CommunicationConfig::TOPIC_HANDLERS => [], + ]; + /**#@-*/ + + /** + * Get array of generated topics name and related to this topic service class and methods + * + * @return array + */ + public function getServices(); + + /** + * Get topic name from webapi_async_config services config array by route url and http method + * + * @param string $routeUrl + * @param string $httpMethod GET|POST|PUT|DELETE + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getTopicName($routeUrl, $httpMethod); +} diff --git a/app/code/Magento/AsynchronousOperations/Model/Entity/BulkSummaryMapper.php b/app/code/Magento/AsynchronousOperations/Model/Entity/BulkSummaryMapper.php index 94ebbdca445..4abbde4c360 100644 --- a/app/code/Magento/AsynchronousOperations/Model/Entity/BulkSummaryMapper.php +++ b/app/code/Magento/AsynchronousOperations/Model/Entity/BulkSummaryMapper.php @@ -8,7 +8,6 @@ use Magento\Framework\EntityManager\MapperInterface; use Magento\Framework\App\ResourceConnection; use Magento\Framework\EntityManager\MetadataPool; -use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; /** * @deprecated 100.2.0 diff --git a/app/code/Magento/AsynchronousOperations/Model/ItemStatus.php b/app/code/Magento/AsynchronousOperations/Model/ItemStatus.php new file mode 100644 index 00000000000..b493e0bb663 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ItemStatus.php @@ -0,0 +1,103 @@ +getData(self::ENTITY_ID); + } + + /** + * @inheritDoc + */ + public function setId($entityId) + { + return $this->setData(self::ENTITY_ID, $entityId); + } + + /** + * @inheritDoc + */ + public function getDataHash() + { + return $this->getData(self::DATA_HASH); + } + + /** + * @inheritDoc + */ + public function setDataHash($hash) + { + return $this->setData(self::DATA_HASH, $hash); + } + + /** + * @inheritDoc + */ + public function getStatus() + { + return $this->getData(self::STATUS); + } + + /** + * @inheritDoc + */ + public function setStatus($status = self::STATUS_ACCEPTED) + { + return $this->setData(self::STATUS, $status); + } + + /** + * @inheritDoc + */ + public function getErrorMessage() + { + return $this->getData(self::ERROR_MESSAGE); + } + + /** + * @inheritDoc + */ + public function setErrorMessage($errorMessage = null) + { + if ($errorMessage instanceof \Exception) { + $errorMessage = $errorMessage->getMessage(); + } + + return $this->setData(self::ERROR_MESSAGE, $errorMessage); + } + + /** + * @inheritDoc + */ + public function getErrorCode() + { + return $this->getData(self::ERROR_CODE); + } + + /** + * @inheritDoc + */ + public function setErrorCode($errorCode = null) + { + if ($errorCode instanceof \Exception) { + $errorCode = $errorCode->getCode(); + } + + return $this->setData(self::ERROR_CODE, (int) $errorCode); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php b/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php new file mode 100644 index 00000000000..28bc8141a8e --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php @@ -0,0 +1,145 @@ +invoker = $invoker; + $this->resource = $resource; + $this->messageController = $messageController; + $this->configuration = $configuration; + $this->operationProcessor = $operationProcessorFactory->create([ + 'configuration' => $configuration + ]); + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function process($maxNumberOfMessages = null) + { + $queue = $this->configuration->getQueue(); + + if (!isset($maxNumberOfMessages)) { + $queue->subscribe($this->getTransactionCallback($queue)); + } else { + $this->invoker->invoke($queue, $maxNumberOfMessages, $this->getTransactionCallback($queue)); + } + } + + /** + * Get transaction callback. This handles the case of async. + * + * @param QueueInterface $queue + * @return \Closure + */ + private function getTransactionCallback(QueueInterface $queue) + { + return function (EnvelopeInterface $message) use ($queue) { + /** @var LockInterface $lock */ + $lock = null; + try { + $topicName = $message->getProperties()['topic_name']; + $lock = $this->messageController->lock($message, $this->configuration->getConsumerName()); + + $allowedTopics = $this->configuration->getTopicNames(); + if (in_array($topicName, $allowedTopics)) { + $this->operationProcessor->process($message->getBody()); + } else { + $queue->reject($message); + return; + } + $queue->acknowledge($message); + } catch (MessageLockException $exception) { + $queue->acknowledge($message); + } catch (ConnectionLostException $e) { + if ($lock) { + $this->resource->getConnection() + ->delete($this->resource->getTableName('queue_lock'), ['id = ?' => $lock->getId()]); + } + } catch (NotFoundException $e) { + $queue->acknowledge($message); + $this->logger->warning($e->getMessage()); + } catch (\Exception $e) { + $queue->reject($message, false, $e->getMessage()); + if ($lock) { + $this->resource->getConnection() + ->delete($this->resource->getTableName('queue_lock'), ['id = ?' => $lock->getId()]); + } + } + }; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/MassPublisher.php b/app/code/Magento/AsynchronousOperations/Model/MassPublisher.php new file mode 100644 index 00000000000..5f0f8e28f9f --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/MassPublisher.php @@ -0,0 +1,106 @@ +exchangeRepository = $exchangeRepository; + $this->envelopeFactory = $envelopeFactory; + $this->messageEncoder = $messageEncoder; + $this->messageValidator = $messageValidator; + $this->publisherConfig = $publisherConfig; + $this->messageIdGenerator = $messageIdGenerator; + } + + /** + * {@inheritdoc} + */ + public function publish($topicName, $data) + { + $envelopes = []; + foreach ($data as $message) { + $this->messageValidator->validate(AsyncConfig::SYSTEM_TOPIC_NAME, $message); + $message = $this->messageEncoder->encode(AsyncConfig::SYSTEM_TOPIC_NAME, $message); + $envelopes[] = $this->envelopeFactory->create( + [ + 'body' => $message, + 'properties' => [ + 'delivery_mode' => 2, + 'message_id' => $this->messageIdGenerator->generate($topicName), + ] + ] + ); + } + $publisher = $this->publisherConfig->getPublisher($topicName); + $connectionName = $publisher->getConnection()->getName(); + $exchange = $this->exchangeRepository->getByConnectionName($connectionName); + $exchange->enqueue($topicName, $envelopes); + return null; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php new file mode 100644 index 00000000000..a7c3eac3490 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php @@ -0,0 +1,155 @@ +identityService = $identityService; + $this->itemStatusInterfaceFactory = $itemStatusInterfaceFactory; + $this->asyncResponseFactory = $asyncResponseFactory; + $this->bulkManagement = $bulkManagement; + $this->logger = $logger; + $this->operationRepository = $operationRepository; + } + + /** + * Schedule new bulk operation based on the list of entities + * + * @param $topicName + * @param $entitiesArray + * @param null $groupId + * @param null $userId + * @return AsyncResponseInterface + * @throws BulkException + * @throws LocalizedException + */ + public function publishMass($topicName, array $entitiesArray, $groupId = null, $userId = null) + { + $bulkDescription = __('Topic %1', $topicName); + + if ($groupId == null) { + $groupId = $this->identityService->generateId(); + + /** create new bulk without operations */ + if (!$this->bulkManagement->scheduleBulk($groupId, [], $bulkDescription, $userId)) { + throw new LocalizedException( + __('Something went wrong while processing the request.') + ); + } + } + + $operations = []; + $requestItems = []; + $bulkException = new BulkException(); + foreach ($entitiesArray as $key => $entityParams) { + /** @var \Magento\WebapiAsync\Api\Data\ItemStatusInterface $requestItem */ + $requestItem = $this->itemStatusInterfaceFactory->create(); + + try { + $operations[] = $this->operationRepository->createByTopic($topicName, $entityParams, $groupId); + $requestItem->setId($key); + $requestItem->setStatus(ItemStatusInterface::STATUS_ACCEPTED); + $requestItems[] = $requestItem; + } catch (\Exception $exception) { + $this->logger->error($exception); + $requestItem->setId($key); + $requestItem->setStatus(ItemStatusInterface::STATUS_REJECTED); + $requestItem->setErrorMessage($exception); + $requestItem->setErrorCode($exception); + $requestItems[] = $requestItem; + $bulkException->addException(new LocalizedException( + __('Error processing %key element of input data', ['key' => $key]), + $exception + )); + } + } + + if (!$this->bulkManagement->scheduleBulk($groupId, $operations, $bulkDescription, $userId)) { + throw new LocalizedException( + __('Something went wrong while processing the request.') + ); + } + /** @var AsyncResponseInterface $asyncResponse */ + $asyncResponse = $this->asyncResponseFactory->create(); + $asyncResponse->setBulkUuid($groupId); + $asyncResponse->setRequestItems($requestItems); + + if ($bulkException->wasErrorAdded()) { + $asyncResponse->setErrors(true); + $bulkException->addData($asyncResponse); + throw $bulkException; + } else { + $asyncResponse->setErrors(false); + } + + return $asyncResponse; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/Operation.php b/app/code/Magento/AsynchronousOperations/Model/Operation.php index dbfce3ccd8b..70cc9f0ebc5 100644 --- a/app/code/Magento/AsynchronousOperations/Model/Operation.php +++ b/app/code/Magento/AsynchronousOperations/Model/Operation.php @@ -5,14 +5,14 @@ */ namespace Magento\AsynchronousOperations\Model; -use Magento\AsynchronousOperations\Api\Data\OperationInterface; +use Magento\AsynchronousOperations\Api\Data\DetailedOperationStatusInterface; use Magento\Framework\DataObject; use Magento\Framework\Api\ExtensibleDataInterface; /** * Class Operation */ -class Operation extends DataObject implements OperationInterface, ExtensibleDataInterface +class Operation extends DataObject implements DetailedOperationStatusInterface, ExtensibleDataInterface { /** * @inheritDoc @@ -78,6 +78,22 @@ public function setSerializedData($serializedData) return $this->setData(self::SERIALIZED_DATA, $serializedData); } + /** + * @inheritDoc + */ + public function getResultSerializedData() + { + return $this->getData(self::RESULT_SERIALIZED_DATA); + } + + /** + * @inheritDoc + */ + public function setResultSerializedData($resultSerializedData) + { + return $this->setData(self::RESULT_SERIALIZED_DATA, $resultSerializedData); + } + /** * @inheritDoc */ diff --git a/app/code/Magento/AsynchronousOperations/Model/Operation/Details.php b/app/code/Magento/AsynchronousOperations/Model/Operation/Details.php index 398934f0933..d248f9c3e92 100644 --- a/app/code/Magento/AsynchronousOperations/Model/Operation/Details.php +++ b/app/code/Magento/AsynchronousOperations/Model/Operation/Details.php @@ -3,9 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\AsynchronousOperations\Model\Operation; use Magento\Framework\Bulk\OperationInterface; +use Magento\Framework\Bulk\BulkStatusInterface; class Details { @@ -19,23 +21,36 @@ class Details */ private $bulkStatus; + /** + * @var null + */ + private $bulkUuid; + /** * Map between status codes and human readable indexes + * * @var array */ private $statusMap = [ - OperationInterface::STATUS_TYPE_COMPLETE => 'operations_successful', - OperationInterface::STATUS_TYPE_RETRIABLY_FAILED => 'failed_retriable', + OperationInterface::STATUS_TYPE_COMPLETE => 'operations_successful', + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED => 'failed_retriable', OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED => 'failed_not_retriable', + OperationInterface::STATUS_TYPE_OPEN => 'open', + OperationInterface::STATUS_TYPE_REJECTED => 'rejected', ]; /** + * Init dependencies. + * * @param \Magento\Framework\Bulk\BulkStatusInterface $bulkStatus + * @param null $bulkUuid */ public function __construct( - \Magento\Framework\Bulk\BulkStatusInterface $bulkStatus + BulkStatusInterface $bulkStatus, + $bulkUuid = null ) { $this->bulkStatus = $bulkStatus; + $this->bulkUuid = $bulkUuid; } /** @@ -47,11 +62,12 @@ public function __construct( public function getDetails($bulkUuid) { $details = [ - 'operations_total' => 0, + 'operations_total' => 0, 'operations_successful' => 0, - 'operations_failed' => 0, - 'failed_retriable' => 0, - 'failed_not_retriable' => 0, + 'operations_failed' => 0, + 'failed_retriable' => 0, + 'failed_not_retriable' => 0, + 'rejected' => 0, ]; if (array_key_exists($bulkUuid, $this->operationCache)) { @@ -65,13 +81,84 @@ public function getDetails($bulkUuid) ); } - // total is sum of successful, retriable, not retriable and open operations - $details['operations_total'] = array_sum($details) + $this->bulkStatus->getOperationsCountByBulkIdAndStatus( - $bulkUuid, - OperationInterface::STATUS_TYPE_OPEN - ); + $details['operations_total'] = array_sum($details); $details['operations_failed'] = $details['failed_retriable'] + $details['failed_not_retriable']; $this->operationCache[$bulkUuid] = $details; + return $details; } + + /** + * @inheritDoc + */ + public function getOperationsTotal() + { + $this->getDetails($this->bulkUuid); + + return $this->operationCache[$this->bulkUuid]['operations_total']; + } + + /** + * @inheritDoc + */ + public function getOpen() + { + $this->getDetails($this->bulkUuid); + $statusKey = $this->statusMap[OperationInterface::STATUS_TYPE_OPEN]; + + return $this->operationCache[$this->bulkUuid][$statusKey]; + } + + /** + * @inheritDoc + */ + public function getOperationsSuccessful() + { + $this->getDetails($this->bulkUuid); + $statusKey = $this->statusMap[OperationInterface::STATUS_TYPE_COMPLETE]; + + return $this->operationCache[$this->bulkUuid][$statusKey]; + } + + /** + * @inheritDoc + */ + public function getTotalFailed() + { + $this->getDetails($this->bulkUuid); + + return $this->operationCache[$this->bulkUuid]['operations_failed']; + } + + /** + * @inheritDoc + */ + public function getFailedNotRetriable() + { + $statusKey = $this->statusMap[OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED]; + + return $this->operationCache[$this->bulkUuid][$statusKey]; + } + + /** + * @inheritDoc + */ + public function getFailedRetriable() + { + $this->getDetails($this->bulkUuid); + $statusKey = $this->statusMap[OperationInterface::STATUS_TYPE_RETRIABLY_FAILED]; + + return $this->operationCache[$this->bulkUuid][$statusKey]; + } + + /** + * @inheritDoc + */ + public function getRejected() + { + $this->getDetails($this->bulkUuid); + $statusKey = $this->statusMap[OperationInterface::STATUS_TYPE_REJECTED]; + + return $this->operationCache[$this->bulkUuid][$statusKey]; + } } diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationManagement.php b/app/code/Magento/AsynchronousOperations/Model/OperationManagement.php index 54b8a767584..ce780a4ba85 100644 --- a/app/code/Magento/AsynchronousOperations/Model/OperationManagement.php +++ b/app/code/Magento/AsynchronousOperations/Model/OperationManagement.php @@ -6,7 +6,7 @@ namespace Magento\AsynchronousOperations\Model; -use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory; +use Magento\AsynchronousOperations\Api\Data\DetailedOperationStatusInterfaceFactory; use Magento\Framework\EntityManager\EntityManager; /** @@ -20,7 +20,7 @@ class OperationManagement implements \Magento\Framework\Bulk\OperationManagement private $entityManager; /** - * @var OperationInterfaceFactory + * @var DetailedOperationStatusInterfaceFactory */ private $operationFactory; @@ -33,24 +33,30 @@ class OperationManagement implements \Magento\Framework\Bulk\OperationManagement * OperationManagement constructor. * * @param EntityManager $entityManager - * @param OperationInterfaceFactory $operationFactory + * @param DetailedOperationStatusInterfaceFactory $operationFactory * @param \Psr\Log\LoggerInterface $logger */ public function __construct( EntityManager $entityManager, - OperationInterfaceFactory $operationFactory, + DetailedOperationStatusInterfaceFactory $operationFactory, \Psr\Log\LoggerInterface $logger ) { $this->entityManager = $entityManager; - $this->operationFactory= $operationFactory; + $this->operationFactory = $operationFactory; $this->logger = $logger; } /** * @inheritDoc */ - public function changeOperationStatus($operationId, $status, $errorCode = null, $message = null, $data = null) - { + public function changeOperationStatus( + $operationId, + $status, + $errorCode = null, + $message = null, + $data = null, + $resultData = null + ) { try { $operationEntity = $this->operationFactory->create(); $this->entityManager->load($operationEntity, $operationId); @@ -58,6 +64,8 @@ public function changeOperationStatus($operationId, $status, $errorCode = null, $operationEntity->setStatus($status); $operationEntity->setResultMessage($message); $operationEntity->setSerializedData($data); + $operationEntity->setResultSerializedData($resultData); + $operationEntity->setResultSerializedData($resultData); $this->entityManager->save($operationEntity); } catch (\Exception $exception) { $this->logger->critical($exception->getMessage()); diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationProcessor.php b/app/code/Magento/AsynchronousOperations/Model/OperationProcessor.php new file mode 100644 index 00000000000..6826c34fd35 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/OperationProcessor.php @@ -0,0 +1,227 @@ +messageValidator = $messageValidator; + $this->messageEncoder = $messageEncoder; + $this->configuration = $configuration; + $this->jsonHelper = $jsonHelper; + $this->operationManagement = $operationManagement; + $this->logger = $logger; + $this->serviceOutputProcessor = $serviceOutputProcessor; + $this->communicationConfig = $communicationConfig; + } + + /** + * Process topic-based encoded message + * + * @param string $encodedMessage + * @return void + */ + public function process(string $encodedMessage) + { + $operation = $this->messageEncoder->decode(AsyncConfig::SYSTEM_TOPIC_NAME, $encodedMessage); + $this->messageValidator->validate(AsyncConfig::SYSTEM_TOPIC_NAME, $operation); + + $status = OperationInterface::STATUS_TYPE_COMPLETE; + $errorCode = null; + $messages = []; + $topicName = $operation->getTopicName(); + $handlers = $this->configuration->getHandlers($topicName); + try { + $data = $this->jsonHelper->unserialize($operation->getSerializedData()); + $entityParams = $this->messageEncoder->decode($topicName, $data['meta_information']); + $this->messageValidator->validate($topicName, $entityParams); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $errorCode = $e->getCode(); + $messages[] = $e->getMessage(); + } + + $outputData = null; + if ($errorCode === null) { + foreach ($handlers as $callback) { + $result = $this->executeHandler($callback, $entityParams); + $status = $result['status']; + $errorCode = $result['error_code']; + $messages = array_merge($messages, $result['messages']); + $outputData = $result['output_data']; + } + } + + if (isset($outputData)) { + try { + $communicationConfig = $this->communicationConfig->getTopic($topicName); + $asyncHandler = + $communicationConfig[CommunicationConfig::TOPIC_HANDLERS][AsyncConfig::DEFAULT_HANDLER_NAME]; + $serviceClass = $asyncHandler[CommunicationConfig::HANDLER_TYPE]; + $serviceMethod = $asyncHandler[CommunicationConfig::HANDLER_METHOD]; + $outputData = $this->serviceOutputProcessor->process( + $outputData, + $serviceClass, + $serviceMethod + ); + $outputData = $this->jsonHelper->serialize($outputData); + } catch (\Exception $e) { + $messages[] = $e->getMessage(); + } + } + + $serializedData = (isset($errorCode)) ? $operation->getSerializedData() : null; + $this->operationManagement->changeOperationStatus( + $operation->getId(), + $status, + $errorCode, + implode('; ', $messages), + $serializedData, + $outputData + ); + } + + /** + * Execute topic handler + * + * @param $callback + * @param $entityParams + * @return array + */ + private function executeHandler($callback, $entityParams) + { + $result = [ + 'status' => OperationInterface::STATUS_TYPE_COMPLETE, + 'error_code' => null, + 'messages' => [], + 'output_data' => null + ]; + try { + $result['output_data'] = call_user_func_array($callback, $entityParams); + $result['messages'][] = sprintf('Service execution success %s::%s', get_class($callback[0]), $callback[1]); + } catch (\Zend_Db_Adapter_Exception $e) { + $this->logger->critical($e->getMessage()); + if ($e instanceof LockWaitException + || $e instanceof DeadlockException + || $e instanceof ConnectionException + ) { + $result['status'] = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED; + $result['error_code'] = $e->getCode(); + $result['messages'][] = __($e->getMessage()); + } else { + $result['status'] = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $result['error_code'] = $e->getCode(); + $result['messages'][] = + __('Sorry, something went wrong during product prices update. Please see log for details.'); + } + } catch (NoSuchEntityException $e) { + $this->logger->error($e->getMessage()); + $result['status'] = ($e instanceof TemporaryStateExceptionInterface) ? + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED : + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $result['error_code'] = $e->getCode(); + $result['messages'][] = $e->getMessage(); + } catch (LocalizedException $e) { + $this->logger->error($e->getMessage()); + $result['status'] = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $result['error_code'] = $e->getCode(); + $result['messages'][] = $e->getMessage(); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + $result['status'] = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED; + $result['error_code'] = $e->getCode(); + $result['messages'][] = $e->getMessage(); + } + return $result; + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationStatus.php b/app/code/Magento/AsynchronousOperations/Model/OperationStatus.php new file mode 100644 index 00000000000..5c975bd1a9a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/OperationStatus.php @@ -0,0 +1,52 @@ +getData(OperationInterface::ID); + } + + /** + * @inheritDoc + */ + public function getStatus() + { + return $this->getData(OperationInterface::STATUS); + } + + /** + * @inheritDoc + */ + public function getResultMessage() + { + return $this->getData(OperationInterface::RESULT_MESSAGE); + } + + /** + * @inheritDoc + */ + public function getErrorCode() + { + return $this->getData(OperationInterface::ERROR_CODE); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php new file mode 100644 index 00000000000..54e65cc3470 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php @@ -0,0 +1,98 @@ +operationFactory = $operationFactory; + $this->jsonSerializer = $jsonSerializer; + $this->messageEncoder = $messageEncoder; + $this->messageValidator = $messageValidator; + $this->entityManager = $entityManager; + } + + /** + * @param $topicName + * @param $entityParams + * @param $groupId + * @return mixed + */ + public function createByTopic($topicName, $entityParams, $groupId) + { + $this->messageValidator->validate($topicName, $entityParams); + $encodedMessage = $this->messageEncoder->encode($topicName, $entityParams); + + $serializedData = [ + 'entity_id' => null, + 'entity_link' => '', + 'meta_information' => $encodedMessage, + ]; + $data = [ + 'data' => [ + OperationInterface::BULK_ID => $groupId, + OperationInterface::TOPIC_NAME => $topicName, + OperationInterface::SERIALIZED_DATA => $this->jsonSerializer->serialize($serializedData), + OperationInterface::STATUS => OperationInterface::STATUS_TYPE_OPEN, + ], + ]; + + /** @var \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation */ + $operation = $this->operationFactory->create($data); + return $this->entityManager->save($operation); + } +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/System/Message/Collection/Synchronized/Plugin.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/System/Message/Collection/Synchronized/Plugin.php index 76c7820fa99..8457a641ed9 100644 --- a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/System/Message/Collection/Synchronized/Plugin.php +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/System/Message/Collection/Synchronized/Plugin.php @@ -143,7 +143,7 @@ private function getText($operationDetails) $summaryReport .= __( '%1 item(s) have been successfully updated.', $operationDetails['operations_successful'] - ) ; + ); } if ($operationDetails['operations_failed'] > 0) { diff --git a/app/code/Magento/AsynchronousOperations/Model/StatusMapper.php b/app/code/Magento/AsynchronousOperations/Model/StatusMapper.php index 3ad260cf26a..e5aee6d2f59 100644 --- a/app/code/Magento/AsynchronousOperations/Model/StatusMapper.php +++ b/app/code/Magento/AsynchronousOperations/Model/StatusMapper.php @@ -25,6 +25,7 @@ public function operationStatusToBulkSummaryStatus($operationStatus) $statusMapping = [ OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED => BulkSummaryInterface::FINISHED_WITH_FAILURE, OperationInterface::STATUS_TYPE_RETRIABLY_FAILED => BulkSummaryInterface::FINISHED_WITH_FAILURE, + OperationInterface::STATUS_TYPE_REJECTED => BulkSummaryInterface::FINISHED_WITH_FAILURE, OperationInterface::STATUS_TYPE_COMPLETE => BulkSummaryInterface::FINISHED_SUCCESSFULLY, OperationInterface::STATUS_TYPE_OPEN => BulkSummaryInterface::IN_PROGRESS, BulkSummaryInterface::NOT_STARTED => BulkSummaryInterface::NOT_STARTED @@ -47,7 +48,8 @@ public function bulkSummaryStatusToOperationStatus($bulkStatus) $statusMapping = [ BulkSummaryInterface::FINISHED_WITH_FAILURE => [ OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, - OperationInterface::STATUS_TYPE_RETRIABLY_FAILED + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED, + OperationInterface::STATUS_TYPE_REJECTED ], BulkSummaryInterface::FINISHED_SUCCESSFULLY => OperationInterface::STATUS_TYPE_COMPLETE, BulkSummaryInterface::IN_PROGRESS => OperationInterface::STATUS_TYPE_OPEN, diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkStatusTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkStatusTest.php index cda55161ab8..7a2f7941f9c 100644 --- a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkStatusTest.php +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/BulkStatusTest.php @@ -54,11 +54,26 @@ class BulkStatusTest extends \PHPUnit\Framework\TestCase */ private $metadataPoolMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkDetailedFactory; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $bulkShortFactory; + /** * @var \PHPUnit_Framework_MockObject_MockObject */ private $entityMetadataMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $entityManager; + /** * @var \PHPUnit_Framework_MockObject_MockObject */ @@ -81,6 +96,15 @@ protected function setUp() \Magento\AsynchronousOperations\Model\BulkStatus\CalculatedStatusSql::class ); $this->metadataPoolMock = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); + $this->bulkDetailedFactory = $this->createPartialMock( + \Magento\AsynchronousOperations\Api\Data\DetailedBulkOperationsStatusInterfaceFactory ::class, + ['create'] + ); + $this->bulkShortFactory = $this->createPartialMock( + \Magento\AsynchronousOperations\Api\Data\BulkOperationsStatusInterfaceFactory::class, + ['create'] + ); + $this->entityManager = $this->createMock(\Magento\Framework\EntityManager\EntityManager::class); $this->entityMetadataMock = $this->createMock(\Magento\Framework\EntityManager\EntityMetadataInterface::class); $this->connectionMock = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); @@ -90,7 +114,10 @@ protected function setUp() $this->operationCollectionFactory, $this->resourceConnectionMock, $this->calculatedStatusSqlMock, - $this->metadataPoolMock + $this->metadataPoolMock, + $this->bulkDetailedFactory, + $this->bulkShortFactory, + $this->entityManager ); } @@ -151,12 +178,13 @@ public function getFailedOperationsByBulkIdDataProvider() { return [ [1, [1]], - [null, + [ + null, [ OperationInterface::STATUS_TYPE_RETRIABLY_FAILED, - OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED - ] - ] + OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, + ], + ], ]; } diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/Operation/DetailsTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/Operation/DetailsTest.php index 7060a48e773..f62e2b7f9d5 100644 --- a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/Operation/DetailsTest.php +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/Operation/DetailsTest.php @@ -34,6 +34,7 @@ public function testGetDetails() $failedRetriable = 23; $failedNotRetriable = 45; $open = 303; + $rejected = 0; $expectedResult = [ 'operations_total' => $completed + $failedRetriable + $failedNotRetriable + $open, @@ -41,6 +42,8 @@ public function testGetDetails() 'operations_failed' => $failedRetriable + $failedNotRetriable, 'failed_retriable' => $failedRetriable, 'failed_not_retriable' => $failedNotRetriable, + 'rejected' => $rejected, + 'open' => $open, ]; $this->bulkStatusMock->method('getOperationsCountByBulkIdAndStatus') @@ -49,6 +52,7 @@ public function testGetDetails() [$uuid, OperationInterface::STATUS_TYPE_RETRIABLY_FAILED, $failedRetriable], [$uuid, OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, $failedNotRetriable], [$uuid, OperationInterface::STATUS_TYPE_OPEN, $open], + [$uuid, OperationInterface::STATUS_TYPE_REJECTED, $rejected], ]); $result = $this->model->getDetails($uuid); diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/OperationManagementTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/OperationManagementTest.php index 32146b7b6eb..0a4e5f2f3ec 100644 --- a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/OperationManagementTest.php +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/OperationManagementTest.php @@ -40,10 +40,11 @@ protected function setUp() $this->entityManagerMock = $this->createMock(\Magento\Framework\EntityManager\EntityManager::class); $this->metadataPoolMock = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); $this->operationFactoryMock = $this->createPartialMock( - \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory::class, + \Magento\AsynchronousOperations\Api\Data\DetailedOperationStatusInterfaceFactory::class, ['create'] ); - $this->operationMock = $this->createMock(\Magento\AsynchronousOperations\Api\Data\OperationInterface::class); + $this->operationMock = + $this->createMock(\Magento\AsynchronousOperations\Api\Data\DetailedOperationStatusInterface::class); $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); $this->model = new \Magento\AsynchronousOperations\Model\OperationManagement( $this->entityManagerMock, diff --git a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/StatusMapperTest.php b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/StatusMapperTest.php index 87d2bbac3ca..89fa80de363 100644 --- a/app/code/Magento/AsynchronousOperations/Test/Unit/Model/StatusMapperTest.php +++ b/app/code/Magento/AsynchronousOperations/Test/Unit/Model/StatusMapperTest.php @@ -73,7 +73,8 @@ public function testBulkSummaryStatusToOperationStatus() $this->model->bulkSummaryStatusToOperationStatus(BulkSummaryInterface::FINISHED_WITH_FAILURE), [ OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED, - OperationInterface::STATUS_TYPE_RETRIABLY_FAILED + OperationInterface::STATUS_TYPE_RETRIABLY_FAILED, + OperationInterface::STATUS_TYPE_REJECTED ] ); diff --git a/app/code/Magento/AsynchronousOperations/etc/db_schema.xml b/app/code/Magento/AsynchronousOperations/etc/db_schema.xml index b986b98abf3..1b99ce9a280 100644 --- a/app/code/Magento/AsynchronousOperations/etc/db_schema.xml +++ b/app/code/Magento/AsynchronousOperations/etc/db_schema.xml @@ -37,6 +37,8 @@ comment="Name of the related message queue topic"/> + + + + + + - + magento_operation id @@ -74,6 +79,29 @@ - + + + + + + Magento\AsynchronousOperations\Model\MassPublisher + Magento\AsynchronousOperations\Model\MassPublisher + + + + + + + Magento\AsynchronousOperations\Model\VirtualType\PublisherPool + + + + + Magento\AsynchronousOperations\Model\VirtualType\BulkManagement + + + + diff --git a/app/code/Magento/AsynchronousOperations/etc/webapi.xml b/app/code/Magento/AsynchronousOperations/etc/webapi.xml new file mode 100644 index 00000000000..253dedd1c7a --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/etc/webapi.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Backend/Block/Menu.php b/app/code/Magento/Backend/Block/Menu.php index d6bdeb4ea89..7d86497288a 100644 --- a/app/code/Magento/Backend/Block/Menu.php +++ b/app/code/Magento/Backend/Block/Menu.php @@ -352,7 +352,7 @@ protected function _addSubMenu($menuItem, $level, $limit, $id = null) return $output; } $output .= '