Usage Analytics

Administrators can leverage recorded Usage Analytics (UA) data to evaluate how end users interact with a search interface, identify content gaps, and improve relevancy by generating and examining dashboards and reports within the administration console (see Coveo Usage Analytics Overview). Moreover, Coveo Machine Learning (Coveo ML) features require UA data to function.

When used correctly, Atomic components take care of logging search and click events for you. This article covers various topics that you may find helpful if you require further customization.

  • As the Atomic library uses Coveo Headless to interface with Coveo, you can access Headless through Atomic in order to leverage advanced UA customizations not possible in Atomic components.

    Therefore, to better understand this article, we recommend familizaring yourself with some core concepts of the Headless library.

  • Take a look at the View Events article to understand how to implement a page view tracker to log view events. View event tracking requires the coveoua.js script rather than the Atomic or Headless libraries.

Modify the Metadata to Send With UA Events

It can be useful to add or modify metadata to send along with the standard UA events logged automatically. You can leverage the analyticsClientMiddleware property of an AnalyticsConfiguration to hook into an analytics event payload before it’s sent to the Coveo platform, as in the following example.

const analyticsClientMiddleware = (eventName, payload) => { 2
    if (payload.visitorId == "") { 3
        payload.customData['loggedIn'] = false          // new metadata field added
    }
    return payload;
};

(async () => {
        await customElements.whenDefined('atomic-search-interface');
        const searchInterface = document.querySelector('atomic-search-interface');
        await searchInterface.initialize({
            accessToken: '<ACCESS_TOKEN>',
            organizationId: '<ORGANIZATION_ID>',
            analytics: {
                analyticsClientMiddleware 1
            },
        })
})();
1 The analyticsClientMiddleware is a function that needs to be defined if we want to add or modify event data (see analytics.ts).
2 The function takes as input an eventName and the event payload.
3 Within this function, you can access the payload data and modify it. In the example above, we check to see if visitorId is an empty string. If this field is empty, we add a new field to customData and set its value to false.

Send Your Own Click Events

Click events are intended to record item view and preview actions, such as:

  • Opening a result link

  • Opening a result quick view

By and large, the standard Coveo Atomic result template components handle click events for you. You can however modify them or send your own, if needed.

Use the analyticsClientMiddleware Property

To customize your click events, we recommend using the analyticsClientMiddleware property and listening to the target action, as in the following example.

(async () => {
        await customElements.whenDefined('atomic-search-interface');
        const searchInterface = document.querySelector('atomic-search-interface');
        await searchInterface.initialize({
            accessToken: '<ACCESS_TOKEN>',
            organizationId: '<ORGANIZATION_ID>',
            analytics: {
                analyticsClientMiddleware: (eventName: string, payload: any) => {
                    if (payload.actionCause === 'documentOpen') { 1
                    const matchingResult = headlessEngine.state.search.results[payload.documentPosition - 1];
                    payload.customData['intent'] = matchingResult.raw['docsintent']; 2
                    }
                    return payload;
                };
            },
    })
})();
1 If a UA event is dispatched with the logDocumentOpen cause, add the target metadata.
2 You can access result item fields when logging metadata. Concretely, you can use the populated default item fields, plus the ones specified via the fieldsToInclude parameter in your atomic-search-interface component. You can inspect search request responses in your search interface to see the currently available fields:
Inspecting response fields

Use the InteractiveResult Controller

To send additional click events, we recommend using the Headless InteractiveResult controller, which can take care of extracting result item metadata and logging events correctly for you, as in the following custom Atomic component example.

<script type="module">
import { initializeBindings, resultContext } from "https://static.cloud.coveo.com/atomic/v1/index.esm.js";
import { buildInteractiveResult} from 'https://static.cloud.coveo.com/atomic/v1/headless/headless.esm.js';

class DocumentAuthor extends HTMLElement {
  shadow;
  result;
  initialized = false;

