Integrate Algolia Personalization

Edit on GitHub

This document describes how to integrate Algolia Personalization. This integration also enables the following Algolia premium features:

  • Dynamic Re-Ranking
  • Query Categorization
  • Search analytics
  • Revenue analytics
  • A/B Testing
Third-party frontends

By default, Spryker supports Algolia Personalization only for Yves. To integrate Algolia Personalization with a third-party or mobile frontend, follow Algolia Personalization for headless frontends.

Prerequisites

  1. Integrate Algolia
  2. Configure Algolia
  3. Add products to your Algolia search indexes

Install and update Spryker packages

  1. Install a Spryker package for tracing events:
composer require --with-dependencies spryker-shop/traceable-event-widget:^1.0.2

If the command doesn’t work, try running it with the --with-all-dependencies flag.

  1. Update Spryker packages:
composer update --with-dependencies spryker-shop/cart-page:^3.45.0 spryker-shop/catalog-page:^1.28.0 spryker-shop/checkout-page:^3.32.1 \
spryker-shop/home-page:^1.2.0 spryker-shop/payment-page:^1.5.0 spryker-shop/product-detail-page:^3.23.0 spryker-shop/product-group-widget:^1.10.1 \
spryker-shop/product-review-widget:^1.16.1 spryker-shop/product-set-detail-page:^1.11.0 spryker-shop/quick-order-page:^4.10.1 \
spryker-shop/shop-ui:^1.82.0

If the command doesn’t work, try running it with the --with-all-dependencies flag.

Enable features

  1. Update the project config:
// config_default.php

$config[KernelAppConstants::TENANT_IDENTIFIER]
    //..   
    = getenv('SPRYKER_TENANT_IDENTIFIER') ?: '';
  1. Enable a widget that triggers events for user actions and sends them to Algolia Insights:
// src/Pyz/Yves/ShopApplication/ShopApplicationDependencyProvider.php

class ShopApplicationDependencyProvider extends SprykerShopApplicationDependencyProvider
{

    protected function getGlobalWidgets(): array
    {
        return [
            //...
            SprykerShop\Yves\TraceableEventWidget\Widget\TraceableEventWidget::class
        ];
}
  1. Enable the plugin that generates an anonymous token for guest users in the session:
// src/Pyz/Yves/EventDispatcher/EventDispatcherDependencyProvider.php

class EventDispatcherDependencyProvider extends SprykerEventDispatcherDependencyProvider
{

    protected function getEventDispatcherPlugins(): array
    {
        return [
            //...
            new Spryker\Yves\Customer\Plugin\EventDispatcher\AnonymousIdSessionAssignEventDispatcherPlugin(),
        ];
}
  1. Build JavaScript assets for Yves using one of the following commands:
npm ci && npm run yves
console frontend:project:install-dependencies && console frontend:yves:build
  1. Install the search-insights dependency:
npm i search-insights

Test and configure Yves customizations to work with Algolia Personalization

If you customized Yves templates on the project level (src/Pyz/Yves/), some events may not trigger or trigger with incorrect data.

Run the project in a testing environment

  1. To be able to see event logs in the console, enable debug mode by setting TraceableEventWidgetConfig::isDebugEnabled() to true.
  2. Run the project locally or deploy to a testing environment.
  3. Open the Storefront’s home page.
  4. In browser development console, go to settings.
  5. Enable the “Preserve log” option.

Check triggered events and their payload

Execute the following cases while monitoring the console for specified events:

Home page cases:

CASE EVENT
If the home page has products, click on a product. PRODUCT_CLICK
If the home page has the add to cart button, click on it. ADD_TO_CART

Product Details page cases:

CASE EVENT
Open the Product Details page PAGE_LOAD with the product’s SKU.
Click Add to cart. ADD_TO_CART with the product’s SKU, currency, price, and quantity.
Click Add to shopping list. ADD_TO_SHOPPING_LIST with product’s SKU.
Click Add to wishlist ADD_TO_WISHLIST with the product’s SKU.

Category and Search Results page cases:

CASE EVENT
Open Category or Search Results page. QueryID is present in the event payload. PAGE_LOAD with displayed products SKUs and search filters.
Click on a product. PRODUCT_CLICK
Click Add to cart. ADD_TO_CART with the product’s SKU, currency, and price.
Click on a filter FILTER_CLICK with a list of filters.

Cart page cases:

CASE EVENT
If applicable: add a product from the “Add to cart” widget. ADD_TO_CART
If applicable: save cart items to a shopping list. ADD_TO_SHOPPING_LIST

Quick Order page cases:

CASE EVENT
If applicable: add a product from the “Add to cart” widget. ADD_TO_CART
If applicable: save cart items to a shopping list. ADD_TO_SHOPPING_LIST

Order Success page cases: | CASE | EVENT | | - | - | | Open the Order Success page | PAGE_LOAD with currency, order total, SKUs, prices, and quantities of purchased products. |

For a full list of available events, see the traceable-events-algolia readme file.

Common issues and solutions

This section common issues to event and solutions. Most solutions involve adding, changing, and fixing events on the project level.

Prerequisites

  1. Locate the page template or view that is used for the page with faulty events.
  2. On the project level, override the {% block eventTracker %} block in the template.

For details on the event configuration API, see the traceable-events-orchestrator README.

Issue: Event not triggering on user action

If an event isn’t firing, verify that the action, like click or change, is configured for the event, like PRODUCT_CLICK. Detailed steps:

