Skip to content

Commit

Permalink
Add new activities to Einstein API (#714)
Browse files Browse the repository at this point in the history
* Add new endpoints to Einstein API

* Update pages to send viewPage activity

* Update checkout to send beginCheckout activity

* Update PDP to send viewSearch and viewCategory activities

* Address duplicate activity calls

* Update PLP to send clickSearch and clickCategory activities

* Include realm. Also include path location on viewPage

* Begin fixing tests

* Move useEffect to before the redirect to fix an error.

* Mock Einstein in product listing test

* Fix lint

* Add tests for new activities

* Add a guard for setting realm and remove correlationId for now.

* Simplify activity logic in PLP

* Fix tests

* Fix lint

* Remove pathname from home page viewPage

* Re-add pathname to home page.

* Refactor: use constants to represent checkout steps

* Add checkoutStep activity to Einstein API

Co-authored-by: Ben Chypak <[email protected]>
  • Loading branch information
vcua-mobify and bendvc authored Sep 22, 2022
1 parent fb7f4c0 commit c5db22c
Show file tree
Hide file tree
Showing 23 changed files with 966 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,34 @@ class EinsteinAPI {
return {requestId: 'test-req-id', uuid: 'test-uuid'}
}

async sendViewSearch() {
return {requestId: 'test-req-id', uuid: 'test-uuid'}
}

async sendClickSearch() {
return {requestId: 'test-req-id', uuid: 'test-uuid'}
}

async sendViewCategory() {
return {requestId: 'test-req-id', uuid: 'test-uuid'}
}

async sendClickCategory() {
return {requestId: 'test-req-id', uuid: 'test-uuid'}
}

async sendViewPage() {
return {requestId: 'test-req-id', uuid: 'test-uuid'}
}

async sendBeginCheckout() {
return {requestId: 'test-req-id', uuid: 'test-uuid'}
}

async sendCheckoutStep() {
return {requestId: 'test-req-id', uuid: 'test-uuid'}
}

async sendViewReco() {
return {requestId: 'test-req-id', uuid: 'test-uuid'}
}
Expand Down
161 changes: 161 additions & 0 deletions packages/template-retail-react-app/app/commerce-api/einstein.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class EinsteinAPI {
console.warn('Missing `cookieId`. For optimal results this value must be defined.')
}

// The first part of the siteId is the realm
if (this.config.siteId) {
body.realm = this.config.siteId.split('-')[0]
}

return body
}

Expand Down Expand Up @@ -90,6 +95,105 @@ class EinsteinAPI {
return this.einsteinFetch(endpoint, method, body)
}

/**
* Tells the Einstein engine when a user views search results.
**/
async sendViewSearch(searchText, searchResults, args) {
const endpoint = `/activities/${this.config.siteId}/viewSearch`
const method = 'POST'

const products = searchResults.hits.map((product) => {
const {productId, sku = '', altId = '', altIdType = ''} = product
return {
id: productId,
sku,
altId,
altIdType
}
})

const body = {
searchText,
products,
...args
}

return this.einsteinFetch(endpoint, method, body)
}

/**
* Tells the Einstein engine when a user clicks on a search result.
**/
async sendClickSearch(searchText, product, args) {
const endpoint = `/activities/${this.config.siteId}/clickSearch`
const method = 'POST'
const {productId, sku = '', altId = '', altIdType = ''} = product
const body = {
searchText,
product: {
id: productId,
sku,
altId,
altIdType
},
...args
}

return this.einsteinFetch(endpoint, method, body)
}

/**
* Tells the Einstein engine when a user views a category.
**/
async sendViewCategory(category, searchResults, args) {
const endpoint = `/activities/${this.config.siteId}/viewCategory`
const method = 'POST'

const products = searchResults.hits.map((product) => {
const {productId, sku = '', altId = '', altIdType = ''} = product
return {
id: productId,
sku,
altId,
altIdType
}
})

const body = {
category: {
id: category.id
},
products,
...args
}

return this.einsteinFetch(endpoint, method, body)
}

/**
* Tells the Einstein engine when a user clicks a product from the category page.
* Not meant to be used when the user clicks a category from the nav bar.
**/
async sendClickCategory(category, product, args) {
const endpoint = `/activities/${this.config.siteId}/clickCategory`
const method = 'POST'
const {productId, sku = '', altId = '', altIdType = ''} = product
const body = {
category: {
id: category.id
},
product: {
id: productId,
sku,
altId,
altIdType
},
...args
}

return this.einsteinFetch(endpoint, method, body)
}

/**
* Tells the Einstein engine when a user views a set of recommendations
* https://developer.salesforce.com/docs/commerce/einstein-api/references#einstein-recommendations:Summary
Expand Down Expand Up @@ -132,6 +236,63 @@ class EinsteinAPI {
return this.einsteinFetch(endpoint, method, body)
}

/**
* Tells the Einstein engine when a user views a page.
* Use this only for pages where another activity does not fit. (ie. on the PDP, use viewProduct rather than this)
**/
async sendViewPage(path, args) {
const endpoint = `/activities/${this.config.siteId}/viewPage`
const method = 'POST'
const body = {
currentLocation: path,
...args
}

return this.einsteinFetch(endpoint, method, body)
}

/**
* Tells the Einstein engine when a user starts the checkout process.
**/
async sendBeginCheckout(basket, args) {
const endpoint = `/activities/${this.config.siteId}/beginCheckout`
const method = 'POST'
const products = basket.productItems.map((product) => {
const {productId, sku = '', price = '', quantity = ''} = product
return {
id: productId,
sku,
price,
quantity
}
})
const subTotal = basket.productSubTotal
const body = {
products: products,
amount: subTotal,
...args
}

return this.einsteinFetch(endpoint, method, body)
}

/**
* Tells the Einstein engine when a user reaches the given step during checkout.
* https://developer.salesforce.com/docs/commerce/einstein-api/references#einstein-recommendations:Summary
**/
async sendCheckoutStep(stepName, stepNumber, basket, args) {
const endpoint = `/activities/${this.config.siteId}/checkoutStep`
const method = 'POST'
const body = {
stepName,
stepNumber,
basketId: basket.basketId,
...args
}

return this.einsteinFetch(endpoint, method, body)
}