  async connectedCallback() {
    if (this.initialized) {
      return;
    }

    this.initialized = true;
    this.shadow = this.attachShadow({ mode: "closed" });

    try {
      this.result = await resultContext(this); 1
      this.bindings = await initializeBindings(this);
      this.interactiveResult = buildInteractiveResult(this.bindings.engine, { 2
        options: { result: this.result }
      });
      this.render();
    } catch (error) {
      console.error(error);
    }
  }

  render() {
    const author = this.result.raw["author"] || "anonymous";
    this.shadow.innerHTML = `<button>Written by: <b>${author}</b></button>`;
    this.shadow.querySelector('button').onclick = () => this.interactiveResult.select(); 3
  }
}

window.customElements.define("document-author", DocumentAuthor);
</script>
<!-- ... -->
<atomic-result-template>
  <template>
    <!-- ... -->
    <atomic-result-fields-list>
      <atomic-field-condition class="field" if-defined="author">
       <document-author></document-author> 4
      </atomic-field-condition>
    </atomic-result-fields-list>
  </template>
</atomic-result-template>
<!-- ... -->
1 Retrieve the active result item.
2 Use the buildInteractiveResult function to initialize an InteractiveResult controller.
3 Let the InteractiveResult controller handle click events for you.
4 Use your custom component in the target result template.

While it is also technically possible to send your own click events by dispatching ClickAnalyticsActions or GenericAnalyticsActions, we generally recommend against doing so because it’s very error prone. For UA reports and ML models to function properly, click events need to contain very specific metadata whose handling is best left to the Atomic and Headless libraries.

Send Your Own Search Events

Search events are intended to record end-user interactions that trigger queries, such as:

  • Submitting a search request from the search box

  • Selecting a facet value

In addition to the standard search events that Atomic components log automatically, you may want to send your own UA events to track specific end-user interactions in your search interface. You can do so by accessing Headless through Atomic and dispatching actions. Generally, we recommend using Headless controllers over dispatching actions directly. However, the latter is still possible and can be helpful in specific use cases that controllers don’t cover, such as when you need to send your own UA events.

Depending on your use case, there are two ways to send your own search events. A search event can either be sent via SearchAnalyticsActions or GenericAnalyticsActions. Both of these are action loaders.

SearchAnalyticsActions are for specific search events for which the cause is recognized by Coveo, such as logFacetClearAll or logInterfaceLoad. These events are utilized by Coveo ML.

GenericAnalyticsActions, on the other hand, allow you to send any type of custom search event via the logSearchEvent action creator. The cause of this event is unknown to the Coveo Platform and thus can’t be utilized by Coveo ML.

If you wish to use SearchAnalyticsActions, we recommend implementing logic such as in the following example. Here a button is configured so that everytime it’s clicked, a search request is triggered and a search event is logged.

import {
    loadSearchActions,loadSearchAnalyticsActions
} from 'https://static.cloud.coveo.com/atomic/v1/headless/headless.esm.js'; 1
(async () => {
    await customElements.whenDefined("atomic-search-interface");
    const searchInterface = document.querySelector("#search");

    await searchInterface.initialize({
        accessToken: '<ACCESS_TOKEN>',
        organizationId: '<ORGANIZATION_ID>',
    });
    // optional method to execute a query and display results right away
    searchInterface.executeFirstSearch();

    function triggerSearch() {
        const headlessEngine = searchInterface.engine; 2
        const {executeSearch} = loadSearchActions(headlessEngine); 3
        const {logInterfaceLoad} = loadSearchAnalyticsActions(headlessEngine); 4
        headlessEngine.dispatch(executeSearch(logInterfaceLoad())); 5
    }

    const button = document.getElementById("custom-button");
    button.addEventListener("click", triggerSearch);
})();
1 Import loadSearchActions and loadSearchAnalyticsActionsfrom the Headless package. This will allow you to create an action to execute a search query and create an action in order to log a search event.
2 After initializing the search interface, access and store the engine in a variable.
3 Get the executeSearch action creator that will allow you to execute a search query.
4 Get a specific action creator, logInterfaceLoad in this scenario. This is the specific analytics event you will log.
5 Dispatch an action to execute a search query with the search analytic action passed in as input, in order to log it.

Take a look at Custom events to see a code example of how to log a search event via GenericAnalyticsActions.

