From 6ac65596cb097fc2bb391bf69a415dedcb0fb232 Mon Sep 17 00:00:00 2001 From: Pierre Gauthier Date: Wed, 13 Aug 2025 08:49:16 +0200 Subject: [PATCH 1/2] [Tracker] Manage page history --- .../Search/Usage/Chart/ViewOrigins.php | 112 ++++++++++++++++++ .../Adminhtml/Search/Usage/ChartInterface.php | 15 +++ .../Search/Usage/Kpi/AggregationProvider.php | 16 +++ .../Model/Search/Usage/Kpi/Report.php | 29 +++++ ...le_elasticsuite_analytics_search_usage.xml | 6 + .../templates/search/usage/kpi.phtml | 8 +- .../adminhtml/web/css/source/_module.less | 8 +- .../etc/elasticsuite_indices.xml | 5 + .../view/frontend/web/js/tracking.js | 95 ++++++++++++++- 9 files changed, 287 insertions(+), 7 deletions(-) create mode 100644 src/module-elasticsuite-analytics/Block/Adminhtml/Search/Usage/Chart/ViewOrigins.php 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) { From 0bad824ebe07a9467348dbab90e1648065c9c276 Mon Sep 17 00:00:00 2001 From: Pierre Gauthier Date: Wed, 1 Oct 2025 11:27:01 +0200 Subject: [PATCH 2/2] [Analytics] Track Autocomplete Events --- .../Model/Search/Usage/Kpi/Report.php | 12 ++++- .../Usage/Terms/AggregationProvider.php | 35 +++++++++++- .../Model/Search/Usage/Terms/Report.php | 54 +++++++++++++++---- .../i18n/de_DE.csv | 3 ++ .../i18n/en_US.csv | 3 ++ .../i18n/fr_FR.csv | 3 ++ .../i18n/nl_NL.csv | 3 ++ .../templates/search/usage/terms.phtml | 6 +++ .../view/frontend/web/js/form-mini.js | 42 ++++++++++++++- .../view/frontend/templates/config.phtml | 4 +- .../view/frontend/web/js/tracking.js | 45 ++++++++++++++-- 11 files changed, 193 insertions(+), 17 deletions(-) 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 f1f6868b8..8603aa148 100644 --- a/src/module-elasticsuite-analytics/Model/Search/Usage/Kpi/Report.php +++ b/src/module-elasticsuite-analytics/Model/Search/Usage/Kpi/Report.php @@ -56,6 +56,8 @@ public function getLabel(string $origin): string return __('Search'); case 'product_views_catalog_product_view_count': return __('Recommender'); + case 'product_views_catalogsearch_autocomplete_click_count': + return __('Autocomplete'); default: return __('Other'); } @@ -64,6 +66,7 @@ public function getLabel(string $origin): string /** * {@inheritdoc} * @SuppressWarnings(PHPMD.ElseExpression) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function processResponse(\Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Response\QueryResponse $response) { @@ -84,10 +87,17 @@ protected function processResponse(\Smile\ElasticsuiteCore\Search\Adapter\Elasti $data[$key] = (int) $value->getMetrics()['count']; if ($value->getAggregations()->getBucket('origin')->getValues()) { $originDetails = ''; + $rawData = []; 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"; + $label = $this->getLabel($key); + if (!array_key_exists($label, $rawData)) { + $rawData[$label] = $data[$key]; + } + } + foreach ($rawData as $label => $count) { + $originDetails .= "• {$label}: {$count}\n"; } $data[$value->getValue() . '_origin_details'] = $originDetails; } diff --git a/src/module-elasticsuite-analytics/Model/Search/Usage/Terms/AggregationProvider.php b/src/module-elasticsuite-analytics/Model/Search/Usage/Terms/AggregationProvider.php index 46ba805d4..145be50f8 100644 --- a/src/module-elasticsuite-analytics/Model/Search/Usage/Terms/AggregationProvider.php +++ b/src/module-elasticsuite-analytics/Model/Search/Usage/Terms/AggregationProvider.php @@ -85,7 +85,10 @@ public function getAggregation() 'name' => 'search_terms', 'metrics' => $this->getMetrics(), 'pipelines' => $this->getPipelines(), - 'childBuckets' => [$this->getFilteredResultCountMetric()], + 'childBuckets' => [ + $this->getFilteredResultCountMetric(), + $this->getPageTypeIdentifierBucket(), + ], 'sortOrder' => ['unique_sessions' => 'desc'], 'size' => $this->helper->getMaxSearchTerms(), ]; @@ -153,6 +156,36 @@ protected function getFilteredResultCountMetric() ); } + /** + * Return aggregation providing the page type identifier. + * + * @return BucketInterface + */ + protected function getPageTypeIdentifierBucket() + { + return $this->aggregationFactory->create( + BucketInterface::TYPE_TERM, + [ + 'name' => 'page_type_identifier', + 'field' => 'page.type.identifier', + 'metrics' => [ + $this->metricFactory->create( + [ + 'name' => 'unique_sessions', + 'field' => 'session.uid', + 'type' => MetricInterface::TYPE_CARDINALITY, + ] + ), + ], + 'include' => [ + 'catalogsearch_result_index', + 'catalogsearch_autocomplete', + 'catalogsearch_autocomplete_click', + ], + ] + ); + } + /** * Return "only first page" query. * diff --git a/src/module-elasticsuite-analytics/Model/Search/Usage/Terms/Report.php b/src/module-elasticsuite-analytics/Model/Search/Usage/Terms/Report.php index cc903780e..1f57a7132 100644 --- a/src/module-elasticsuite-analytics/Model/Search/Usage/Terms/Report.php +++ b/src/module-elasticsuite-analytics/Model/Search/Usage/Terms/Report.php @@ -13,8 +13,13 @@ */ namespace Smile\ElasticsuiteAnalytics\Model\Search\Usage\Terms; +use Magento\Framework\Api\Search\AggregationValueInterface; +use Magento\Framework\Api\Search\BucketInterface; +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\Search\Model\SearchEngine; use Smile\ElasticsuiteAnalytics\Model\AbstractReport; use Smile\ElasticsuiteAnalytics\Model\Report\SearchRequestBuilder; +use Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Response\QueryResponse; /** * Search terms Report @@ -22,7 +27,7 @@ * @category Smile * @package Smile\ElasticsuiteAnalytics */ -class Report extends AbstractReport implements \Magento\Framework\View\Element\Block\ArgumentInterface +class Report extends AbstractReport implements ArgumentInterface { /** * @var array @@ -32,12 +37,12 @@ class Report extends AbstractReport implements \Magento\Framework\View\Element\B /** * Constructor. * - * @param \Magento\Search\Model\SearchEngine $searchEngine Search engine. - * @param SearchRequestBuilder $searchRequestBuilder Search request builder. - * @param array $postProcessors Response post processors. + * @param SearchEngine $searchEngine Search engine. + * @param SearchRequestBuilder $searchRequestBuilder Search request builder. + * @param array $postProcessors Response post processors. */ public function __construct( - \Magento\Search\Model\SearchEngine $searchEngine, + SearchEngine $searchEngine, SearchRequestBuilder $searchRequestBuilder, array $postProcessors = [] ) { @@ -49,7 +54,7 @@ public function __construct( * {@inheritdoc} * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - protected function processResponse(\Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Response\QueryResponse $response) + protected function processResponse(QueryResponse $response) { $data = []; @@ -73,6 +78,13 @@ protected function processResponse(\Smile\ElasticsuiteCore\Search\Adapter\Elasti } $data[$searchTerm]['result_count'] = round((float) $resultCountMetrics ?: 0); } + if ($value->getAggregations()->getBucket('page_type_identifier')) { + $this->processAutocompleteData( + $value->getAggregations()->getBucket('page_type_identifier'), + $searchTerm, + $data + ); + } } } @@ -83,14 +95,38 @@ protected function processResponse(\Smile\ElasticsuiteCore\Search\Adapter\Elasti return $data; } + /** + * Process autocomplete data from page type aggregation. + * + * @param BucketInterface $pageTypeIdAgg Page type aggregation bucket. + * @param string $searchTerm Search term. + * @param array $data Aggregated data. + * @return void + */ + private function processAutocompleteData(BucketInterface $pageTypeIdAgg, string $searchTerm, array &$data): void + { + $sessionsByPageType = []; + foreach ($pageTypeIdAgg->getValues() as $pageTypeIdVal) { + $sessionsByPageType[$pageTypeIdVal->getValue()] = $pageTypeIdVal->getMetrics()['unique_sessions']; + } + + $typedWithClick = $sessionsByPageType['catalogsearch_autocomplete_click'] ?? 0; + $typedAndPressEnter = $sessionsByPageType['catalogsearch_result_index'] ?? 0; + $typedWithNoClick = $data[$searchTerm]['sessions'] - $typedWithClick - $typedAndPressEnter; + + $data[$searchTerm]['sessions_without_click'] = $typedWithNoClick; + $data[$searchTerm]['sessions_with_click'] = $typedWithClick; + $data[$searchTerm]['sessions_with_submit'] = $typedAndPressEnter; + } + /** * Return the bucket values from the main aggregation * - * @param \Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Response\QueryResponse $response ES Query response. + * @param QueryResponse $response ES Query response. * - * @return \Magento\Framework\Api\Search\AggregationValueInterface[] + * @return AggregationValueInterface[] */ - private function getValues(\Smile\ElasticsuiteCore\Search\Adapter\Elasticsuite\Response\QueryResponse $response) + private function getValues(QueryResponse $response) { $bucket = $response->getAggregations()->getBucket('search_terms'); diff --git a/src/module-elasticsuite-analytics/i18n/de_DE.csv b/src/module-elasticsuite-analytics/i18n/de_DE.csv index c6e09c3f3..e4b795091 100644 --- a/src/module-elasticsuite-analytics/i18n/de_DE.csv +++ b/src/module-elasticsuite-analytics/i18n/de_DE.csv @@ -28,7 +28,10 @@ "Exact searches","Exakte Suche" "Conversion rate","Konversionsrate" "All sessions","Alle Sitzungen" +"with clicks","Mit Klick" "With search","Mit Suche" +"with submit","Mit Bestätigung" +"without clicks","Ohne Klick" "Without search","Ohne Suche" "What is this ?","Was ist das?" "The terms listed below are the most popular search terms, whether they lead to exact or close matches.
    The link on a term leads to its edit screen where you can either merchandise the search results page or configure a redirection - for instance to redirect the user to a dedicated category.
    The ""conversion rate"" column value is computed as the ratio of the number of sessions where an order was placed and the term(s) searched for on the total number of sessions where the term(s) were searched for.","Die unten aufgeführten Begriffe sind die beliebtesten Suchbegriffe, unabhängig davon, ob sie zu exakten oder geschlossenen Übereinstimmungen führen.
    Der Link auf einem Begriff führt zu seinem Bearbeiten-Bildschirm, auf dem Sie entweder Merchandise der Suchergebnisseite vermarkten oder eine Umleitung konfigurieren können - zum Beispiel um den Benutzer in eine eigene Kategorie umzuleiten.
    Der Spaltenwert der Spalte ""Konversionsrate"" wird als Verhältnis der Anzahl der Sitzungen berechnet, in denen eine Bestellung aufgegeben wurde, und der Suchtermin nach der Gesamtzahl der Sitzungen, nach denen die Begriffe gesucht wurden." diff --git a/src/module-elasticsuite-analytics/i18n/en_US.csv b/src/module-elasticsuite-analytics/i18n/en_US.csv index 7780c1570..270061fc2 100644 --- a/src/module-elasticsuite-analytics/i18n/en_US.csv +++ b/src/module-elasticsuite-analytics/i18n/en_US.csv @@ -28,7 +28,10 @@ "Exact searches","Exact searches" "Conversion rate","Conversion rate" "All sessions","All sessions" +"with clicks","with clicks" "With search","With search" +"with submit","with submit" +"without clicks","without clicks" "Without search","Without search" "What is this ?","What is this ?" "The terms listed below are the most popular search terms, whether they lead to exact or close matches.
    The link on a term leads to its edit screen where you can either merchandise the search results page or configure a redirection - for instance to redirect the user to a dedicated category.
    The ""conversion rate"" column value is computed as the ratio of the number of sessions where an order was placed and the term(s) searched for on the total number of sessions where the term(s) were searched for.","The terms listed below are the most popular search terms, whether they lead to exact or close matches.
    The link on a term leads to its edit screen where you can either merchandise the search results page or configure a redirection - for instance to redirect the user to a dedicated category.
    The ""conversion rate"" column value is computed as the ratio of the number of sessions where an order was placed and the term(s) searched for on the total number of sessions where the term(s) were searched for." diff --git a/src/module-elasticsuite-analytics/i18n/fr_FR.csv b/src/module-elasticsuite-analytics/i18n/fr_FR.csv index 27775672a..53c46fe3d 100644 --- a/src/module-elasticsuite-analytics/i18n/fr_FR.csv +++ b/src/module-elasticsuite-analytics/i18n/fr_FR.csv @@ -28,7 +28,10 @@ "Exact searches","Recherches exactes" "Conversion rate","Taux de conversion" "All sessions","Toute session" +"with clicks","avec clic" "With search","Avec recherche" +"with submit","avec validation" +"without clicks","sans clic" "Without search","Sans recherche" "What is this ?","Qu'est-ce que c'est ?" "The terms listed below are the most popular search terms, whether they lead to exact or close matches.
    The link on a term leads to its edit screen where you can either merchandise the search results page or configure a redirection - for instance to redirect the user to a dedicated category.
    The ""conversion rate"" column value is computed as the ratio of the number of sessions where an order was placed and the term(s) searched for on the total number of sessions where the term(s) were searched for.","Les termes listés ci-dessous sont les termes les plus populaires, qu'ils amènent à des résultats exacts ou approchants.
    Le lien sous un terme permet d'atteindre son écran d'édition où vous pouvez soit merchandiser la page de résultats soit bien configurer une redirection - par exemple en redirigeant l'utilisateur vers une catégorie dédiée.
    La valeur de la colonne ""taux de conversion"" est calculée comme le rapport du nombre de sessions avec un passage de commande et la recherche du/de ce(s) terme(s) sur le nombre total de sessions avec ce(s) terme(s) de recherche." diff --git a/src/module-elasticsuite-analytics/i18n/nl_NL.csv b/src/module-elasticsuite-analytics/i18n/nl_NL.csv index 1f59af76f..de54afbb5 100644 --- a/src/module-elasticsuite-analytics/i18n/nl_NL.csv +++ b/src/module-elasticsuite-analytics/i18n/nl_NL.csv @@ -28,7 +28,10 @@ "Exact searches","Exacte zoekopdrachten" "Conversion rate","Conversion Rate" "All sessions","Alle sessies" +"with clicks","Met klik" "With search","Met zoekopdracht" +"with submit","Met bevestiging" +"without clicks","Zonder klik" "Without search","Zonder zoekopdracht" "What is this ?","Wat is dit?" "The terms listed below are the most popular search terms, whether they lead to exact or close matches.
    The link on a term leads to its edit screen where you can either merchandise the search results page or configure a redirection - for instance to redirect the user to a dedicated category.
    The ""conversion rate"" column value is computed as the ratio of the number of sessions where an order was placed and the term(s) searched for on the total number of sessions where the term(s) were searched for.","De onderstaande termen zijn de populairste zoektermen, ongeacht of ze leiden tot exacte of gesloten matches.
    De link op een term leidt naar het bewerkingsscherm waar je ofwel merchandise de zoekresultaten pagina kunt weergeven of een omleiding kunt configureren - bijvoorbeeld om de gebruiker door te verwijzen naar een speciale categorie.
    De kolom ""conversierative"" wordt berekend als de verhouding van het aantal sessies waar een bestelling is geplaatst en de termen waarnaar is gezocht op het totaal aantal sessies waarnaar de termen(s) werden gezocht." diff --git a/src/module-elasticsuite-analytics/view/adminhtml/templates/search/usage/terms.phtml b/src/module-elasticsuite-analytics/view/adminhtml/templates/search/usage/terms.phtml index 5461406aa..6e8b38e06 100644 --- a/src/module-elasticsuite-analytics/view/adminhtml/templates/search/usage/terms.phtml +++ b/src/module-elasticsuite-analytics/view/adminhtml/templates/search/usage/terms.phtml @@ -51,6 +51,9 @@ $hiddenTermsData = array_slice($termsData, $maxVisibleTerms); + + + getShowConversionRate()) : ?> @@ -82,6 +85,9 @@ $hiddenTermsData = array_slice($termsData, $maxVisibleTerms); + + + getShowConversionRate()) : ?> diff --git a/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js b/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js index 48fd6ba47..0f1aa79cb 100644 --- a/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js +++ b/src/module-elasticsuite-core/view/frontend/web/js/form-mini.js @@ -276,6 +276,19 @@ define([ if (this.responseList.indexList.length) { this._updateAriaHasPopup(true); + const productCount = data.filter(item => item.type === 'product').length; + const urlObject = new URL(this.options.url); + const trackingEvent = new CustomEvent( + 'elasticsuite:autocomplete', + { + detail: { + path: urlObject.pathname, + queryString: value, + productCount: productCount + } + } + ); + document.dispatchEvent(trackingEvent); } else { this._updateAriaHasPopup(false); } @@ -283,8 +296,35 @@ define([ this.responseList.indexList .on('click vclick', function (e) { self.responseList.selected = $(this); + if (self.responseList.selected.attr("href")) { - window.location.href = self.responseList.selected.attr("href"); + // Find the section header (dt) by going up to the dl parent and finding its dt + const sectionHeader = self.responseList.selected.closest('dl').find('dt'); + const isProduct = sectionHeader.hasClass('title-product'); + + // Only dispatch the custom event for products + if (isProduct) { + const trackingEvent = new CustomEvent( + 'elasticsuite:autocomplete:product_click', + { + detail: { + href: self.responseList.selected.attr("href"), + text: self.responseList.selected.find('.product-name').text(), + queryString: self.element.val() + }, + bubbles: true, + cancelable: true + } + ); + + document.dispatchEvent(trackingEvent); + } + + const href = self.responseList.selected.attr("href"); + setTimeout(function() { + window.location.href = href; + }, 100); + e.stopPropagation(); return false; } diff --git a/src/module-elasticsuite-tracker/view/frontend/templates/config.phtml b/src/module-elasticsuite-tracker/view/frontend/templates/config.phtml index 9867de89a..d10952e06 100644 --- a/src/module-elasticsuite-tracker/view/frontend/templates/config.phtml +++ b/src/module-elasticsuite-tracker/view/frontend/templates/config.phtml @@ -23,18 +23,18 @@ getJsonHelper(); if ($block->isEnabled()) { + $storeId = $this->escapeJsQuote($block->getStoreId()); $beaconUrl = $this->escapeJsQuote($block->getBeaconUrl()); $telemetryUrl = $this->escapeJsQuote($block->getTelemetryUrl()); $telemetryEnabled = $this->escapeJsQuote($block->isTelemetryEnabled()); $sessionConfig = $jsonHelper->jsonEncode($block->getCookieConfig()); - $trackerApiConfig = ""; + $trackerApiConfig = "storeId: '{$storeId}',"; if ($block->isUsingAPI()) { $endpointUrl = $this->escapeJsQuote($block->getEndpointUrl()); $trackerApiConfig .= "endpointUrl:'{$endpointUrl}',"; } - $storeId = $this->escapeJsQuote($block->getStoreId()); $userConsentScript = $this->escapeJsQuote($block->getUserConsentScript()); $userConsentConfig = $jsonHelper->jsonEncode($block->getUserConsentConfig()); 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 62d311e17..c6859d051 100644 --- a/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js +++ b/src/module-elasticsuite-tracker/view/frontend/web/js/tracking.js @@ -67,13 +67,19 @@ const smileTracker = (function () { // Add page title and page URL to the tracked variables function addStandardPageVars() { // Website base url tracking (eg. mydomain.com) - this.addPageVar("site", window.location.hostname); + if (!this.vars.hasOwnProperty(transformVarName.bind(this)("site", "page"))) { + this.addPageVar("site", window.location.hostname); + } // Page URL tracking (eg. home.html) - this.addPageVar("url", window.location.pathname); + if (!this.vars.hasOwnProperty(transformVarName.bind(this)("url", "page"))) { + this.addPageVar("url", window.location.pathname); + } // Page title tracking - this.addPageVar("title", document.title); + if (!this.vars.hasOwnProperty(transformVarName.bind(this)("title", "page"))) { + this.addPageVar("title", document.title); + } } // Append GA campaign variable to the tracked variables @@ -205,6 +211,7 @@ const smileTracker = (function () { buildTrackingImg.bind(this)(bodyNode, getTrackerUrl.bind(this)()); } this.trackerSent = true; + this.trackerVarsAdded = false; this.vars = {}; } } @@ -411,6 +418,37 @@ const smileTracker = (function () { } } + function bindAutocompleteEvent() { + let autocompleteDebounceTimer = null; + const AUTOCOMPLETE_DEBOUNCE_DELAY = 1000; // 1s + + document.addEventListener('elasticsuite:autocomplete', function(event) { + if (autocompleteDebounceTimer !== null) { + clearTimeout(autocompleteDebounceTimer); + } + + // Prevent to send many autocomplete tracking event as the user type in the search box. + autocompleteDebounceTimer = setTimeout(function() { + smileTracker.addPageVar('search.query', event.detail.queryString); + smileTracker.addPageVar('type.identifier', 'catalogsearch_autocomplete'); + smileTracker.addPageVar('type.label', 'Autocomplete Form'); + smileTracker.addPageVar('url', event.detail.path); + smileTracker.addPageVar('product_list.product_count', event.detail.productCount); + smileTracker.sendTag(true); + + autocompleteDebounceTimer = null; + }, AUTOCOMPLETE_DEBOUNCE_DELAY); + }); + + document.addEventListener('elasticsuite:autocomplete:product_click', function(event) { + smileTracker.addPageVar('search.query', event.detail.queryString); + smileTracker.addPageVar('type.identifier', 'catalogsearch_autocomplete_click'); + smileTracker.addPageVar('type.label', event.detail.text); + smileTracker.addPageVar('url', event.detail.href); + smileTracker.sendTag(true); + }); + } + // Implementation of the tracker const SmileTrackerImpl = function() { this.vars = {}; @@ -419,6 +457,7 @@ const smileTracker = (function () { this.trackerVarsAdded = false; this.sessionInitialized = false; this.customerData = {}; + bindAutocompleteEvent.bind(this)(); }; SmileTrackerImpl.prototype.sendTag = function (forceCollect = false) {