  1. Check the configuration is set up for new and changed components.
{% block eventTracker %}
    {% set events = {
        list: events.list | merge([
            {
                event: 'NEEDED_EVENT_NAME', // e.g., PRODUCT_CLICK
                name: 'NEEDED_EVENT_LISTENER', // e.g., click/change
                triggers: [...event triggers data],
            },
        ]),
        data: events.data,
    } %}

    {{ parent() }}
{% endblock %}

Configuration for built-in components is provided by default. For more details, see API documentation.

  1. Check the Event Selector. CSS selectors are provided by default. If you changed selectors, update the configuration accordingly:
{% block eventTracker %}
    {% set events = {
        list: events.list | merge([
            {
                event: 'NEEDED_EVENT_NAME', // e.g., PRODUCT_CLICK
                name: 'NEEDED_EVENT_LISTENER', // e.g., click/change/load
                triggers: [
                    {
                        selector: 'new_css_selector_path', // The element selector to monitor
                        /* event data configuration */
                    },
                ],
            },
        ]),
        data: events.data,
    } %}

    {{ parent() }}
{% endblock %}

Issue: Incorrect event payload

You can view event payload in the console under Adapter Data:. If payload is incorrect, check and adjust static and dynamic data configuration.

  • Adjust static data in the eventTracker block:
{% block eventTracker %}
    {% set events = {
        list: events.list,
        data: events.data | merge({
          existing_key_to_override: New Data,
          new_key: New Data,
        }),
    } %}

    {{ parent() }}
{% endblock %}
  • Adjust the configuration for dynamic data for the needed triggers.
Dynamic data configuration example
{% set events = {
    list: events.list | merge([{
        event: 'EVENT_EXAMPLE',
        name: 'click',
        triggers: [
            {
                selector: '.js-related-products',
                groupAs: {
                    key: 'relatedProducts', // Group data under the 'relatedProducts' key
                    toArray: true, // Convert the grouped data into an array format
                },
                data: {
                    details: {
                        selector: 'self', // Look for the 'details' attribute within the current element
                        flatten: true, // Flatten the structure of the object to simplify it
                    },
                    name: {
                        selector: '.product-name', // Search for an element with the 'product-name' class within the monitored element
                        attribute: 'price', // Use the 'price' attribute as the value; if absent, fallback to the element's text content
                    },
                    price: {
                        value: 'static value', // Assign a fixed value to the 'price' attribute
                    },
                    attributes: {
                        selector: '.attribute-selector',
                        multi: true, // Collect all matching elements and return their data as an array
                    },
                    metadata: {
                        multi: true,
                        selector: '.metadata-row',
                        composed: { // Create nested structures for more detailed data gathering and start searching elements from `.metadata-row` selector.
                            brand: {
                                selector: '.product-brand',
                                attribute: 'textContent'
                            },
                            category: {
                                selector: '.product-category',
                            },
                        },
                    },
                },
            },
        ],
    }]),
    data: events.data,
} %}

{# Expected transformed data format in the console:
  {
      ...global data/event metadata,
      relatedProducts: {
          // Flattened data from the 'details' attribute
          name: VALUE, // The value taken from the 'name' selector or attribute
          price: 'static value', // The fixed 'price' value
          attributes: [VALUE, VALUE, VALUE, ...], // Array of values collected from elements matching '.attribute-selector'
          metadata: [
              {
                  brand: VALUE, // 'brand' data extracted from the '.metadata-row .product-brand' element
                  category: VALUE, // 'category' data from the '.metadata-row .product-category' element
              },
              {
                  brand: VALUE, // 'brand' data extracted from the '.metadata-row .product-brand' element
                  category: VALUE, // 'category' data from the '.metadata-row .product-category' element
              },
              ...
          ]
      }
  }
#}

For more information, see API documentation.

Examples of integration into demo shops

For examples of Algolia Personalization integration into demo shops, see the following PRs:

Update privacy policy

With Algolia Personalization, user data is tracked and sent to Algolia. To ensure user privacy, you need to update your privacy policy and proactively collect users’ consent for data tracking. The data consent popup can be similar to the following:

>User Data analytics

To enhance your experience, we use data and analytics to understand how you interact with our site.
By accepting, you allow us to capture anonymous events for personalization, analysis, and continuous improvement of your experience on our platform.

Verify the installation

  1. Deploy to a testing environment.

  2. In the Back Office, go to Apps and verify that Algolia is connected and configured.

  3. If you’ve previously been using the Algolia App, in the Back Office, disconnect and connect it again with the same Algolia credentials. This action updates your project config to be able to send events to Algolia.

  4. On the Storefront, do the following as a guest user:

  • Search products
  • Filter search results
  • From search results, go to a product’s page
  • Add products to cart
  • Place orders
  1. Repeat step 3 as a logged-in user.
  2. In the Algolia Dashboard, go to Data Sources>Events. Make sure the events you’ve triggered are displayed.

Configure Algolia features

When your indexes have enough data, such as unique searches and events, you can start configuring Personalization, Dynamic Re-Ranking, and Query Categorization features.

When updating the configuration of Algolia features, make sure to A/B test them before rolling out globally. A/B testing lets you test configuration and see how it affects conversion rates for a limited audience. For more details, see A/B Testing.