Send Custom Events

Custom events are intended to record end-user interactions that don’t trigger a query or open a query result, such as:

  • Updating end-user preferences

  • Changing the result list layout

GenericAnalyticsActions allow you to send any type of custom event via the logCustomEvent action creator. The cause of this event is unknown to the Coveo Platform and thus can not be utilized by Coveo ML.

If you wish to use GenericAnalyticsActions, we recommend implementing logic such as in the following example.

import {
    loadGenericAnalyticsActions
} from 'https://static.cloud.coveo.com/atomic/v1/headless/headless.esm.js'; 1
(async () => {
    await customElements.whenDefined("atomic-search-interface");
    const searchInterface = document.querySelector("#search");

    await searchInterface.initialize({
        accessToken: '<ACCESS_TOKEN>',
        organizationId: '<ORGANIZATION_ID>',
    });
    // optional method to execute a query and display results right away
    searchInterface.executeFirstSearch();

    function customEvent() {
        const headlessEngine = searchInterface.engine; 2
        const {logCustomEvent} = loadGenericAnalyticsActions(headlessEngine); 3

        const payload = { 4
            evt: "<EVT>",
            type:"<TYPE>"
        }
        headlessEngine.dispatch(logCustomEvent(payload)); 5
    }
    const button = document.getElementById("custom-button");
    button.addEventListener("click", customEvent);
})();
1 Import loadGenericAnalyticsActions from the Headless package. This will allow you to return a dictionary of possible generic analytics action creators.
2 After initializing the search interface, access and store the engine in a variable.
3 Get a specific action creator, logCustomEvent in this scenario.
4 Create a payload object to be sent to the Coveo Platform when logging a custom event. This payload will describe the details of which event led to the action being triggered.
5 Dispatch the action to log the custom event.

Send Events Externally

Use analyticsClientMiddleware

If you want to log UA events to an external service, such as Google Analytics, we recommend leveraging the analyticsClientMiddleware property in your AnalyticsConfiguration to hook into an analytics event payload, as in the following example.

const pushToGoogleDataLayer = (payload: Record<string, unknown>) => {
  // For implementation details, see the Google documentation
  // (https://developers.google.com/tag-platform/tag-manager/web/datalayer#datalayer)
};
// ...
(async () => {
        await customElements.whenDefined('atomic-search-interface');
        const searchInterface = document.querySelector('atomic-search-interface');
        await searchInterface.initialize({
            accessToken: '<ACCESS_TOKEN>',
            organizationId: '<ORGANIZATION_ID>',
            analytics: {
                analyticsClientMiddleware: (eventType, payload) => {
                    pushToGoogleDataLayer(payload);
                    return payload;
                },
            }
        }),
})();

Anonymize UA Data

By default, the Usage Analytics Write API will extract the name and userDisplayName, if present, from the search token. If the users of your search interface are authenticated, you may want to hash their identities in order to ensure that they can’t be clearly identified in UA data. You can do so when initializing an engine instance by setting the anonymous property of the AnalyticsConfiguration as in the example below.

(async () => {
        await customElements.whenDefined('atomic-search-interface');
        const searchInterface = document.querySelector('atomic-search-interface');
        await searchInterface.initialize({
            accessToken: '<ACCESS_TOKEN>',
            organizationId: '<ORGANIZATION_ID>',
            analytics: {
                anonymous: true
            }),
})();

Disable and Enable Analytics

Coveo UA uses the coveo_visitorId cookie to track individual users and sessions. When implementing a cookie policy, you may need to disable UA tracking for end-users under specific circumstances (e.g., when a user decides to disable functionality cookies).

To do so, you can use the analytics property of the atomic-search-interface. Setting this property to false will also clear all Coveo UA-related information stored in the browser (coveo_visitorId cookie, action history, etc.).

To re-enable UA tracking, you can set this property to true.

<atomic-search-interface analytics="false">

// or even
const searchInterface = document.querySelector('atomic-search-interface');
// ... init and later on
searchInterface.analytics = false;

To understand how Coveo Usage Analytics tracks users and sessions, see What’s a user visit?.