Skip to content

Optimizely X v1 integration #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Oct 3, 2017
Merged

Optimizely X v1 integration #1

merged 24 commits into from
Oct 3, 2017

Conversation

brennan
Copy link
Contributor

@brennan brennan commented Sep 18, 2017

@f2prateek Don't intend to merge and release quite yet, but perhaps we can do a code review together while I'm here.

@brennan brennan requested a review from f2prateek September 18, 2017 19:05
@ladanazita
Copy link

I think it would be good to add log statements

OptimizelyXIntegration(final Analytics analytics) {

listener =
new NotificationListener() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core functionality of the integration. It shouldn't be an anonymous class.


OptimizelyXIntegration(final Analytics analytics) {

listener =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This listener is unused.... you need to pass it to Optimizely so that it can actually call it.

this.context = context;
this.logger = logger;
}
public class OptimizelyXIntegration extends Integration<NotificationListener> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should this return the listener? I think this should return Void.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be something like public class OptimizelyXIntegration extends Integration<Void> implements OptimizelyEventListener { yeah?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

functionally yes. the exact code wouldn't work because OptimizelyEventListener is in the old version only; while the new version has NotificationListener it's an abstract class and not an interface, so it needs to be structured differently.

listener =
new NotificationListener() {
@Override
public void onEventTracked(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understood this correctly, this listener on iOS listens for an Optimizely event to be tracked - which we are not concerned about. I think we need to only listen for the onExperimentActivated event since the main use case that customers are looking for is the ability to send events to Optimizely without having to instrument native Optimizely track calls

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users will still have to implement native Optimizely track and activate method calls - the NotificationListener class is called only when either track or activate in Optimizely's native API is invoked. That said, I agree that we should only listen for "activate" events! Users will probably already have Segment track events set up to track user events and goal completions.

Long eventValue,
LogEvent logEvent) {

if (!isNullOrEmpty(attributes)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so I understand, you calling identify with the Optimizely user attributes, correct?

Copy link
Contributor Author

@brennan brennan Sep 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! But dovetailing with your comment above, I agree that all we really care about is tracking when an experiment was activated. With this in mind, I think we should just remove our call to identify with Optimizely attributes, since users will prob be calling identify via Segment as well.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jaime at Optimizely mentions that our mutual clients would like the ability to send attributes/traits of users to Optimizely so that Optimizely can use those attributes/traits for targeting. However, we can't really do much here because Optimizely X doesn't have a way to set these user attributes outside of an activate experiment event.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it would be helpful but we could set a user property for the given user with the variation and experiment viewed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm that might be tricky, because a single user can be associated with many different experiments - I think this may result in trait bloat in other downstream tools and warehouses.

public void track(TrackPayload track) {
super.track(track);

if (!isNullOrEmpty(track.userId())) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to consider only tracking anonymous users, since an experiment is likely to be set early on, before an identify is called.


@Before public void setUp() {
}
initMocks(this);
PowerMockito.mock(OptimizelyClient.class);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you also have to mock the manager and when Optimizely is initialized, return the mocked client. Something maybe like

    manager = mock(OptimizelyManager.class);
    when(manager.getOptimizely()).thenReturn(client);

Traits traits = new Traits()
.putValue("userId", "123");
integration.track(new TrackPayloadBuilder().properties(properties).traits(traits).event("event").build());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may have to do something like when(client.isValid()).thenReturn(true);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awaiting feedback from Prateek on the new approach in the main lib before completing tests - these tests are still valid for the previous version of the integration.

if (!client.isValid()) {
logger.verbose("The OptimizelyClient instance is invalid. Please check your Optimizely project id.");
try {
Thread.sleep(60000);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why pause execution for this time interval?

Copy link
Contributor Author

@brennan brennan Sep 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recursive method that polls the validity of the OptimizelyClient instance once per minute until it's valid. This value is arbitrary, but we should seek feedback from Jaime on a more appropriate interval.

logger.verbose("The OptimizelyClient instance is invalid. Please check your Optimizely project id.");
try {
Thread.sleep(60000);
} catch ( java.lang.InterruptedException e) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this log the error?

@brennan
Copy link
Contributor Author

brennan commented Sep 28, 2017

@f2prateek @ladanazita Just E2E tested, and I think this is ready for a final code review. Thanks!

build.gradle Outdated
mavenCentral()
}

provided 'com.segment.analytics.android:analytics:4.0.0'
compile 'com.optimizely.ab:android-sdk:+'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Android we have refrained from using + - best practice dictates to specify version numbers as + can lead to unpredictable builds - not sure if we have a strong opinion here

private List<TrackPayload> trackEvents = new ArrayList<>();
private final Handler handler = new Handler();

public static Factory factory(OptimizelyManager manager) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is ever used

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used by customers when register the factory with Segment.

listen = settings.getBoolean("listen", true);

if (client.isValid()) {
isClientValid = true;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using a separate variable, why not just use the client object with client.isValid()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was intentional for efficiency, and to ensure we're only checking the client object directly on our polling thread. Let me know if this is not the right approach!

if (!isClientValid) {
logger.verbose("Optimizely not initialized. Enqueueing action: %s", track);
if (queueSize < 100) {
trackEvents.add(track);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two blocks have shared code. Should refactor it to be DRY. Also should log if a message is being dropped.

if (!nonInteraction) {
properties.remove("nonInteraction");
}
analytics.track("Experiment Viewed", properties, options);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you will also need to pass in Optimizely X : no so the event does not attempt to go back into Optimizely

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! That's already specified in the options object passed into the event.

* Fixes to main lib, update tests to check track queue flush method
@f2prateek f2prateek merged commit 3b505df into master Oct 3, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants