Skip to content
This repository has been archived by the owner on Apr 28, 2020. It is now read-only.

Commit

Permalink
Add custom update subscription events (#7)
Browse files Browse the repository at this point in the history
Add custom update subscription events
  • Loading branch information
samdozor authored Feb 14, 2018
1 parent e835d4c commit 3d587ec
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 13 deletions.
2 changes: 1 addition & 1 deletion iterable-extension/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dependencies {
compile (
'com.amazonaws:aws-lambda-java-core:1.2.0',
'com.amazonaws:aws-lambda-java-events:2.0.2',
'com.mparticle:java-sdk:1.4.0'
'com.mparticle:java-sdk:1.8.0'
)
compile project(':iterable-java-sdk')
testCompile('junit:junit:4.12')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,13 +358,16 @@ public ModuleRegistrationResponse processRegistrationRequest(ModuleRegistrationR
)
);
permissions.setAllowAccessDeviceApplicationStamp(true);
permissions.setAllowUserAttributes(true);
permissions.setAllowDeviceInformation(true);
response.setPermissions(permissions);
response.setDescription("<a href=\"https://www.iterable.com\">Iterable</a> makes consumer growth marketing and user engagement simple. With Iterable, marketers send the right message, to the right device, at the right time.");
EventProcessingRegistration eventProcessingRegistration = new EventProcessingRegistration()
.setSupportedRuntimeEnvironments(
Arrays.asList(
RuntimeEnvironment.Type.ANDROID,
RuntimeEnvironment.Type.IOS)
RuntimeEnvironment.Type.IOS,
RuntimeEnvironment.Type.MOBILEWEB)
);

List<Setting> eventSettings = new ArrayList<>();
Expand Down Expand Up @@ -400,7 +403,6 @@ public ModuleRegistrationResponse processRegistrationRequest(ModuleRegistrationR
Event.Type.CUSTOM_EVENT,
Event.Type.PUSH_SUBSCRIPTION,
Event.Type.PUSH_MESSAGE_RECEIPT,
Event.Type.USER_ATTRIBUTE_CHANGE,
Event.Type.USER_IDENTITY_CHANGE,
Event.Type.PRODUCT_ACTION);

Expand All @@ -419,8 +421,103 @@ public ModuleRegistrationResponse processRegistrationRequest(ModuleRegistrationR
return response;
}

private static List<Integer> convertToIntList(String csv){
if (csv == null) {
return null;
} else if (csv.isEmpty()) {
return new ArrayList<>();
}

List<Integer> list = new ArrayList<>();
StringTokenizer st = new StringTokenizer(csv, ",");
while (st.hasMoreTokens()) {
list.add(Integer.parseInt(st.nextToken().trim()));
}
return list;
}

public static final String UPDATE_SUBSCRIPTIONS_CUSTOM_EVENT_NAME = "subscriptionsUpdated";
public static final String EMAIL_LIST_ID_LIST_KEY = "emailListIds";
public static final String UNSUBSCRIBE_CHANNEL_ID_LIST_KEY = "unsubscribedChannelIds";
public static final String UNSUBSCRIBE_MESSAGE_TYPE_ID_LIST_KEY = "unsubscribedMessageTypeIds";
public static final String CAMPAIGN_ID_KEY = "campaignId";
public static final String TEMPLATE_ID_KEY = "templateId";

/**
*
* This is expected to be called with an event that conforms to the following:
* Name: "updateSubscriptions"
*
* And has at least some of the following:
*
* Attribute: emailListIds
* Attribute: unsubscribedChannelIds
* Attribute: unsubscribedMessageTypeIds
* Attribute: campaignId
* Attribute: templateId
*
*/
private boolean processSubscribeEvent(CustomEvent event) throws IOException {
UpdateSubscriptionsRequest updateRequest = generateSubscriptionRequest(event);
if (updateRequest == null) {
return false;
}
Response<IterableApiResponse> response = iterableService.updateSubscriptions(getApiKey(event), updateRequest).execute();
if (response.isSuccessful() && !response.body().isSuccess()) {
throw new IOException(response.body().toString());
} else if (!response.isSuccessful()) {
throw new IOException("Error sending update subscriptions event to Iterable: HTTP " + response.code());
}
return true;

}

