Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,18 +386,18 @@ To log commerce events:

```js
const product = new MParticle.Product('Test product for cart', '1234', 19.99);
const transactionAttributes = new MParticle.TransactionAttributes(
'Test transaction id'
);
const event = MParticle.CommerceEvent.createProductActionEvent(
MParticle.ProductActionType.AddToCart,
[product],
transactionAttributes
[product]
);

MParticle.logCommerceEvent(event);
```

Transaction attributes are optional for product actions such as AddToCart.
Purchase and Refund events must include `TransactionAttributes` with a unique
transaction ID.

```js
const promotion = new MParticle.Promotion(
'Test promotion id',
Expand Down
20 changes: 9 additions & 11 deletions android/src/main/java/com/mparticle/react/MParticleModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -645,16 +645,14 @@ class MParticleModule(
val product = convertProduct(productMap) ?: return null
val transactionAttributesMap = map.getMap("transactionAttributes")
val transactionAttributes = convertTransactionAttributes(transactionAttributesMap)
val builder =
transactionAttributes?.let {
CommerceEvent.Builder(productAction, product).transactionAttributes(it)
}
val builder = CommerceEvent.Builder(productAction, product)
transactionAttributes?.let { builder.transactionAttributes(it) }

for (i in 1 until productsArray.size()) {
val nextProductMap = productsArray.getMap(i)
val nextProduct = convertProduct(nextProductMap)
if (nextProduct != null) {
builder?.addProduct(nextProduct)
builder.addProduct(nextProduct)
}
}
builder
Expand Down Expand Up @@ -696,22 +694,22 @@ class MParticleModule(
}

if (map.hasKey("shouldUploadEvent")) {
builder?.shouldUploadEvent(map.getBoolean("shouldUploadEvent"))
builder.shouldUploadEvent(map.getBoolean("shouldUploadEvent"))
}
if (map.hasKey("customAttributes")) {
builder?.customAttributes(convertStringMap(map.getMap("customAttributes")))
builder.customAttributes(convertStringMap(map.getMap("customAttributes")))
}
if (map.hasKey("currency")) {
map.getString("currency")?.let { builder?.currency(it) }
map.getString("currency")?.let { builder.currency(it) }
}
if (map.hasKey("checkoutStep")) {
builder?.checkoutStep(map.getInt("checkoutStep"))
builder.checkoutStep(map.getInt("checkoutStep"))
}
if (map.hasKey("checkoutOptions")) {
map.getString("checkoutOptions")?.let { builder?.checkoutOptions(it) }
map.getString("checkoutOptions")?.let { builder.checkoutOptions(it) }
}

return builder?.build()
return builder.build()
}

private fun convertProduct(map: ReadableMap?): Product? {
Expand Down
123 changes: 123 additions & 0 deletions android/src/test/java/com/mparticle/react/MParticleModuleTest.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.mparticle.react;

import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReactApplicationContext;
import com.mparticle.BaseEvent;
import com.mparticle.MParticle;
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Product;
import com.mparticle.react.testutils.MockMap;

import org.junit.Before;
Expand All @@ -16,9 +20,14 @@
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

@RunWith(MockitoJUnitRunner.class)
public class MParticleModuleTest {
private static final int ADD_TO_CART_ACTION = 1;
private static final int PURCHASE_ACTION = 7;

private MParticle mParticle;
private MParticleModule module;

Expand All @@ -40,4 +49,118 @@ public void logEventConvertsNullCustomAttributeToEmptyString() {
Mockito.verify(mParticle).logEvent(eventCaptor.capture());
assertEquals("", eventCaptor.getValue().getCustomAttributes().get("coupon_code"));
}

@Test
public void logCommerceEventLogsAddToCartWithoutTransactionAttributes() {
ReadableArray products = products(
product("Shirt", "shirt-1", 25.0),
product("Shoes", "shoes-1", 50.0));

module.logCommerceEvent(commerceEvent(ADD_TO_CART_ACTION, products, null));

CommerceEvent event = captureCommerceEvent();
assertEquals(Product.ADD_TO_CART, event.getProductAction());
assertNotNull(event.getProducts());
assertEquals(2, event.getProducts().size());
assertNotNull(event.getTransactionAttributes());
assertNull(event.getTransactionAttributes().getId());
}

@Test
public void logCommerceEventLogsAddToCartWithEmptyTransactionAttributes() {
ReadableMap transactionAttributes = Mockito.mock(ReadableMap.class);

module.logCommerceEvent(commerceEvent(
ADD_TO_CART_ACTION,
products(product("Shirt", "shirt-1", 25.0)),
transactionAttributes));

CommerceEvent event = captureCommerceEvent();
assertEquals(Product.ADD_TO_CART, event.getProductAction());
assertNotNull(event.getTransactionAttributes());
assertNull(event.getTransactionAttributes().getId());
}

@Test
public void logCommerceEventLogsAddToCartWithEmptyTransactionId() {
ReadableMap transactionAttributes = transactionAttributes("");

module.logCommerceEvent(commerceEvent(
ADD_TO_CART_ACTION,
products(product("Shirt", "shirt-1", 25.0)),
transactionAttributes));

CommerceEvent event = captureCommerceEvent();
assertEquals(Product.ADD_TO_CART, event.getProductAction());
assertEquals("", event.getTransactionAttributes().getId());
}

@Test
public void logCommerceEventAttachesValidPurchaseTransactionAttributes() {
ReadableMap transactionAttributes = transactionAttributes("order-123");

module.logCommerceEvent(commerceEvent(
PURCHASE_ACTION,
products(product("Shirt", "shirt-1", 25.0)),
transactionAttributes));

CommerceEvent event = captureCommerceEvent();
assertEquals(Product.PURCHASE, event.getProductAction());
assertEquals("order-123", event.getTransactionAttributes().getId());
}

@Test
public void logCommerceEventPassesPurchaseWithoutTransactionAttributesToNativeSdk() {
module.logCommerceEvent(commerceEvent(
PURCHASE_ACTION,
products(product("Shirt", "shirt-1", 25.0)),
null));

CommerceEvent event = captureCommerceEvent();
assertEquals(Product.PURCHASE, event.getProductAction());
assertNotNull(event.getTransactionAttributes());
assertNull(event.getTransactionAttributes().getId());
}

private CommerceEvent captureCommerceEvent() {
ArgumentCaptor<BaseEvent> eventCaptor = ArgumentCaptor.forClass(BaseEvent.class);
Mockito.verify(mParticle).logEvent(eventCaptor.capture());
return (CommerceEvent) eventCaptor.getValue();
}

private ReadableMap commerceEvent(
int productActionType,
ReadableArray products,
ReadableMap transactionAttributes) {
ReadableMap event = Mockito.mock(ReadableMap.class);
Mockito.when(event.hasKey("productActionType")).thenReturn(true);
Mockito.when(event.getInt("productActionType")).thenReturn(productActionType);
Mockito.when(event.getArray("products")).thenReturn(products);
Mockito.when(event.getMap("transactionAttributes")).thenReturn(transactionAttributes);
return event;
}

private ReadableArray products(ReadableMap... products) {
ReadableArray productArray = Mockito.mock(ReadableArray.class);
Mockito.when(productArray.size()).thenReturn(products.length);
for (int i = 0; i < products.length; i++) {
Mockito.when(productArray.getMap(i)).thenReturn(products[i]);
}
return productArray;
}

private ReadableMap product(String name, String sku, double price) {
ReadableMap product = Mockito.mock(ReadableMap.class);
Mockito.when(product.getString("name")).thenReturn(name);
Mockito.when(product.getString("sku")).thenReturn(sku);
Mockito.when(product.getDouble("price")).thenReturn(price);
return product;
}

private ReadableMap transactionAttributes(String transactionId) {
ReadableMap transactionAttributes = Mockito.mock(ReadableMap.class);
Mockito.when(transactionAttributes.hasKey("transactionId")).thenReturn(true);
Mockito.when(transactionAttributes.getString("transactionId")).thenReturn(transactionId);
return transactionAttributes;
}
}
36 changes: 36 additions & 0 deletions js/__tests__/attribute-normalization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
Event,
Impression,
Product,
TransactionAttributes,
logCommerceEvent,
logEvent,
logMPEvent,
Expand Down Expand Up @@ -167,4 +168,39 @@ describe('custom attribute normalization', () => {
expect(normalizedCommerce.impressions).toBeUndefined();
expect(normalizedCommerce.products[0].customAttributes).toBeUndefined();
});

it('omits product action transaction attributes when none are provided', () => {
const event = CommerceEvent.createProductActionEvent(1, [
new Product('Shirt', 'shirt-1', 25),
]);

expect(event.transactionAttributes).toBeUndefined();

logCommerceEvent(event);

const normalizedEvent = mockNativeModule.logCommerceEvent.mock.calls[0][0];
expect(
Object.prototype.hasOwnProperty.call(
normalizedEvent,
'transactionAttributes'
)
).toBe(false);
});

it('preserves explicitly provided product action transaction attributes', () => {
const transactionAttributes = new TransactionAttributes('order-123');
const event = CommerceEvent.createProductActionEvent(
7,
[new Product('Shirt', 'shirt-1', 25)],
transactionAttributes
);

expect(event.transactionAttributes).toBe(transactionAttributes);

logCommerceEvent(event);

expect(
mockNativeModule.logCommerceEvent.mock.calls[0][0].transactionAttributes
).toBe(transactionAttributes);
});
});
11 changes: 7 additions & 4 deletions js/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -799,12 +799,15 @@ export class CommerceEvent {
static createProductActionEvent(
productActionType: number,
products: Product[],
transactionAttributes: TransactionAttributes = new TransactionAttributes('')
transactionAttributes?: TransactionAttributes
): CommerceEvent {
return new CommerceEvent()
const commerceEvent = new CommerceEvent()
.setProductActionType(productActionType)
.setProducts(products)
.setTransactionAttributes(transactionAttributes);
.setProducts(products);

return transactionAttributes == null
? commerceEvent
: commerceEvent.setTransactionAttributes(transactionAttributes);
}

static createPromotionEvent(
Expand Down
Loading