-
Notifications
You must be signed in to change notification settings - Fork 1
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
Conversation
I think it would be good to add log statements |
OptimizelyXIntegration(final Analytics analytics) { | ||
|
||
listener = | ||
new NotificationListener() { |
There was a problem hiding this comment.
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 = |
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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())) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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()); | ||
|
There was a problem hiding this comment.
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);
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
@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:+' |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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()?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 Don't intend to merge and release quite yet, but perhaps we can do a code review together while I'm here.