diff --git a/api/src/com/todoroo/andlib/utility/AndroidUtilities.java b/api/src/com/todoroo/andlib/utility/AndroidUtilities.java index 7a35e8315..4633d459c 100644 --- a/api/src/com/todoroo/andlib/utility/AndroidUtilities.java +++ b/api/src/com/todoroo/andlib/utility/AndroidUtilities.java @@ -200,6 +200,19 @@ public class AndroidUtilities { return -1; } + /** + * Return index of value in integer array + * @param array array to search + * @param value value to look for + * @return + */ + public static int indexOf(int[] array, int value) { + for (int i = 0; i < array.length; i++) + if (array[i] == value) + return i; + return -1; + } + /** * Serializes a content value into a string */ diff --git a/astrid/src/com/todoroo/astrid/dao/ABTestEventDao.java b/astrid/src/com/todoroo/astrid/dao/ABTestEventDao.java index 5ccc20d80..0ecb9bfbf 100644 --- a/astrid/src/com/todoroo/astrid/dao/ABTestEventDao.java +++ b/astrid/src/com/todoroo/astrid/dao/ABTestEventDao.java @@ -1,8 +1,13 @@ package com.todoroo.astrid.dao; import com.todoroo.andlib.data.DatabaseDao; +import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.sql.Order; +import com.todoroo.andlib.sql.Query; +import com.todoroo.andlib.utility.AndroidUtilities; +import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.data.ABTestEvent; public class ABTestEventDao extends DatabaseDao { @@ -16,4 +21,50 @@ public class ABTestEventDao extends DatabaseDao { setDatabase(database); } + public void createInitialTestEvent(String testName, String testVariant, + boolean newUser, boolean activeUser) { + ABTestEvent event = new ABTestEvent(); + event.setValue(ABTestEvent.TEST_NAME, testName); + event.setValue(ABTestEvent.TEST_VARIANT, testVariant); + event.setValue(ABTestEvent.NEW_USER, newUser ? 1 : 0); + event.setValue(ABTestEvent.ACTIVATED_USER, activeUser ? 1 : 0); + event.setValue(ABTestEvent.TIME_INTERVAL, ABTestEvent.TIME_INTERVAL_0); + event.setValue(ABTestEvent.DATE_RECORDED, DateUtilities.now()); + + createNew(event); + } + + public boolean createTestEventWithTimeInterval(String testName, int timeInterval) { + TodorooCursor existing = query(Query.select(ABTestEvent.PROPERTIES) + .where(ABTestEvent.TEST_NAME.eq(testName)).orderBy(Order.asc(ABTestEvent.TIME_INTERVAL))); + + try { + if (existing.getCount() == 0) + return false; + + existing.moveToLast(); + ABTestEvent item = new ABTestEvent(existing); + int lastRecordedTimeIntervalIndex = AndroidUtilities.indexOf( + ABTestEvent.TIME_INTERVALS, item.getValue(ABTestEvent.TIME_INTERVAL)); + + int currentTimeIntervalIndex = AndroidUtilities.indexOf( + ABTestEvent.TIME_INTERVALS, timeInterval); + + if (lastRecordedTimeIntervalIndex < 0 || currentTimeIntervalIndex < 0 + || lastRecordedTimeIntervalIndex >= currentTimeIntervalIndex) + return false; + + long now = DateUtilities.now(); + for (int i = lastRecordedTimeIntervalIndex + 1; i <= currentTimeIntervalIndex; i++) { + item.clearValue(ABTestEvent.ID); + item.setValue(ABTestEvent.TIME_INTERVAL, ABTestEvent.TIME_INTERVALS[i]); + item.setValue(ABTestEvent.DATE_RECORDED, now); + createNew(item); + } + } finally { + existing.close(); + } + return true; + } + } diff --git a/astrid/src/com/todoroo/astrid/data/ABTestEvent.java b/astrid/src/com/todoroo/astrid/data/ABTestEvent.java index 3a4c69c50..476484d0b 100644 --- a/astrid/src/com/todoroo/astrid/data/ABTestEvent.java +++ b/astrid/src/com/todoroo/astrid/data/ABTestEvent.java @@ -9,18 +9,25 @@ import com.todoroo.andlib.data.Property.IntegerProperty; import com.todoroo.andlib.data.Property.LongProperty; import com.todoroo.andlib.data.Property.StringProperty; import com.todoroo.andlib.data.Table; -import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.astrid.api.AstridApiConstants; @SuppressWarnings("nls") public class ABTestEvent extends AbstractModel { - public static final long TEST_INTERVAL_0 = 0; - public static final long TEST_INTERVAL_3 = 3 * DateUtilities.ONE_DAY; - public static final long TEST_INTERVAL_7 = DateUtilities.ONE_WEEK; - public static final long TEST_INTERVAL_14 = 2 * DateUtilities.ONE_WEEK; - public static final long TEST_INTERVAL_21 = 3 * DateUtilities.ONE_WEEK; + public static final int TIME_INTERVAL_0 = 0; + public static final int TIME_INTERVAL_3 = 3; + public static final int TIME_INTERVAL_7 = 7; + public static final int TIME_INTERVAL_14 = 14; + public static final int TIME_INTERVAL_21 = 21; + public static final int[] TIME_INTERVALS = { + TIME_INTERVAL_0, + TIME_INTERVAL_3, + TIME_INTERVAL_7, + TIME_INTERVAL_14, + TIME_INTERVAL_21 + }; // --- table and uri @@ -70,7 +77,7 @@ public class ABTestEvent extends AbstractModel { * Which time interval event this data point corresponds to. * Should be one of the time interval constants defined above. */ - public static final LongProperty TIME_INTERVAL = new LongProperty( + public static final IntegerProperty TIME_INTERVAL = new IntegerProperty( TABLE, "timeInterval"); // one of the constants defined above /** The actual date on which this data point was recorded. */ @@ -92,6 +99,17 @@ public class ABTestEvent extends AbstractModel { return defaultValues; } + // --- data access boilerplate + + public ABTestEvent() { + super(); + } + + public ABTestEvent(TodorooCursor cursor) { + this(); + readPropertiesFromCursor(cursor); + } + @Override public long getId() { return getIdHelper(ID); diff --git a/astrid/src/com/todoroo/astrid/service/abtesting/ABChooser.java b/astrid/src/com/todoroo/astrid/service/abtesting/ABChooser.java index f5e535acb..9c647c4e9 100644 --- a/astrid/src/com/todoroo/astrid/service/abtesting/ABChooser.java +++ b/astrid/src/com/todoroo/astrid/service/abtesting/ABChooser.java @@ -6,7 +6,7 @@ import java.util.Set; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.utility.Preferences; -import com.todoroo.astrid.service.StatisticsService; +import com.todoroo.astrid.dao.ABTestEventDao; /** * Helper class to facilitate A/B testing by randomly choosing an option @@ -21,6 +21,9 @@ public class ABChooser { @Autowired private ABTests abTests; + @Autowired + private ABTestEventDao abTestEventDao; + private final Random random; public ABChooser() { @@ -32,10 +35,10 @@ public class ABChooser { * Iterates through the list of all available tests and makes sure that a choice * is made for each of them */ - public void makeChoicesForAllTests() { + public void makeChoicesForAllTests(boolean newUser, boolean activatedUser) { Set tests = abTests.getAllTestKeys(); for (String test : tests) { - getChoiceForTest(test); + makeChoiceForTest(test, newUser, activatedUser); } } @@ -47,21 +50,22 @@ public class ABChooser { * The statistics session needs to be open here in order to collect statistics * * @param testKey - the preference key string of the option (defined in ABTests) - * @return */ - private int getChoiceForTest(String testKey) { + private void makeChoiceForTest(String testKey, boolean newUser, boolean activatedUser) { int pref = readChoiceForTest(testKey); - if (pref > NO_OPTION) return pref; + if (pref > NO_OPTION) return; int chosen = NO_OPTION; if (abTests.isValidTestKey(testKey)) { int[] optionProbs = abTests.getProbsForTestKey(testKey); + String[] optionDescriptions = abTests.getDescriptionsForTestKey(testKey); chosen = chooseOption(optionProbs); setChoiceForTest(testKey, chosen); - StatisticsService.reportEvent(abTests.getDescriptionForTestOption(testKey, chosen)); // Session should be open + String desc = optionDescriptions[chosen]; + abTestEventDao.createInitialTestEvent(testKey, desc, newUser, activatedUser); } - return chosen; + return; } /**