diff --git a/Block/Adminhtml/System/Config/Fieldset/Expanded.php b/Block/Adminhtml/System/Config/Fieldset/Expanded.php new file mode 100644 index 0000000..c7f8992 --- /dev/null +++ b/Block/Adminhtml/System/Config/Fieldset/Expanded.php @@ -0,0 +1,15 @@ +jsonEncoder = $jsonEncoder; + parent::__construct($context, $data, $secureRenderer); + + } + + protected function _prepareLayout() + { + parent::_prepareLayout(); + if (!$this->getTemplate()) { + $this->setTemplate('Bazaarvoice_Connector::system/config/status.phtml'); + } + return $this; + } + + /** + * Return element html + * + * @param AbstractElement $element + * @return string + */ + protected function _getElementHtml(AbstractElement $element) + { + return $this->_toHtml(); + } + + /** + * Remove scope label + * + * @param AbstractElement $element + * @return string + */ + public function render(AbstractElement $element) + { + $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); + return parent::render($element); + } + + public function getValidations() { + $cronJobs = new \Magento\Framework\DataObject(); + $cronJobs->setTitle('Cron Jobs') + ->setDescription('Validating BV cron jobs status') + ->setUrl('validate/cron'); + + $productFeed = new \Magento\Framework\DataObject(); + $productFeed->setTitle('Product Feed') + ->setDescription('Validating BV cron jobs status') + ->setUrl('validate/cron'); + +// $this->_validations = [ +// 'cron_jobs' => [ +// 'title' => 'Cron Jobs', +// 'description' => 'Validating BV cron jobs status', +// 'url' => 'validate/cron' +// ], +// 'product_feed' => [ +// 'title' => 'Product Feed', +// 'description' => 'Check if product feed config is enabled and if so, check the flat tables', +// 'url' => 'validate/product_feed' +// ], +// 'sftp' => [ +// 'title' => 'SFTP Connection', +// 'description' => 'Checking SFTP connections', +// 'url' => 'validate/sftp' +// ] +// ]; + + $this->_validations = [$cronJobs, $productFeed]; + + return $this->jsonEncoder->encode($this->_validations); + } +} diff --git a/Block/Adminhtml/System/Config/Validator.php b/Block/Adminhtml/System/Config/Validator.php new file mode 100644 index 0000000..08a174f --- /dev/null +++ b/Block/Adminhtml/System/Config/Validator.php @@ -0,0 +1,25 @@ +config = $config; + $this->scopeConfig = $scopeConfig; + $this->resultJsonFactory = $resultJsonFactory; + $this->cronCollection = $collectionFactory; + + + parent::__construct($context); + } + + public function execute() + { + $this->_jobs = $this->config->getJobs(); + + if ($this->configurationCheck()) { + $this->_messages[] = [ + 'success' => true, + 'message' => "All BV cron jobs were configured properly" + ]; + + $collection = $this->getSchedules(); + if ($collection) { + // make sure scheduled jobs were running successfully + // make sure there's no failed jobs + + $items = $collection->getItems(); + $sendProductsStatus = false; + // bazaarvoice_send_orders + foreach($items as $item) { + if ($item->getData('job_code') == self::BV_CRONJOBS[0]) { + if($item->getData('status') == self::STATUS_ERROR) { + $this->_success = false; + $this->_messages[] = [ + 'success' => false, + 'message' => 'An error was occured when running' . self::BV_CRONJOBS[0] . ' last ' . $item->getData('messages') + ]; + } + + if($item->getData('status') == self::STATUS_SUCCESS) { + $this->_messages[] = [ + 'success' => true, + 'message' => 'bazaarvoice_send_orders cron job is working properly' + ]; + } + + if($item->getData('status') == self::STATUS_RUNNING) { + $this->_messages[] = [ + 'success' => true, + 'message' => 'bazaarvoice_send_orders cron job is running' + ]; + } + } + } + + // bazaarvoice_send_products + $collection->addFieldToFilter('job_code', self::BV_CRONJOBS[1]); + switch($collection->getLastItem()->getData('status')) { + case self::STATUS_RUNNING: + $this->_messages[] = [ + 'success' => true, + 'message' => 'bazaarvoice_send_products cron job is running' + ]; + break; + case self::STATUS_SUCCESS: + $this->_messages[] = [ + 'success' => true, + 'message' => 'bazaarvoice_send_products is working properly.' + ]; + break; + case self::STATUS_ERROR: + $this->_success = false; + $this->_messages[] = [ + 'success' => false, + 'message' => 'An error occurred when running the last scheduled task for bazaarvoice_send_products cron job.' + ]; + break; + } + + + $this->_messages[] = [ + 'success' => true, + 'message' => "All BV cron jobs are running successfully!" + ]; + }else { + $this->_messages[] = [ + 'success' => 'warning', + 'message' => "No BV cron jobs are currently in the queue. Please try again later!" + ]; + } + } + + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultJsonFactory->create(); + return $resultJson->setData([ + 'success' => $this->_success, + 'messages' => $this->_messages + ]); + } + + public function configurationCheck() { + + foreach(self::BV_CRONJOBS as $cronJob) { + if(!$this->in_array_r($cronJob, $this->_jobs)) { + $this->_success = false; + $this->_messages = [ + 'success' => false, + 'message' => $cronJob + " does not exist on the cron job list of the system." + ]; + } + } + + if (!$this->_success) { + return false; + } + + return true; + } + + private function in_array_r($item , $array){ + return preg_match('/"'.preg_quote($item, '/').'"/i' , json_encode($array)); + } + + protected function getSchedules() { + $collection = $this->cronCollection->create() + ->addFieldToFilter('job_code', ['in' => implode(",",self::BV_CRONJOBS)]) + ->addOrder('scheduled_at', 'DESC') + ->addOrder('job_code', 'ASC') + ->setPageSize(self::MAX_PAGE_SIZE) + ->addFieldToFilter( + 'scheduled_at', [ + 'gt' => date( + 'Y-m-d H:m:s', + strtotime(date('Y-m-d H:m:s') . ' -7 day') + ) + ] + ); + + if (!$collection->count()) { + return false; + } + + return $collection; + } +} diff --git a/Controller/Adminhtml/Config/Validation/RequiredConfig.php b/Controller/Adminhtml/Config/Validation/RequiredConfig.php new file mode 100644 index 0000000..fb399ed --- /dev/null +++ b/Controller/Adminhtml/Config/Validation/RequiredConfig.php @@ -0,0 +1,82 @@ +storeManager = $storeManager; + + $this->configProvider = $configProvider; + $this->resultJsonFactory = $resultJsonFactory; + + parent::__construct($context); + } + + public function execute() + { + try { + $this->dccAndProducFeedEnabledCheck(); + + }catch (LocalizedException $e) { + $this->_success = false; + $this->_messages[] = [ + 'success' => false, + 'message' => $e->getMessage() + ]; + } + + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultJsonFactory->create(); + return $resultJson->setData([ + 'success' => $this->_success, + 'messages' => $this->_messages + ]); + } + + protected function dccAndProducFeedEnabledCheck() { + if( + $this->configProvider->isDccEnabled($this->storeManager->getStore()->getId(), ScopeInterface::SCOPE_STORE) + && $this->configProvider->canSendProductFeed($this->storeManager->getStore()->getId(), ScopeInterface::SCOPE_STORE)) + { + throw new LocalizedException(__('You can only choose between Product Feed and DCC, but not both.')); + }else if( + !$this->configProvider->isDccEnabled($this->storeManager->getStore()->getId(), ScopeInterface::SCOPE_STORE) + && !$this->configProvider->canSendProductFeed($this->storeManager->getStore()->getId(), ScopeInterface::SCOPE_STORE)) + { + throw new LocalizedException(__('Product feed is not enabled.')); + }else if($this->configProvider->isDccEnabled($this->storeManager->getStore()->getId(), ScopeInterface::SCOPE_STORE)) { + $this->_messages[] = [ + 'success' => true, + 'message' => "DCC cofiguration looks good!" + ]; + }else { + $this->_messages[] = [ + 'success' => true, + 'message' => "Product feed cofiguration looks good!" + ]; + } + + } +} diff --git a/Controller/Adminhtml/Config/Validation/Sftp.php b/Controller/Adminhtml/Config/Validation/Sftp.php new file mode 100644 index 0000000..12f5e28 --- /dev/null +++ b/Controller/Adminhtml/Config/Validation/Sftp.php @@ -0,0 +1,88 @@ +sftp = $sftp; + $this->storeManager = $storeManager; + + $this->configProvider = $configProvider; + $this->resultJsonFactory = $resultJsonFactory; + $this->tagFilter = $tagFilter; + + parent::__construct($context); + } + + public function execute() + { + + + try { + $params = [ + 'host' => $this->configProvider->getSftpHost($this->storeManager->getStore()->getId(), ScopeInterface::SCOPE_STORE), + 'username' => $this->configProvider->getSftpUsername($this->storeManager->getStore()->getId(), ScopeInterface::SCOPE_STORE), + 'password' => $this->configProvider->getSftpPassword($this->storeManager->getStore()->getId()) + ]; + + foreach ($params as $key => $param) { + if(!$param) { + throw new LocalizedException(__(ucfirst($key) . " is missing. Kindly check the configuration above.")); + } + } + + $this->sftp->open($params); + $this->sftp->close(); + + $this->_messages[] = [ + 'success' => true, + 'message' => 'SFTP has been configured and connected successfully' + ]; + }catch (\Magento\Framework\Exception\LocalizedException $e) { + $this->_success = false; + $this->_messages[] = [ + 'success' => false, + 'message' => $e->getMessage() + ]; + } catch (\Exception $e) { + $this->_success = false; + $message = __($e->getMessage()); + $this->_messages[] = [ + 'success' => false, + 'message' => $this->tagFilter->filter($message) + ]; + } + + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultJsonFactory->create(); + return $resultJson->setData([ + 'success' => $this->_success, + 'messages' => $this->_messages + ]); + + } +} diff --git a/Model/ResourceModel/Cron/Collection.php b/Model/ResourceModel/Cron/Collection.php new file mode 100644 index 0000000..15dbf79 --- /dev/null +++ b/Model/ResourceModel/Cron/Collection.php @@ -0,0 +1,7 @@ + + + + + Bazaarvoice\Connector\Block\Adminhtml\System\Config\Fieldset\Expanded + + + + Bazaarvoice\Connector\Block\Adminhtml\System\Config\Form\FieldTable + + + + + + +
+ + + \Magento\Config\Block\System\Config\Form\Fieldset +
diff --git a/etc/crontab.xml b/etc/crontab.xml index 34d8d25..7ef83e7 100644 --- a/etc/crontab.xml +++ b/etc/crontab.xml @@ -7,10 +7,13 @@ - 30 2 * * 6 + + */1 * * * * + - 0 1 * * * + */1 * * * * + diff --git a/view/adminhtml/templates/system/config/status.phtml b/view/adminhtml/templates/system/config/status.phtml new file mode 100644 index 0000000..8610e3e --- /dev/null +++ b/view/adminhtml/templates/system/config/status.phtml @@ -0,0 +1,72 @@ + + + + +
+ +
+
+ +
+

Cron Jobs

Check cron BV cron jobs configuration and job status

+ +
+
+ +
+
+ +
+
+ +
+

SFTP

Check SFTP configuration and connection.

+ +
+
+ + +
+
+ +
+
+ +
+

Product Feed/DCC

Check Product feed and DCC configuration.

+ +
+
+ + +
+
+ +
+ diff --git a/view/adminhtml/web/css/source/_module.less b/view/adminhtml/web/css/source/_module.less new file mode 100644 index 0000000..e63b15b --- /dev/null +++ b/view/adminhtml/web/css/source/_module.less @@ -0,0 +1,4 @@ +@import "module/_variables.less"; +@import "module/_list.less"; +@import "module/_collapsible.less"; + diff --git a/view/adminhtml/web/css/source/module/_collapsible.less b/view/adminhtml/web/css/source/module/_collapsible.less new file mode 100644 index 0000000..50a7520 --- /dev/null +++ b/view/adminhtml/web/css/source/module/_collapsible.less @@ -0,0 +1,32 @@ +.bv__config_checker { + .section-config { + margin-bottom: 20px; + } +} + +.bv__collapsible { + div[data-role="content"] { + padding: 10px; + }; + + div[data-role="title"] { + position: relative; + display: flex; + justify-content: space-between; + h4 { + padding-top: 0px; + } + + button { + position: absolute; + right: 0px; + } + } + + .bv-loader { + width: 100%; + text-align: center; + .lib-css(background-color, @color-gray-light01); + padding: 5px; + } +} diff --git a/view/adminhtml/web/css/source/module/_list.less b/view/adminhtml/web/css/source/module/_list.less new file mode 100644 index 0000000..fad6e94 --- /dev/null +++ b/view/adminhtml/web/css/source/module/_list.less @@ -0,0 +1,79 @@ +@list-item-icon__indent-left: 3.5rem; + +ul, +ol, +dl { + margin-top: 0; +} + +.list { + margin-bottom: 1em; + padding-left: 0; + + > li { + display: block; + margin-bottom: .75em; + position: relative; + margin: 20px; + + > .icon-success, + > .icon-failed { + font-size: 1.6em; + left: -.1em; + position: absolute; + top: 0; + } + + > .icon-success { + .lib-css(color, @message-success__color); + } + + > .icon-failed { + .lib-css(color, @error__color); + } + } +} + +.list-item-icon { + padding-left: @list-item-icon__indent-left; +} + +.list-item-success, +.list-item-failed, +.list-item-warning { + padding-left: @list-item-icon__indent-left; + + &:before { + .lib-css(font-family, @icons-admin__font-name); + font-size: 1.6em; + left: -.1em; + position: absolute; + top: -.2em; + } +} + +.list-item-success { + .lib-css(color, @color-dark-green1); + &:before { + .lib-css(color, @color-dark-green1); + content: @icon-success-messages__content; + } +} + +.list-item-failed { + .lib-css(color, @error__color); + &:before { + .lib-css(color, @error__color); + content: @icon-error__content; + } +} + +.list-item-warning { + .lib-css(color, @color-brownie1); + &:before { + .lib-css(color, @icon-warning); + content: @icon-warning__content; + } +} + + diff --git a/view/adminhtml/web/css/source/module/_variables.less b/view/adminhtml/web/css/source/module/_variables.less new file mode 100644 index 0000000..a1072d7 --- /dev/null +++ b/view/adminhtml/web/css/source/module/_variables.less @@ -0,0 +1,7 @@ + +@icons-round__size: 2.5rem; + +@icon-success-messages__content: @icon-check-mage__content; +@icon-error-messages__content: @icon-error__content; +@icon-warning-messages__content: @icon-warning__content; +@icon-notice-messages__content: @icon-info__content; diff --git a/view/adminhtml/web/js/bv-collapsible.js b/view/adminhtml/web/js/bv-collapsible.js new file mode 100644 index 0000000..945b62e --- /dev/null +++ b/view/adminhtml/web/js/bv-collapsible.js @@ -0,0 +1,76 @@ +define(['jquery', 'collapsible'], function($) { + 'use strict'; + + const procesJson = function() { + var that = this; + var loading = $('

Working...

'); + if (this.content.attr('aria-busy')) { + setTimeout(function () { + that.content.html(loading); + that.trigger.prop('disabled', true); + }, 1); + } + $.when( this.xhr ).then(function( data, status, jqHR) { + if(typeof status === "undefined") { + return false; + } + + var content = $('
'); + + if(jqHR.status == 200) { + data = JSON.parse(data); + + var overAllStatus = data.success ? 'success' : 'failed'; + + that.element.addClass(overAllStatus); + + data.messages.map(function(message) { + + var li = $('
  • '); + + switch(message.success) { + case true: + li.addClass('list-item-success'); + break; + case false: + li.addClass('list-item-failed'); + break; + default: + li.addClass('list-item-warning'); + } + + li.append('

    '+ message.message +'

    ') + + content.find('ul').append(li); + return message; + }); + + setTimeout(function () { + that.content.html(content.html()); + }, 1); + }else { + var li = $('
  • ') + .addClass('list-item-failed') + + li.html('

    An error has been occured.

    '); + content.find('ul').html(li); + + setTimeout(function () { + that.content.html(content.html()); + }, 1); + } + }).always(function() { + that.trigger.prop('disabled', false); + }) + } + + $.widget('bv.collapsible', $.mage.collapsible, { + + _loadContent: function() { + this._super(); + procesJson.bind(this)(); + } + }); + + return $.bv.collapsible; +}); diff --git a/view/adminhtml/web/js/config-validation-view-model.js b/view/adminhtml/web/js/config-validation-view-model.js new file mode 100644 index 0000000..f1434fc --- /dev/null +++ b/view/adminhtml/web/js/config-validation-view-model.js @@ -0,0 +1,25 @@ +define(['jquery', 'uiComponent', 'ko'], function ($, Component, ko) { + 'use strict'; + + return Component.extend({ + bv_validations: ko.observableArray([ + { + "name": "cron_jobs", + "title":"Cron Jobs", + "description":"Validating BV cron jobs status", + "url":"validate/cron" + }, + { + "name": "product_feed", + "title":"Product Feed", + "description":"Validating BV cron jobs status", + "url":"validate/cron" + } + ]), + initialize: function () { + this._super(); + } + }); + } +); +