/**
* Tells the Einstein engine when a user adds an item to their cart.
* https://developer.salesforce.com/docs/commerce/einstein-api/references#einstein-recommendations:Summary
Expand Down
133 changes: 127 additions & 6 deletions packages/template-retail-react-app/app/commerce-api/einstein.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
mockAddToCartProduct,
mockGetZoneRecommendationsResponse,
mockProduct,
mockCategory,
mockSearchResults,
mockBasket,
mockRecommendationsResponse,
mockRecommenderDetails
} from './mocks/einstein-mock-data'
Expand Down Expand Up @@ -55,7 +58,125 @@ describe('EinsteinAPI', () => {
'x-cq-client-id': 'test-id'
},
body:
'{"product":{"id":"56736828M","sku":"","altId":"","altIdType":""},"cookieId":"test-usid"}'
'{"product":{"id":"56736828M","sku":"","altId":"","altIdType":""},"cookieId":"test-usid","realm":"test"}'
}
)
})

test('viewSearch sends expected api request', async () => {
const searchTerm = 'tie'
await einsteinApi.sendViewSearch(searchTerm, mockSearchResults)
expect(fetch).toHaveBeenCalledWith(
'http://localhost/test-path/v3/activities/test-site-id/viewSearch',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-cq-client-id': 'test-id'
},
body:
'{"searchText":"tie","products":[{"id":"25752986M","sku":"","altId":"","altIdType":""},{"id":"25752235M","sku":"","altId":"","altIdType":""},{"id":"25752218M","sku":"","altId":"","altIdType":""},{"id":"25752981M","sku":"","altId":"","altIdType":""}],"cookieId":"test-usid","realm":"test"}'
}
)
})

test('viewCategory sends expected api request', async () => {
await einsteinApi.sendViewCategory(mockCategory, mockSearchResults)
expect(fetch).toHaveBeenCalledWith(
'http://localhost/test-path/v3/activities/test-site-id/viewCategory',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-cq-client-id': 'test-id'
},
body:
'{"category":{"id":"mens-accessories-ties"},"products":[{"id":"25752986M","sku":"","altId":"","altIdType":""},{"id":"25752235M","sku":"","altId":"","altIdType":""},{"id":"25752218M","sku":"","altId":"","altIdType":""},{"id":"25752981M","sku":"","altId":"","altIdType":""}],"cookieId":"test-usid","realm":"test"}'
}
)
})

test('clickSearch sends expected api request', async () => {
const searchTerm = 'tie'
const clickedProduct = mockSearchResults.hits[0]
await einsteinApi.sendClickSearch(searchTerm, clickedProduct)
expect(fetch).toHaveBeenCalledWith(
'http://localhost/test-path/v3/activities/test-site-id/clickSearch',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-cq-client-id': 'test-id'
},
body:
'{"searchText":"tie","product":{"id":"25752986M","sku":"","altId":"","altIdType":""},"cookieId":"test-usid","realm":"test"}'
}
)
})

test('clickCategory sends expected api request', async () => {
const clickedProduct = mockSearchResults.hits[0]
await einsteinApi.sendClickCategory(mockCategory, clickedProduct)
expect(fetch).toHaveBeenCalledWith(
'http://localhost/test-path/v3/activities/test-site-id/clickCategory',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-cq-client-id': 'test-id'
},
body:
'{"category":{"id":"mens-accessories-ties"},"product":{"id":"25752986M","sku":"","altId":"","altIdType":""},"cookieId":"test-usid","realm":"test"}'
}
)
})

test('viewPage sends expected api request', async () => {
const path = '/'
await einsteinApi.sendViewPage(path)
expect(fetch).toHaveBeenCalledWith(
'http://localhost/test-path/v3/activities/test-site-id/viewPage',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-cq-client-id': 'test-id'
},
body: '{"currentLocation":"/","cookieId":"test-usid","realm":"test"}'
}
)
})

test('beginCheckout sends expected api request', async () => {
await einsteinApi.sendBeginCheckout(mockBasket)
expect(fetch).toHaveBeenCalledWith(
'http://localhost/test-path/v3/activities/test-site-id/beginCheckout',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-cq-client-id': 'test-id'
},
body:
'{"products":[{"id":"682875719029M","sku":"","price":29.99,"quantity":1}],"amount":29.99,"cookieId":"test-usid","realm":"test"}'
}
)
})

test('checkouStep sends expected api request', async () => {
const checkoutStepName = 'CheckoutStep'
const checkoutStep = 0
await einsteinApi.sendCheckoutStep(checkoutStepName, checkoutStep, mockBasket)
expect(fetch).toHaveBeenCalledWith(
'http://localhost/test-path/v3/activities/test-site-id/checkoutStep',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-cq-client-id': 'test-id'
},
body:
'{"stepName":"CheckoutStep","stepNumber":0,"basketId":"f6bbeee30fb93c2f94213f60f8","cookieId":"test-usid","realm":"test"}'
}
)
})
Expand All @@ -71,7 +192,7 @@ describe('EinsteinAPI', () => {
'x-cq-client-id': 'test-id'
},
body:
'{"products":[{"id":"883360544021M","sku":"","price":155,"quantity":1}],"cookieId":"test-usid"}'
'{"products":[{"id":"883360544021M","sku":"","price":155,"quantity":1}],"cookieId":"test-usid","realm":"test"}'
}
)
})
Expand All @@ -87,7 +208,7 @@ describe('EinsteinAPI', () => {
'x-cq-client-id': 'test-id'
},
body:
'{"recommenderName":"testRecommender","__recoUUID":"883360544021M","product":{"id":"56736828M","sku":"","altId":"","altIdType":""},"cookieId":"test-usid"}'
'{"recommenderName":"testRecommender","__recoUUID":"883360544021M","product":{"id":"56736828M","sku":"","altId":"","altIdType":""},"cookieId":"test-usid","realm":"test"}'
}
)
})
Expand All @@ -103,7 +224,7 @@ describe('EinsteinAPI', () => {
'x-cq-client-id': 'test-id'
},
body:
'{"recommenderName":"testRecommender","__recoUUID":"883360544021M","products":{"id":"test-reco"},"cookieId":"test-usid"}'
'{"recommenderName":"testRecommender","__recoUUID":"883360544021M","products":{"id":"test-reco"},"cookieId":"test-usid","realm":"test"}'
}
)
})
Expand Down Expand Up @@ -136,7 +257,7 @@ describe('EinsteinAPI', () => {
'Content-Type': 'application/json',
'x-cq-client-id': 'test-id'
},
body: '{"cookieId":"test-usid"}'
body: '{"cookieId":"test-usid","realm":"test"}'
}
)

Expand Down Expand Up @@ -189,7 +310,7 @@ describe('EinsteinAPI', () => {
'Content-Type': 'application/json',
'x-cq-client-id': 'test-id'
},
body: '{"cookieId":"test-usid"}'
body: '{"cookieId":"test-usid","realm":"test"}'
}
)

Expand Down
Loading

0 comments on commit c5db22c

Please sign in to comment.