From d0554f4701515cf4542b532b8915b00d2565cb21 Mon Sep 17 00:00:00 2001 From: "Mark J. Becker" Date: Thu, 7 Mar 2024 11:56:08 +0100 Subject: [PATCH] Add storefront events to recommendations block --- .../product-recommendations.js | 60 ++++- .../acdl/schemas/recommendationsContext.json | 241 ++++++++++++++++++ .../acdl/schemas/recs-api-request-sent.json | 12 + .../schemas/recs-api-response-received.json | 12 + scripts/acdl/schemas/recs-item-click.json | 28 ++ .../schemas/recs-unit-impression-render.json | 19 ++ scripts/acdl/schemas/recs-unit-view.json | 24 ++ scripts/acdl/validate.js | 6 + 8 files changed, 399 insertions(+), 3 deletions(-) create mode 100644 scripts/acdl/schemas/recommendationsContext.json create mode 100644 scripts/acdl/schemas/recs-api-request-sent.json create mode 100644 scripts/acdl/schemas/recs-api-response-received.json create mode 100644 scripts/acdl/schemas/recs-item-click.json create mode 100644 scripts/acdl/schemas/recs-unit-impression-render.json create mode 100644 scripts/acdl/schemas/recs-unit-view.json diff --git a/blocks/product-recommendations/product-recommendations.js b/blocks/product-recommendations/product-recommendations.js index 6ca74cd493..57328dded0 100644 --- a/blocks/product-recommendations/product-recommendations.js +++ b/blocks/product-recommendations/product-recommendations.js @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ import { performCatalogServiceQuery } from '../../scripts/commerce.js'; const recommendationsQuery = `query GetRecommendations( @@ -24,6 +25,8 @@ const recommendationsQuery = `query GetRecommendations( images { url } + externalId + __typename } storefrontLabel totalProducts @@ -50,11 +53,17 @@ function renderPlaceholder(block) { `; } -function renderItem(product) { +function renderItem(unitId, product) { const urlKey = product.url.split('/').pop().replace('.html', ''); const image = product.images[0]?.url; - return document.createRange().createContextualFragment(`
+ const clickHandler = () => { + window.adobeDataLayer.push((dl) => { + dl.push({ event: 'recs-item-click', eventInfo: { ...dl.getState(), unitId, productId: parseInt(product.externalId, 10) || 0 } }); + }); + }; + + const item = document.createRange().createContextualFragment(``); + item.querySelector('a').addEventListener('click', clickHandler); + + return item; } function renderItems(block, recommendations) { @@ -74,6 +86,10 @@ function renderItems(block, recommendations) { return; } + window.adobeDataLayer.push((dl) => { + dl.push({ event: 'recs-unit-impression-render', eventInfo: { ...dl.getState(), unitId: recommendation.unitId } }); + }); + // Title block.querySelector('h2').textContent = recommendation.storefrontLabel; @@ -82,10 +98,38 @@ function renderItems(block, recommendations) { grid.innerHTML = ''; const { productsView } = recommendation; productsView.forEach((product) => { - grid.appendChild(renderItem(product)); + grid.appendChild(renderItem(recommendation.unitId, product)); + }); + + const inViewObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + window.adobeDataLayer.push((dl) => { + dl.push({ event: 'recs-unit-view', eventInfo: { ...dl.getState(), unitId: recommendation.unitId } }); + }); + inViewObserver.disconnect(); + } + }); }); + inViewObserver.observe(block); } +const mapUnit = (unit) => ({ + ...unit, + unitType: 'primary', + searchTime: 0, + primaryProducts: unit.totalProducts, + backupProducts: 0, + products: unit.productsView.map((product, index) => ({ + ...product, + rank: index, + score: 0, + productId: parseInt(product.externalId, 10) || 0, + type: '?', + queryType: product.__typename, + })), +}); + async function loadRecommendation(block, context) { // Only proceed if all required data is available if (!context.pageType @@ -108,8 +152,18 @@ async function loadRecommendation(block, context) { console.error('Error parsing product view history', e); } + window.adobeDataLayer.push((dl) => { + dl.push({ event: 'recs-api-request-sent', eventInfo: { ...dl.getState() } }); + }); + recommendationsPromise = performCatalogServiceQuery(recommendationsQuery, context); const { recommendations } = await recommendationsPromise; + + window.adobeDataLayer.push((dl) => { + dl.push({ recommendationsContext: { units: recommendations.results.map(mapUnit) } }); + dl.push({ event: 'recs-api-response-received', eventInfo: { ...dl.getState() } }); + }); + renderItems(block, recommendations); } diff --git a/scripts/acdl/schemas/recommendationsContext.json b/scripts/acdl/schemas/recommendationsContext.json new file mode 100644 index 0000000000..ce950a99f4 --- /dev/null +++ b/scripts/acdl/schemas/recommendationsContext.json @@ -0,0 +1,241 @@ +{ + "type": "object", + "properties": { + "units": { + "type": "array", + "items": { + "$ref": "#/definitions/RecommendationUnit" + } + } + }, + "definitions": { + "RecommendationUnit": { + "type": "object", + "properties": { + "unitId": { + "type": "string" + }, + "unitName": { + "type": "string" + }, + "unitType": { + "type": "string" + }, + "searchTime": { + "type": "number" + }, + "totalProducts": { + "type": "number" + }, + "primaryProducts": { + "type": "number" + }, + "backupProducts": { + "type": "number" + }, + "products": { + "type": "array", + "items": { + "$ref": "#/definitions/RecommendedProduct" + } + }, + "pagePlacement": { + "type": [ + "string", + "null" + ] + }, + "typeId": { + "type": "string" + }, + "yOffsetTop": { + "type": [ + "number", + "null" + ] + }, + "yOffsetBottom": { + "type": [ + "number", + "null" + ] + } + }, + "required": [ + "unitId", + "unitName", + "unitType", + "searchTime", + "totalProducts", + "primaryProducts", + "backupProducts", + "products", + "pagePlacement", + "typeId" + ] + }, + "RecommendedProduct": { + "type": "object", + "properties": { + "rank": { + "type": "number" + }, + "score": { + "type": "number" + }, + "sku": { + "type": "string" + }, + "name": { + "type": "string" + }, + "productId": { + "type": "number" + }, + "shortDescription": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "visibility": { + "type": "string" + }, + "categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "weight": { + "type": "number" + }, + "weightType": { + "type": [ + "string", + "null" + ] + }, + "currency": { + "type": "string" + }, + "image": { + "$ref": "#/definitions/Image" + }, + "smallImage": { + "$ref": "#/definitions/Image" + }, + "thumbnailImage": { + "$ref": "#/definitions/Image" + }, + "swatchImage": { + "type": [ + "string", + "null" + ] + }, + "url": { + "type": "string" + }, + "prices": { + "$ref": "#/definitions/Prices" + }, + "queryType": { + "type": "string" + } + }, + "required": [ + "rank", + "score", + "sku", + "name", + "productId", + "type", + "visibility", + "categories", + "weight", + "url", + "queryType" + ] + }, + "Image": { + "type": "object", + "properties": { + "label": { + "type": [ + "string", + "null" + ] + }, + "url": { + "type": "string" + } + }, + "required": [ + "url" + ] + }, + "Prices": { + "type": "object", + "properties": { + "maximum": { + "$ref": "#/definitions/Price" + }, + "minimum": { + "$ref": "#/definitions/Price" + } + }, + "required": [ + "maximum", + "minimum" + ] + }, + "Price": { + "type": "object", + "properties": { + "finalAdjustments": { + "type": "array", + "items": { + "$ref": "#/definitions/Adjustment" + } + }, + "final": { + "type": "number" + }, + "regular": { + "type": "number" + }, + "regularAdjustments": { + "type": "array", + "items": { + "$ref": "#/definitions/Adjustment" + } + } + }, + "required": [ + "finalAdjustments", + "final", + "regular", + "regularAdjustments" + ] + }, + "Adjustment": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "amount": { + "type": "number" + } + }, + "required": [ + "code", + "amount" + ] + } + } +} \ No newline at end of file diff --git a/scripts/acdl/schemas/recs-api-request-sent.json b/scripts/acdl/schemas/recs-api-request-sent.json new file mode 100644 index 0000000000..10070f8313 --- /dev/null +++ b/scripts/acdl/schemas/recs-api-request-sent.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "properties": { + "event": { + "type": "string", + "const": "recs-api-request-sent" + } + }, + "required": [ + "event" + ] +} \ No newline at end of file diff --git a/scripts/acdl/schemas/recs-api-response-received.json b/scripts/acdl/schemas/recs-api-response-received.json new file mode 100644 index 0000000000..1ef5e0e7b6 --- /dev/null +++ b/scripts/acdl/schemas/recs-api-response-received.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "properties": { + "event": { + "type": "string", + "const": "recs-api-response-received" + } + }, + "required": [ + "event" + ] +} \ No newline at end of file diff --git a/scripts/acdl/schemas/recs-item-click.json b/scripts/acdl/schemas/recs-item-click.json new file mode 100644 index 0000000000..7335c808b0 --- /dev/null +++ b/scripts/acdl/schemas/recs-item-click.json @@ -0,0 +1,28 @@ +{ + "type": "object", + "properties": { + "event": { + "type": "string", + "const": "recs-item-click" + }, + "eventInfo": { + "type": "object", + "properties": { + "unitId": { + "type": "string" + }, + "productId": { + "type": "number" + } + }, + "required": [ + "unitId", + "productId" + ] + } + }, + "required": [ + "event", + "eventInfo" + ] +} \ No newline at end of file diff --git a/scripts/acdl/schemas/recs-unit-impression-render.json b/scripts/acdl/schemas/recs-unit-impression-render.json new file mode 100644 index 0000000000..df64a20ce6 --- /dev/null +++ b/scripts/acdl/schemas/recs-unit-impression-render.json @@ -0,0 +1,19 @@ +{ + "type": "object", + "properties": { + "event": { + "type": "string", + "const": "recs-unit-impression-render" + }, + "eventInfo": { + "type": "object", + "properties": { + "unitId": { + "type": "string" + } + }, + "required": ["unitId"] + } + }, + "required": ["event", "eventInfo"] + } \ No newline at end of file diff --git a/scripts/acdl/schemas/recs-unit-view.json b/scripts/acdl/schemas/recs-unit-view.json new file mode 100644 index 0000000000..6f3c6ef047 --- /dev/null +++ b/scripts/acdl/schemas/recs-unit-view.json @@ -0,0 +1,24 @@ +{ + "type": "object", + "properties": { + "event": { + "type": "string", + "const": "recs-unit-view" + }, + "eventInfo": { + "type": "object", + "properties": { + "unitId": { + "type": "string" + } + }, + "required": [ + "unitId" + ] + } + }, + "required": [ + "event", + "eventInfo" + ] +} \ No newline at end of file diff --git a/scripts/acdl/validate.js b/scripts/acdl/validate.js index 5e9bbba8f1..0a87bda00c 100644 --- a/scripts/acdl/validate.js +++ b/scripts/acdl/validate.js @@ -9,6 +9,12 @@ const schemas = [ 'productContext', 'categoryContext', 'product-page-view', + 'recommendationsContext', + 'recs-api-request-sent', + 'recs-api-response-received', + 'recs-item-click', + 'recs-unit-impression-render', + 'recs-unit-view' ]; (await Promise.all( schemas.map(async schema => {