static UpdateSubscriptionsRequest generateSubscriptionRequest(CustomEvent event) {
if (!UPDATE_SUBSCRIPTIONS_CUSTOM_EVENT_NAME.equalsIgnoreCase(event.getName())) {
return null;
}
UpdateSubscriptionsRequest updateRequest = new UpdateSubscriptionsRequest();

Map<String, String> eventAttributes = event.getAttributes();
updateRequest.emailListIds = convertToIntList(eventAttributes.get(EMAIL_LIST_ID_LIST_KEY));
updateRequest.unsubscribedChannelIds = convertToIntList(eventAttributes.get(UNSUBSCRIBE_CHANNEL_ID_LIST_KEY));
updateRequest.unsubscribedMessageTypeIds = convertToIntList(eventAttributes.get(UNSUBSCRIBE_MESSAGE_TYPE_ID_LIST_KEY));

String campaignId = eventAttributes.get(CAMPAIGN_ID_KEY);
if (!isEmpty(campaignId)) {
try {
updateRequest.campaignId = Integer.parseInt(campaignId.trim());
}catch (NumberFormatException ignored) {

}
}

String templateId = eventAttributes.get(TEMPLATE_ID_KEY);
if (!isEmpty(templateId)) {
try {
updateRequest.templateId = Integer.parseInt(templateId.trim());
}catch (NumberFormatException ignored) {

}
}

List<UserIdentity> identities = event.getContext().getUserIdentities();
if (identities != null) {
for (UserIdentity identity : identities) {
if (identity.getType().equals(UserIdentity.Type.EMAIL)) {
updateRequest.email = identity.getValue();
}
}
}
return updateRequest;
}

@Override
public void processCustomEvent(CustomEvent event) throws IOException {
if (processSubscribeEvent(event)) {
return;
}

TrackRequest request = new TrackRequest(event.getName());
request.createdAt = (int) (event.getTimestamp() / 1000.0);
request.dataFields = attemptTypeConversion(event.getAttributes());
Expand All @@ -434,9 +531,6 @@ public void processCustomEvent(CustomEvent event) throws IOException {
}
}
}
//TODO use custom flags to set campaign and template id
//request.campaignId = event.getCustomFlags()....
//request.templateId = event.getCustomFlags()....

