diff --git a/src/module-elasticsuite-analytics/Block/Adminhtml/Search/Usage/Chart/ViewOrigins.php b/src/module-elasticsuite-analytics/Block/Adminhtml/Search/Usage/Chart/ViewOrigins.php new file mode 100644 index 000000000..cbfecda28 --- /dev/null +++ b/src/module-elasticsuite-analytics/Block/Adminhtml/Search/Usage/Chart/ViewOrigins.php @@ -0,0 +1,112 @@ + + * @copyright 2020 Smile + * @license Open Software License ("OSL") v. 3.0 + */ + +namespace Smile\ElasticsuiteAnalytics\Block\Adminhtml\Search\Usage\Chart; + +use Smile\ElasticsuiteAnalytics\Block\Adminhtml\Search\Usage\ChartInterface; + +/** + * View origins graph block. + * + * @category Smile + * @package Smile\ElasticsuiteAnalytics\Block\Adminhtml\Search\Usage + */ +class ViewOrigins extends \Magento\Backend\Block\Template implements ChartInterface +{ + /** + * @var \Smile\ElasticsuiteAnalytics\Model\Search\Usage\Kpi\Report + */ + private $report; + + /** + * @var \Magento\Framework\Serialize\Serializer\Json + */ + private $serializer; + + /** + * Constructor. + * + * @param \Magento\Backend\Block\Template\Context $context Context. + * @param \Smile\ElasticsuiteAnalytics\Model\Search\Usage\Kpi\Report $report KPI report model. + * @param \Magento\Framework\Serialize\Serializer\Json $serializer Json serializer. + * @param array $data Data. + */ + public function __construct( + \Magento\Backend\Block\Template\Context $context, + \Smile\ElasticsuiteAnalytics\Model\Search\Usage\Kpi\Report $report, + \Magento\Framework\Serialize\Serializer\Json $serializer, + array $data = [] + ) { + parent::__construct($context, $data); + $this->report = $report; + $this->serializer = $serializer; + } + + /** + * {@inheritdoc} + */ + public function getChartOptions() + { + $options = [ + 'colors' => [ + self::COLOR_RED, + self::COLOR_BLUE, + self::COLOR_GREEN, + self::COLOR_YELLOW, + self::COLOR_GRAY, + self::COLOR_PINK, + ], + ]; + + return $this->serializer->serialize($options); + } + + /** + * {@inheritdoc} + */ + public function getChartData() + { + $rawData = []; + $data = [ + 'cols' => [ + ['type' => 'string', 'label' => __('Session type')], + ['type' => 'number', 'label' => __('Count')], + ], + 'rows' => [], + ]; + + try { + $reportData = $this->report->getData(); + if (array_key_exists('product_views_count', $reportData)) { + unset($reportData['product_views_count']); + foreach ($reportData as $key => $value) { + if (str_starts_with($key, 'product_views_')) { + $label = $this->report->getLabel($key); + if (!array_key_exists($label, $rawData)) { + $rawData[$label] = 0; + } + $rawData[$label] += (int) $value; + } + } + foreach ($rawData as $label => $count) { + $data['rows'][] = ['c' => [['v' => $label], ['v' => $count]]]; + } + } + } catch (\LogicException $e) { + ; + } + + return $this->serializer->serialize($data); + } +} diff --git a/src/module-elasticsuite-analytics/Block/Adminhtml/Search/Usage/ChartInterface.php b/src/module-elasticsuite-analytics/Block/Adminhtml/Search/Usage/ChartInterface.php index 9dc58dcc0..1b90b9dca 100644 --- a/src/module-elasticsuite-analytics/Block/Adminhtml/Search/Usage/ChartInterface.php +++ b/src/module-elasticsuite-analytics/Block/Adminhtml/Search/Usage/ChartInterface.php @@ -37,6 +37,21 @@ interface ChartInterface */ const COLOR_GREEN = '#25BC94'; + /** + * Constant for yellow background/drawing chart color + */ + const COLOR_YELLOW = '#FFB800'; + + /** + * Constant for gray background/drawing chart color + */ + const COLOR_GRAY = '#6B7280'; + + /** + * Constant for pink background/drawing chart color + */ + const COLOR_PINK = '#EC4899'; + /** * Return chart data in the format expected by Google Charts API as a JSON encoded string. * (see https://developers.google.com/chart/interactive/docs/reference#dataparam) diff --git a/src/module-elasticsuite-analytics/Model/Search/Usage/Kpi/AggregationProvider.php b/src/module-elasticsuite-analytics/Model/Search/Usage/Kpi/AggregationProvider.php index 6669316b2..1d150422e 100644 --- a/src/module-elasticsuite-analytics/Model/Search/Usage/Kpi/AggregationProvider.php +++ b/src/module-elasticsuite-analytics/Model/Search/Usage/Kpi/AggregationProvider.php @@ -66,6 +66,7 @@ public function getAggregation() 'name' => 'data', 'queries' => $this->getQueries(), 'metrics' => $this->getMetrics(), + 'childBuckets' => $this->getChildBuckets(), ]; return $this->aggregationFactory->create(BucketInterface::TYPE_QUERY_GROUP, $aggParams); @@ -143,4 +144,19 @@ private function getQueries() return $queries; } + + /** + * Return child bucket for query group aggregation. + * + * @return array + */ + private function getChildBuckets(): array + { + return [ + 'origin' => $this->aggregationFactory->create( + BucketInterface::TYPE_TERM, + ['name' => 'origin', 'field' => 'previous_page.type.identifier.keyword'] + ), + ]; + } } diff --git a/src/module-elasticsuite-analytics/Model/Search/Usage/Kpi/Report.php b/src/module-elasticsuite-analytics/Model/Search/Usage/Kpi/Report.php index 2e5f03796..f1f6868b8 100644 --- a/src/module-elasticsuite-analytics/Model/Search/Usage/Kpi/Report.php +++ b/src/module-elasticsuite-analytics/Model/Search/Usage/Kpi/Report.php @@ -41,6 +41,26 @@ class Report extends AbstractReport 'spellcheck_usage_rate', ]; + /** + * Get data label. + * + * @param string $origin Origin code. + * @return string + */ + public function getLabel(string $origin): string + { + switch ($origin) { + case 'product_views_catalog_category_view_count': + return __('Category'); + case 'product_views_catalogsearch_result_index_count': + return __('Search'); + case 'product_views_catalog_product_view_count': + return __('Recommender'); + default: + return __('Other'); + } + } + /** * {@inheritdoc} * @SuppressWarnings(PHPMD.ElseExpression) @@ -62,6 +82,15 @@ protected function processResponse(\Smile\ElasticsuiteCore\Search\Adapter\Elasti } elseif (in_array($value->getValue(), ['product_views', 'category_views', 'add_to_cart', 'sales'])) { $key = sprintf("%s_count", $value->getValue()); $data[$key] = (int) $value->getMetrics()['count']; + if ($value->getAggregations()->getBucket('origin')->getValues()) { + $originDetails = ''; + foreach ($value->getAggregations()->getBucket('origin')->getValues() ?? [] as $originData) { + $key = sprintf("%s_%s_count", $value->getValue(), $originData->getValue()); + $data[$key] = (int) $originData->getMetrics()['count']; + $originDetails .= "• {$this->getLabel($key)}: {$originData->getMetrics()['count']}\n"; + } + $data[$value->getValue() . '_origin_details'] = $originDetails; + } } } diff --git a/src/module-elasticsuite-analytics/view/adminhtml/layout/smile_elasticsuite_analytics_search_usage.xml b/src/module-elasticsuite-analytics/view/adminhtml/layout/smile_elasticsuite_analytics_search_usage.xml index 5daa07c32..4878cb691 100644 --- a/src/module-elasticsuite-analytics/view/adminhtml/layout/smile_elasticsuite_analytics_search_usage.xml +++ b/src/module-elasticsuite-analytics/view/adminhtml/layout/smile_elasticsuite_analytics_search_usage.xml @@ -88,6 +88,12 @@ BarChart + + + Views origins + PieChart + + diff --git a/src/module-elasticsuite-analytics/view/adminhtml/templates/search/usage/kpi.phtml b/src/module-elasticsuite-analytics/view/adminhtml/templates/search/usage/kpi.phtml index c3c95eb13..ccd251285 100644 --- a/src/module-elasticsuite-analytics/view/adminhtml/templates/search/usage/kpi.phtml +++ b/src/module-elasticsuite-analytics/view/adminhtml/templates/search/usage/kpi.phtml @@ -52,8 +52,12 @@ $data = $block->getKpi();
  • - - + + + + + +
  • diff --git a/src/module-elasticsuite-analytics/view/adminhtml/web/css/source/_module.less b/src/module-elasticsuite-analytics/view/adminhtml/web/css/source/_module.less index d610bf524..cb22727a1 100644 --- a/src/module-elasticsuite-analytics/view/adminhtml/web/css/source/_module.less +++ b/src/module-elasticsuite-analytics/view/adminhtml/web/css/source/_module.less @@ -65,7 +65,13 @@ .dashboard-totals-list { .dashboard-totals-item { width: 10%; - text-align: center + text-align: center; + + .has-details { + cursor: pointer; + text-decoration: underline dotted @color-green-apple; + font-style: oblique; + } } } diff --git a/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml b/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml index 884f2234b..d40310860 100644 --- a/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml +++ b/src/module-elasticsuite-tracker/etc/elasticsuite_indices.xml @@ -39,6 +39,11 @@ + + + + + diff --git a/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js b/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js index cbbda5c6e..62d311e17 100644 --- a/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js +++ b/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js @@ -159,6 +159,7 @@ const smileTracker = (function () { addCampaignVars.bind(this)(); addMetaPageVars.bind(this)(); addResolutionVars.bind(this)(); + handlePageHistory.bind(this)(); this.trackerVarsAdded = true; } @@ -275,6 +276,10 @@ const smileTracker = (function () { addVariable.bind(this)(transformVarName.bind(this)(varName , 'page'), value); } + function addPreviousPageVar(varName, value) { + addVariable.bind(this)(transformVarName.bind(this)(varName , 'previous_page'), value); + } + function addCustomerVar(varName, value) { addVariable.bind(this)(transformVarName.bind(this)(varName , 'customer'), value); } @@ -324,6 +329,88 @@ const smileTracker = (function () { } } + function handlePageHistory() { + const currentPageData = { + url: this.vars['page[url]'], + type: { + identifier: this.vars['page[type][identifier]'], + label: this.vars['page[type][label]'] + } + }; + + addPageDataToHistory.bind(this)(currentPageData); + + const previousPage = getPreviousPageDataFromHistory.bind(this)(); + if (previousPage) { + addPreviousPageVar.bind(this)('url', previousPage.url); + addPreviousPageVar.bind(this)('type.identifier', previousPage.type.identifier); + addPreviousPageVar.bind(this)('type.label', previousPage.type.label); + } + } + + function fetchPageHistory() { + // Get existing history + let pageHistory = []; + try { + const historyData = localStorage.getItem(this.historyStorageKey); + pageHistory = historyData ? JSON.parse(historyData) : []; + } catch (e) { + console.warn('Elasticsuite tracker: Error retrieving page history:', e); + } + + return pageHistory; + } + + function persistPageHistory(pageHistory) { + // Limit history size + if (pageHistory.length > this.historyMaxLength) { + pageHistory = pageHistory.slice(0, this.historyMaxLength); + } + + // Save updated history + try { + localStorage.setItem(this.historyStorageKey, JSON.stringify(pageHistory)); + } catch (e) { + console.warn('Error saving page history:', e); + } + } + + function getPreviousPageDataFromHistory() { + const comeFromExternalPage = !document.referrer + || (document.referrer && !document.referrer.includes(window.location.hostname)); + + if (comeFromExternalPage) { + return { + url: document.referrer, + type: { + identifier: 'external', + label: 'external' + } + }; + } + + let pageHistory = fetchPageHistory.bind(this)(); + if (pageHistory.length > 1) { + return pageHistory[1]; // Current page is at index 0, previous page at index 1 + } + + return null; + } + + function addPageDataToHistory(pageData) { + let pageHistory = fetchPageHistory.bind(this)(); + + // Check if user navigated back to a previous page + const pageIndex = pageHistory.findIndex(page => page.url === pageData.url); + + // If the page is the same as the last one, do nothing + // If not, we add it + if (pageIndex !== 0) { + pageHistory.unshift(pageData); + persistPageHistory.bind(this)(pageHistory); + } + } + // Implementation of the tracker const SmileTrackerImpl = function() { this.vars = {}; @@ -353,10 +440,10 @@ const smileTracker = (function () { if (config.hasOwnProperty('endpointUrl') && (config.endpointUrl.length !== 0)) { this.endpointUrl = config.endpointUrl; } - this.telemetryEnabled = config.telemetryEnabled; - this.telemetryUrl = config.telemetryUrl; - - + this.telemetryEnabled = config.telemetryEnabled; + this.telemetryUrl = config.telemetryUrl; + this.historyStorageKey = 'smile_tracker_page_history'; + this.historyMaxLength = 5; }; SmileTrackerImpl.prototype.addPageVar = function (varName, value) {