diff --git a/examples/events/custom-event-override.md b/examples/events/custom-event-override.md index f078d5ff..88439871 100644 --- a/examples/events/custom-event-override.md +++ b/examples/events/custom-event-override.md @@ -1,9 +1,13 @@ ## event overrides -For any event with a set `customContext`, the collector overrides joins fields set in the relevant contexts with fields in `customContext`. The use case for overrides is when a developer wants to reuse and extend contexts set by other parts of the page in already supporte events. +For any event with a set `customContext`, the collector overrides joins fields set in the relevant contexts with fields in `customContext`. The use case for overrides is when a developer wants to reuse and extend contexts set by other parts of the page in already supported events. Event overrides are only applicable when forwarding to AEP. They are not applied to Adobe Commerce and Sensei analytics events. Additional info in [README](../../packages/storefront-events-collector/README.md) +> [!NOTE] +> When augmenting `productListItems` with custom attributes in AEP event payloads, match products using SKU. This requirement does not apply to `product-page-view` events. + + ### 🔧 Usage ```javascript @@ -75,3 +79,26 @@ mse.publish.pageView({ }, }); ``` + +### Example 4 - adding custom context to productListItems with events with multiple products + +```javascript +const mse = window.magentoStorefrontEvents; + +mse.context.setCustom({ + productListItems: [ + { + SKU: "24-WB01", //Match SKU to override correct product in event payload + productCategory: "Hand Bag", //Custom attribute added to event payload + name: "Strive Handbag (CustomName)" //Override existing attribute with custom value in event payload + }, + { + SKU: "24-MB04", + productCategory: "Backpack Bag", + name: "Strive Backpack (CustomName)" + }, + ], +}); + +mse.publish.shoppingCartView(); +``` \ No newline at end of file diff --git a/packages/storefront-events-collector/src/handlers/account/signOutAEP.ts b/packages/storefront-events-collector/src/handlers/account/signOutAEP.ts index b87456c8..d6d47504 100644 --- a/packages/storefront-events-collector/src/handlers/account/signOutAEP.ts +++ b/packages/storefront-events-collector/src/handlers/account/signOutAEP.ts @@ -11,7 +11,7 @@ const aepHandler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.userAccount = { diff --git a/packages/storefront-events-collector/src/handlers/checkout/placeOrderAEP.ts b/packages/storefront-events-collector/src/handlers/checkout/placeOrderAEP.ts index c1d81343..c20f4bc2 100644 --- a/packages/storefront-events-collector/src/handlers/checkout/placeOrderAEP.ts +++ b/packages/storefront-events-collector/src/handlers/checkout/placeOrderAEP.ts @@ -23,7 +23,7 @@ const aepHandler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.commerce = payload.commerce || {}; diff --git a/packages/storefront-events-collector/src/handlers/page/viewAEP.ts b/packages/storefront-events-collector/src/handlers/page/viewAEP.ts index 4dbcde9d..e8705582 100644 --- a/packages/storefront-events-collector/src/handlers/page/viewAEP.ts +++ b/packages/storefront-events-collector/src/handlers/page/viewAEP.ts @@ -12,7 +12,7 @@ const aepHandler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.web = payload.web || {}; diff --git a/packages/storefront-events-collector/src/handlers/product/addToCartAEP.ts b/packages/storefront-events-collector/src/handlers/product/addToCartAEP.ts index e479196f..38c5679a 100644 --- a/packages/storefront-events-collector/src/handlers/product/addToCartAEP.ts +++ b/packages/storefront-events-collector/src/handlers/product/addToCartAEP.ts @@ -15,7 +15,7 @@ const aepHandler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.commerce = payload.commerce || {}; diff --git a/packages/storefront-events-collector/src/handlers/product/removeFromCartAEP.ts b/packages/storefront-events-collector/src/handlers/product/removeFromCartAEP.ts index bfd61a63..c22657ee 100644 --- a/packages/storefront-events-collector/src/handlers/product/removeFromCartAEP.ts +++ b/packages/storefront-events-collector/src/handlers/product/removeFromCartAEP.ts @@ -15,7 +15,7 @@ const aepHandler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.commerce = payload.commerce || {}; diff --git a/packages/storefront-events-collector/src/handlers/product/viewAEP.ts b/packages/storefront-events-collector/src/handlers/product/viewAEP.ts index 41f20ecf..374ef188 100644 --- a/packages/storefront-events-collector/src/handlers/product/viewAEP.ts +++ b/packages/storefront-events-collector/src/handlers/product/viewAEP.ts @@ -13,7 +13,7 @@ const aepHandler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } const productListItemFromCustomContext: ProductListItem | undefined = payload.productListItems?.length diff --git a/packages/storefront-events-collector/src/handlers/requisitionList/addToRequisitionListAEP.ts b/packages/storefront-events-collector/src/handlers/requisitionList/addToRequisitionListAEP.ts index 6403921b..276ee4bd 100644 --- a/packages/storefront-events-collector/src/handlers/requisitionList/addToRequisitionListAEP.ts +++ b/packages/storefront-events-collector/src/handlers/requisitionList/addToRequisitionListAEP.ts @@ -23,7 +23,7 @@ const handler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.commerce = payload.commerce || {}; diff --git a/packages/storefront-events-collector/src/handlers/requisitionList/createRequisitionListAEP.ts b/packages/storefront-events-collector/src/handlers/requisitionList/createRequisitionListAEP.ts index 264d8b21..f7f58e6b 100644 --- a/packages/storefront-events-collector/src/handlers/requisitionList/createRequisitionListAEP.ts +++ b/packages/storefront-events-collector/src/handlers/requisitionList/createRequisitionListAEP.ts @@ -14,7 +14,7 @@ const handler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.commerce = payload.commerce || {}; diff --git a/packages/storefront-events-collector/src/handlers/requisitionList/deleteRequisitionListAEP.ts b/packages/storefront-events-collector/src/handlers/requisitionList/deleteRequisitionListAEP.ts index 51257188..86e04e81 100644 --- a/packages/storefront-events-collector/src/handlers/requisitionList/deleteRequisitionListAEP.ts +++ b/packages/storefront-events-collector/src/handlers/requisitionList/deleteRequisitionListAEP.ts @@ -14,7 +14,7 @@ const handler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.commerce = payload.commerce || {}; diff --git a/packages/storefront-events-collector/src/handlers/requisitionList/removeFromRequisitionListAEP.ts b/packages/storefront-events-collector/src/handlers/requisitionList/removeFromRequisitionListAEP.ts index 790fbf66..0ce9262f 100644 --- a/packages/storefront-events-collector/src/handlers/requisitionList/removeFromRequisitionListAEP.ts +++ b/packages/storefront-events-collector/src/handlers/requisitionList/removeFromRequisitionListAEP.ts @@ -22,7 +22,7 @@ const aepHandler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.commerce = payload.commerce || {}; diff --git a/packages/storefront-events-collector/src/handlers/search/searchRequestSentAEP.ts b/packages/storefront-events-collector/src/handlers/search/searchRequestSentAEP.ts index d7a58b63..ab8f7ec1 100644 --- a/packages/storefront-events-collector/src/handlers/search/searchRequestSentAEP.ts +++ b/packages/storefront-events-collector/src/handlers/search/searchRequestSentAEP.ts @@ -18,7 +18,7 @@ const handler = async (event: Event): Promise => { if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } const sortFromCtx: SearchSort[] = (searchInputCtx?.data.sort as SearchSort[]) ?? []; diff --git a/packages/storefront-events-collector/src/handlers/search/searchResponseReceivedAEP.ts b/packages/storefront-events-collector/src/handlers/search/searchResponseReceivedAEP.ts index c701eae6..6cb0f449 100644 --- a/packages/storefront-events-collector/src/handlers/search/searchResponseReceivedAEP.ts +++ b/packages/storefront-events-collector/src/handlers/search/searchResponseReceivedAEP.ts @@ -29,7 +29,7 @@ const handler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.siteSearch = { diff --git a/packages/storefront-events-collector/src/handlers/shoppingCart/initiateCheckoutAEP.ts b/packages/storefront-events-collector/src/handlers/shoppingCart/initiateCheckoutAEP.ts index d462f4d6..9975999b 100644 --- a/packages/storefront-events-collector/src/handlers/shoppingCart/initiateCheckoutAEP.ts +++ b/packages/storefront-events-collector/src/handlers/shoppingCart/initiateCheckoutAEP.ts @@ -14,7 +14,7 @@ const handler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.commerce = payload.commerce || {}; diff --git a/packages/storefront-events-collector/src/handlers/shoppingCart/openCartAEP.ts b/packages/storefront-events-collector/src/handlers/shoppingCart/openCartAEP.ts index ed7a6f8f..6ee74776 100644 --- a/packages/storefront-events-collector/src/handlers/shoppingCart/openCartAEP.ts +++ b/packages/storefront-events-collector/src/handlers/shoppingCart/openCartAEP.ts @@ -13,7 +13,7 @@ const handler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.commerce = payload.commerce || {}; diff --git a/packages/storefront-events-collector/src/handlers/shoppingCart/viewAEP.ts b/packages/storefront-events-collector/src/handlers/shoppingCart/viewAEP.ts index dca8e3c8..672ea6e6 100644 --- a/packages/storefront-events-collector/src/handlers/shoppingCart/viewAEP.ts +++ b/packages/storefront-events-collector/src/handlers/shoppingCart/viewAEP.ts @@ -15,7 +15,7 @@ const aepHandler = async (event: Event): Promise => { let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.commerce = payload.commerce || {}; diff --git a/packages/storefront-events-collector/src/utils/aep/account.ts b/packages/storefront-events-collector/src/utils/aep/account.ts index f7cbe079..044bb74e 100644 --- a/packages/storefront-events-collector/src/utils/aep/account.ts +++ b/packages/storefront-events-collector/src/utils/aep/account.ts @@ -7,7 +7,7 @@ const createAccountPayload = (customContext: any, accountContext: sdkSchemas.Acc let payload: BeaconSchema = {}; if (customContext && Object.keys(customContext as BeaconSchema).length !== 0) { // override payload on custom context - payload = customContext as BeaconSchema; + payload = { ...customContext } as BeaconSchema; } payload.person = payload.person || {}; diff --git a/packages/storefront-events-collector/src/utils/aep/productListItems.ts b/packages/storefront-events-collector/src/utils/aep/productListItems.ts index 719523d1..c068cf14 100644 --- a/packages/storefront-events-collector/src/utils/aep/productListItems.ts +++ b/packages/storefront-events-collector/src/utils/aep/productListItems.ts @@ -28,19 +28,19 @@ const createProductListItems = ( if (requisitionListItemsContext) { requisitionListItemsContext.items?.map((item) => { - const productListItemFromCustomContext = productListFromCustomContextMap.get(item.sku); - const requisitionListItem = { - SKU: item.sku, - name: productListItemFromCustomContext?.name || item.name, - quantity: productListItemFromCustomContext?.quantity || Number(item.quantity), - priceTotal: - productListItemFromCustomContext?.priceTotal || - formatPrice((Number(item.pricing?.regularPrice) || 0) * Number(item.quantity)), - currencyCode: - productListItemFromCustomContext?.currencyCode || - (item.pricing?.currencyCode ?? storefrontContext.storeViewCurrencyCode), - selectedOptions: productListItemFromCustomContext?.selectedOptions || item.selectedOptions, - }; + const requisitionListItem = productListFromCustomContextMap.get(item.sku) || {}; + + // custom SKU is not supported as it is used as identification for merging + requisitionListItem.SKU = item.sku; + requisitionListItem.name = requisitionListItem?.name || item.name; + requisitionListItem.quantity = requisitionListItem?.quantity || Number(item.quantity); + requisitionListItem.priceTotal = + requisitionListItem?.priceTotal || + formatPrice((Number(item.pricing?.regularPrice) || 0) * Number(item.quantity)); + requisitionListItem.currencyCode = + requisitionListItem?.currencyCode || + (item.pricing?.currencyCode ?? storefrontContext.storeViewCurrencyCode); + requisitionListItem.selectedOptions = requisitionListItem?.selectedOptions || item.selectedOptions; returnList.push(requisitionListItem); }); } else { @@ -53,26 +53,21 @@ const createProductListItems = ( }); }); - const productListItemFromCustomContext = productListFromCustomContextMap.get(item.product?.sku); + const productListItem = productListFromCustomContextMap.get(item.product?.sku) || {}; - const productListItem: ProductListItem = { - SKU: item.product?.sku, - name: productListItemFromCustomContext?.name || item.product?.name, - quantity: productListItemFromCustomContext?.quantity || item.quantity, - priceTotal: - productListItemFromCustomContext?.priceTotal || - formatPrice(item.prices?.price?.value * item.quantity) || - 0, - productImageUrl: productListItemFromCustomContext?.productImageUrl || item.product.mainImageUrl, - currencyCode: - productListItemFromCustomContext?.currencyCode || - (item.prices?.price?.currency ?? storefrontContext.storeViewCurrencyCode), - discountAmount: - productListItemFromCustomContext?.discountAmount || - item.discountAmount || - getDiscountAmount(item.product), - selectedOptions: productListItemFromCustomContext?.selectedOptions || selectedOptions, - }; + // custom SKU is not supported as it is used as identification for merging + productListItem.SKU = item.product?.sku; + productListItem.name = productListItem.name || item.product?.name; + productListItem.quantity = productListItem?.quantity || item.quantity; + productListItem.priceTotal = + productListItem?.priceTotal || formatPrice(item.prices?.price?.value * item.quantity) || 0; + productListItem.productImageUrl = productListItem?.productImageUrl || item.product.mainImageUrl; + productListItem.currencyCode = + productListItem?.currencyCode || + (item.prices?.price?.currency ?? storefrontContext.storeViewCurrencyCode); + productListItem.discountAmount = + productListItem?.discountAmount || item.discountAmount || getDiscountAmount(item.product); + productListItem.selectedOptions = productListItem?.selectedOptions || selectedOptions; returnList.push(productListItem); }); diff --git a/packages/storefront-events-collector/tests/handlers/shoppingCart/shoppingCartViewAep.test.ts b/packages/storefront-events-collector/tests/handlers/shoppingCart/shoppingCartViewAep.test.ts new file mode 100644 index 00000000..80c7f386 --- /dev/null +++ b/packages/storefront-events-collector/tests/handlers/shoppingCart/shoppingCartViewAep.test.ts @@ -0,0 +1,138 @@ +jest.mock("../../../src/alloy"); +import { Event } from "@adobe/magento-storefront-events-sdk/dist/types/types/events"; +import { sendEvent } from "../../../src/alloy"; +import { shoppingCartViewHandlerAEP } from "../../../src/handlers"; +import { mockEvent } from "../../utils/mocks"; + +const AEPevent = { ...mockEvent }; +delete AEPevent.eventInfo.customContext; + +test("correctly structures AEP event and calls alloy.sendEvent", () => { + shoppingCartViewHandlerAEP(mockEvent); + + expect(sendEvent).toHaveBeenCalledTimes(1); + + expect(sendEvent).toHaveBeenCalledWith( + { + commerce: { + cart: { + cartID: "111111", + }, + productListViews: { + value: 1, + }, + commerceScope: { + environmentID: "aaaaaa", + storeCode: "magento", + storeViewCode: "default", + websiteCode: "website", + }, + order: { + discountAmount: 0, + }, + }, + productListItems: [ + { + SKU: "ts001", + name: "T-Shirt", + quantity: 1, + priceTotal: 20, + productImageUrl: undefined, + currencyCode: "USD", + discountAmount: 0, + selectedOptions: [], + }, + { + SKU: "h001", + name: "Hoodie", + quantity: 1, + priceTotal: 50, + productImageUrl: undefined, + currencyCode: "USD", + discountAmount: 0, + selectedOptions: [], + }, + ], + _id: undefined, + eventType: "commerce.productListViews", + }, + mockEvent, + ); +}); + +test("correctly structures AEP event with customContext and calls alloy.sendEvent", () => { + const customContext = { + commerce: { + order: { + couponCode: "couponCode123", + }, + }, + productListItems: [ + { + SKU: "ts001", + name: "Custom Product Name", + categoryId: "customCat001", + }, + ], + }; + const mockedAEPevent = { + event: "shopping-cart-view", + eventInfo: { + ...AEPevent.eventInfo, + customContext, + }, + } as Event; + + shoppingCartViewHandlerAEP(mockedAEPevent); + + expect(sendEvent).toHaveBeenCalledTimes(1); + + expect(sendEvent).toHaveBeenCalledWith( + { + commerce: { + cart: { + cartID: "111111", + }, + productListViews: { + value: 1, + }, + commerceScope: { + environmentID: "aaaaaa", + storeCode: "magento", + storeViewCode: "default", + websiteCode: "website", + }, + order: { + discountAmount: 0, + couponCode: customContext.commerce.order.couponCode, + }, + }, + productListItems: [ + { + SKU: "ts001", + name: customContext.productListItems[0].name, + quantity: 1, + priceTotal: 20, + productImageUrl: undefined, + currencyCode: "USD", + discountAmount: 0, + selectedOptions: [], + categoryId: customContext.productListItems[0].categoryId, + }, + { + SKU: "h001", + name: "Hoodie", + quantity: 1, + priceTotal: 50, + productImageUrl: undefined, + currencyCode: "USD", + discountAmount: 0, + selectedOptions: [], + }, + ], + _id: undefined, + eventType: "commerce.productListViews", + }, + mockedAEPevent, + ); +});