Response<IterableApiResponse> response = iterableService.track(getApiKey(event), request).execute();
if (response.isSuccessful() && !response.body().isSuccess()) {
Expand Down Expand Up @@ -546,7 +640,6 @@ public AudienceMembershipChangeResponse processAudienceMembershipChangeRequest(A
ApiUser user = new ApiUser();
user.email = email;
user.userId = userId;
user.dataFields = profile.getUserAttributes();
if (!additions.containsKey(listId)) {
additions.put(listId, new LinkedList<>());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.mparticle.sdk.model.registration.UserIdentityPermission;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import retrofit2.Call;
import retrofit2.Response;
Expand Down Expand Up @@ -155,7 +156,6 @@ public void testProcessRegistrationRequest() throws Exception {
assertTrue("Iterable should support custom events", eventTypes.contains(Event.Type.CUSTOM_EVENT));
assertTrue("Iterable should support push subscriptions", eventTypes.contains(Event.Type.PUSH_SUBSCRIPTION));
assertTrue("Iterable should support push receipts", eventTypes.contains(Event.Type.PUSH_MESSAGE_RECEIPT));
assertTrue("Iterable should support user attribute changes", eventTypes.contains(Event.Type.USER_ATTRIBUTE_CHANGE));
assertTrue("Iterable should support user identity changes", eventTypes.contains(Event.Type.USER_IDENTITY_CHANGE));

Setting setting = response.getAudienceProcessingRegistration().getAudienceConnectionSettings().get(0);
Expand Down Expand Up @@ -639,4 +639,71 @@ public void testGetPlaceholderEmailEnvironmentAndroidID() throws Exception {
String email = IterableExtension.getPlaceholderEmail(request);
assertEquals("[email protected]", email);
}

@org.junit.Test
public void testUpdateSubscriptionsEvent() throws Exception {
IterableExtension extension = new IterableExtension();
extension.iterableService = Mockito.mock(IterableService.class);
Call callMock = Mockito.mock(Call.class);
Mockito.when(extension.iterableService.updateSubscriptions(Mockito.any(), Mockito.any()))
.thenReturn(callMock);
Mockito.when(extension.iterableService.track(Mockito.any(), Mockito.any()))
.thenReturn(callMock);
IterableApiResponse apiResponse = new IterableApiResponse();
apiResponse.code = IterableApiResponse.SUCCESS_MESSAGE;
Response<IterableApiResponse> response = Response.success(apiResponse);
Mockito.when(callMock.execute()).thenReturn(response);

long timeStamp = System.currentTimeMillis();
CustomEvent event = new CustomEvent();
event.setTimestamp(timeStamp);
event.setName(IterableExtension.UPDATE_SUBSCRIPTIONS_CUSTOM_EVENT_NAME);
EventProcessingRequest request = new EventProcessingRequest();
Account account = new Account();
Map<String, String> settings = new HashMap<>();
settings.put(SETTING_API_KEY, "foo api key 2");
account.setAccountSettings(settings);
request.setAccount(account);
List<UserIdentity> userIdentities = new LinkedList<>();
userIdentities.add(new UserIdentity(UserIdentity.Type.EMAIL, Identity.Encoding.RAW, "[email protected]"));
request.setUserIdentities(userIdentities);
Event.Context context = new Event.Context(request);
event.setContext(context);
Map<String, String> attributes = new HashMap<>();
// Added some random spaces to test it doesn't mess with parsing
attributes.put(IterableExtension.EMAIL_LIST_ID_LIST_KEY, "1, 2, 3, 4 , 5 , 6 , 7 ,8");
attributes.put(IterableExtension.UNSUBSCRIBE_CHANNEL_ID_LIST_KEY, " 1, 3, 5 ,7 ");
attributes.put(IterableExtension.UNSUBSCRIBE_MESSAGE_TYPE_ID_LIST_KEY, " 1, 3, 5 ,7 ,10");
attributes.put(IterableExtension.CAMPAIGN_ID_KEY, "2323");
attributes.put(IterableExtension.TEMPLATE_ID_KEY, " 5555 ");
event.setAttributes(attributes);
List<Integer> expectedEmailListIdList = Arrays.asList(1,2,3,4,5,6,7,8);
List<Integer> expectedChannelIdList = Arrays.asList(1,3,5,7);
List<Integer> expectedMessageTypeIdList = Arrays.asList(1,3,5,7,10);
int expectedCampaignId = 2323;
int expectedTemplateId = 5555;

extension.processCustomEvent(event);

ArgumentCaptor<UpdateSubscriptionsRequest> argument = ArgumentCaptor.forClass(UpdateSubscriptionsRequest.class);
ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
Mockito.verify(extension.iterableService).updateSubscriptions(stringArgumentCaptor.capture(), argument.capture());
assertEquals("foo api key 2", stringArgumentCaptor.getValue());
assertEquals("[email protected]", argument.getValue().email);
assertEquals(expectedEmailListIdList, argument.getValue().emailListIds);
assertEquals(expectedChannelIdList, argument.getValue().unsubscribedChannelIds);
assertEquals(expectedMessageTypeIdList, argument.getValue().unsubscribedMessageTypeIds);
assertEquals(expectedCampaignId, (int)argument.getValue().campaignId);
assertEquals(expectedTemplateId, (int)argument.getValue().templateId);

apiResponse.code = "anything but success";

IOException exception = null;
try {
extension.processCustomEvent(event);
} catch (IOException ioe) {
exception = ioe;
}
assertNotNull("Iterable extension should have thrown an IOException", exception);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import retrofit2.http.POST;
import retrofit2.http.Query;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
Expand All @@ -25,7 +24,7 @@ public interface IterableService {

String HOST = "api.iterable.com";
String PARAM_API_KEY = "api_key";
long SERVICE_TIMEOUT_MILLIS = 300;
long SERVICE_TIMEOUT_MILLIS = 500;

@POST("api/events/track")
Call<IterableApiResponse> track(@Query(IterableService.PARAM_API_KEY) String apiKey, @Body TrackRequest trackRequest);
Expand All @@ -51,6 +50,9 @@ public interface IterableService {
@POST("api/commerce/trackPurchase")
Call<IterableApiResponse> trackPurchase(@Query(IterableService.PARAM_API_KEY) String apiKey, @Body TrackPurchaseRequest purchaseRequest);

@POST("api/users/updateSubscriptions")
Call<IterableApiResponse> updateSubscriptions(@Query(IterableService.PARAM_API_KEY) String apiKey, @Body UpdateSubscriptionsRequest userUpdateRequest);

/**
* At the moment this is only used for unit testing the list subscribe/unsubscribe API calls
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.mparticle.iterable;

import java.util.List;

public class UpdateSubscriptionsRequest {
public String email;
public List<Integer> emailListIds;
public List<Integer> unsubscribedChannelIds;
public List<Integer> unsubscribedMessageTypeIds;
public Integer campaignId;
public Integer templateId;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.mparticle.iterable;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import retrofit2.Call;
import retrofit2.Response;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.*;

import static org.junit.Assert.assertTrue;

Expand Down Expand Up @@ -44,6 +43,8 @@ public void setUp() throws Exception {
.thenReturn(callMock);
Mockito.when(iterableService.trackPurchase(Mockito.any(), Mockito.any()))
.thenReturn(callMock);
Mockito.when(iterableService.updateSubscriptions(Mockito.any(), Mockito.any()))
.thenReturn(callMock);
IterableApiResponse apiResponse = new IterableApiResponse();
apiResponse.code = IterableApiResponse.SUCCESS_MESSAGE;
Response<IterableApiResponse> response = Response.success(apiResponse);
Expand Down Expand Up @@ -95,6 +96,7 @@ public void testUserUpdate() throws Exception {
userUpdateRequest.email = TEST_EMAIL;
Map<String, String> attributes = new HashMap<String, String>();
attributes.put("test attribute key", "test attribute value");

userUpdateRequest.dataFields = attributes;
Response<IterableApiResponse> response = iterableService.userUpdate(ITERABLE_API_KEY, userUpdateRequest).execute();
assertTrue("Retrofit request not successful:\nMessage: " + response.message() + "\nCode: " + response.code(), response.isSuccessful());
Expand Down Expand Up @@ -178,4 +180,17 @@ public void testTrackPurchase() throws Exception {
assertTrue("Retrofit request not successful:\nMessage: " + response.message() + "\nCode: " + response.code(), response.isSuccessful());
assertTrue("Iterable response was not successful:\n" + response.body().toString(), response.body().isSuccess());
}

@Test
public void testUpdateSubscriptions() throws Exception {
UpdateSubscriptionsRequest request = new UpdateSubscriptionsRequest();
request.email = TEST_EMAIL;
request.unsubscribedChannelIds = Arrays.asList(1,2,3);
request.unsubscribedMessageTypeIds = Arrays.asList(1,2,3);
request.emailListIds = Arrays.asList(1,2,3);
Response<IterableApiResponse> response = iterableService.updateSubscriptions(ITERABLE_API_KEY, request).execute();
assertTrue("Retrofit request not successful:\nMessage: " + response.message() + "\nCode: " + response.code(), response.isSuccessful());
assertTrue("Iterable response was not successful:\n" + response.body().toString(), response.body().isSuccess());

}
}

0 comments on commit 3d587ec

Please sign in to comment.