Skip to content

Commit b85cbbe

Browse files
authored
Merge pull request #258 from Countly/auto_user_props_save
Auto user props save
2 parents 7ec98f9 + cb538c0 commit b85cbbe

File tree

8 files changed

+183
-14
lines changed

8 files changed

+183
-14
lines changed

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
## 24.1.4
2-
3-
* Updated user properties caching mechanism according to sessions.
2+
* ! Minor breaking change ! User properties will now be automatically saved under the following conditions:
3+
* When an event is recorded
4+
* During an internal timer tick
5+
* Upon flushing the event queue
6+
* When a session call made
47
* Cleaned up unused gradle dependencies from root build.gradle.
58

69
## 24.1.3
7-
810
* Extended minimum JDK support to 8.
911

1012
## 24.1.2

sdk-java/src/main/java/ly/count/sdk/java/Config.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ public class Config {
243243
protected String city = null;
244244
protected String country = null;
245245
protected boolean locationEnabled = true;
246-
protected boolean autoSendUserPropertiesOnSessions = true;
246+
protected boolean autoSendUserProperties = true;
247247

248248
// TODO: storage limits & configuration
249249
// protected int maxRequestsStored = 0;
@@ -1482,13 +1482,19 @@ public String toString() {
14821482
}
14831483
}
14841484

1485+
// Disabling new Added features
1486+
14851487
/**
1486-
* Disable automatic sending of user properties on session begin, update and end
1488+
* Disable automatic sending of user properties on
1489+
* - When an event is recorded
1490+
* - During an internal timer tick
1491+
* - Upon flushing the event queue
1492+
* - When a session call made
14871493
*
14881494
* @return {@code this} instance for method chaining
14891495
*/
1490-
public Config disableAutoSendUserPropertiesOnSessions() {
1491-
this.autoSendUserPropertiesOnSessions = false;
1496+
public Config disableAutoSendUserProperties() {
1497+
this.autoSendUserProperties = false;
14921498
return this;
14931499
}
14941500
}

sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ boolean isLocationDisabled() {
213213
return !locationEnabled;
214214
}
215215

216-
boolean isAutoSendUserPropertiesOnSessions() {
217-
return autoSendUserPropertiesOnSessions;
216+
boolean isAutoSendUserProperties() {
217+
return autoSendUserProperties;
218218
}
219219
}

sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleEvents.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ protected void recordEventInternal(String key, int count, Double sum, Double dur
109109

110110
Utils.removeInvalidDataFromSegments(segmentation, L);
111111

112+
if (internalConfig.isAutoSendUserProperties() && internalConfig.sdk.userProfile() != null) {
113+
internalConfig.sdk.module(ModuleUserProfile.class).saveInternal();
114+
}
115+
112116
String eventId, pvid = null, cvid = null;
113117
if (Utils.isEmptyOrNull(eventIdOverride)) {
114118
L.d("[ModuleEvents] recordEventInternal, Generating new event id because it was null or empty");
@@ -139,7 +143,7 @@ private void addEventToQueue(EventImpl event) {
139143
checkEventQueueToSend(false);
140144
}
141145

142-
private void checkEventQueueToSend(boolean forceSend) {
146+
void checkEventQueueToSend(boolean forceSend) {
143147
L.d("[ModuleEvents] queue size:[" + eventQueue.eqSize() + "] || forceSend: " + forceSend);
144148
if (forceSend || eventQueue.eqSize() >= internalConfig.getEventsBufferSize()) {
145149
addEventsToRequestQ(null);

sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleUserProfile.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,17 @@ protected void saveInternal() {
259259
if (internalConfig.sdk.location() != null) {
260260
internalConfig.sdk.module(ModuleLocation.class).saveLocationToParamsLegacy(generatedParams);
261261
}
262+
262263
L.d("[ModuleUserProfile] saveInternal, generated params [" + generatedParams + "]");
264+
if (generatedParams.length() <= 0) {
265+
L.d("[ModuleUserProfile] saveInternal, nothing to save returning");
266+
return;
267+
}
268+
269+
if (internalConfig.isAutoSendUserProperties() && internalConfig.sdk.events() != null) {
270+
internalConfig.sdk.module(ModuleEvents.class).checkEventQueueToSend(true);
271+
}
272+
263273
ModuleRequests.pushAsync(internalConfig, new Request(generatedParams));
264274
clearInternal();
265275
}
@@ -288,6 +298,22 @@ public void stop(InternalConfig config, boolean clearData) {
288298
userProfileInterface = null;
289299
}
290300

301+
@Override
302+
protected void onTimer() {
303+
if (internalConfig.isAutoSendUserProperties()) {
304+
saveInternal();
305+
}
306+
}
307+
308+
@Override
309+
public void deviceIdChanged(String oldDeviceId, boolean withMerge) {
310+
super.deviceIdChanged(oldDeviceId, withMerge);
311+
L.d("[ModuleUserProfile] deviceIdChanged: oldDeviceId = " + oldDeviceId + ", withMerge = " + withMerge);
312+
if (internalConfig.isAutoSendUserProperties() && !withMerge) {
313+
saveInternal();
314+
}
315+
}
316+
291317
public class UserProfile {
292318

293319
/**

sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ Future<Boolean> begin(Long now) {
118118
}
119119

120120
this.consents = SDKCore.instance.consents;
121-
if (config.isAutoSendUserPropertiesOnSessions() && config.sdk.userProfile() != null) {
121+
if (config.isAutoSendUserProperties() && config.sdk.userProfile() != null) {
122122
config.sdk.module(ModuleUserProfile.class).saveInternal();
123123
}
124124

@@ -160,7 +160,7 @@ Future<Boolean> update(Long now) {
160160
}
161161

162162
this.consents = SDKCore.instance.consents;
163-
if (config.isAutoSendUserPropertiesOnSessions() && config.sdk.userProfile() != null) {
163+
if (config.isAutoSendUserProperties() && config.sdk.userProfile() != null) {
164164
config.sdk.module(ModuleUserProfile.class).saveInternal();
165165
}
166166

@@ -198,7 +198,7 @@ Future<Boolean> end(Long now, final Tasks.Callback<Boolean> callback, String did
198198
ended = now == null ? System.nanoTime() : now;
199199

200200
this.consents = SDKCore.instance.consents;
201-
if (config.isAutoSendUserPropertiesOnSessions() && config.sdk.userProfile() != null) {
201+
if (config.isAutoSendUserProperties() && config.sdk.userProfile() != null) {
202202
config.sdk.module(ModuleUserProfile.class).saveInternal();
203203
}
204204

sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,137 @@ public void timedEventFlow() throws InterruptedException {
442442
TestUtils.validateEventInEQ(eKeys[0], null, 2, 4.0, 2.0, 1, 2, TestUtils.keysValues[1], null, "", TestUtils.keysValues[0]);
443443
}
444444

445+
/**
446+
* Recording events with user properties and with flushing events
447+
* Validating that if a user property set before a recordEvent call it is sent before adding the event to EQ
448+
* And also user properties packed after flushing events.
449+
*
450+
* @throws InterruptedException when sleep is interrupted
451+
*/
452+
@Test
453+
public void eventsUserProps() throws InterruptedException {
454+
init(TestUtils.getConfigEvents(4).setUpdateSessionTimerDelay(2));
455+
456+
Countly.instance().userProfile().setProperty("before_event", "value1");
457+
Countly.instance().events().recordEvent(eKeys[0]);
458+
459+
Map<String, String>[] RQ = TestUtils.getCurrentRQ();
460+
Assert.assertEquals(1, RQ.length);
461+
Assert.assertEquals(TestUtils.json("custom", TestUtils.map("before_event", "value1")), RQ[0].get("user_details"));
462+
TestUtils.validateEventInEQ(eKeys[0], null, 1, null, null, 0, 1, "_CLY_", null, "", null);
463+
464+
Countly.instance().userProfile().setProperty("after_event", "value2");
465+
Thread.sleep(2500); // wait for the tick
466+
RQ = TestUtils.getCurrentRQ();
467+
Assert.assertEquals(3, RQ.length);
468+
Assert.assertTrue(RQ[1].containsKey("events"));
469+
Assert.assertEquals(TestUtils.json("custom", TestUtils.map("after_event", "value2")), RQ[2].get("user_details"));
470+
}
471+
472+
/**
473+
* Recording events with user properties and with flushing events will not work because reversed
474+
*
475+
* @throws InterruptedException when sleep is interrupted
476+
*/
477+
@Test
478+
public void eventsUserProps_reversed() throws InterruptedException {
479+
init(TestUtils.getConfigEvents(4).setUpdateSessionTimerDelay(2).disableAutoSendUserProperties());
480+
481+
Countly.instance().userProfile().setProperty("before_event", "value1");
482+
Countly.instance().events().recordEvent(eKeys[0]);
483+
484+
Map<String, String>[] RQ = TestUtils.getCurrentRQ();
485+
Assert.assertEquals(0, RQ.length);
486+
TestUtils.validateEventInEQ(eKeys[0], null, 1, null, null, 0, 1, "_CLY_", null, "", null);
487+
488+
Countly.instance().userProfile().setProperty("after_event", "value2");
489+
Thread.sleep(2500); // wait for the tick
490+
RQ = TestUtils.getCurrentRQ();
491+
492+
Assert.assertEquals(1, RQ.length);
493+
Assert.assertTrue(RQ[0].containsKey("events"));
494+
}
495+
496+
/**
497+
* Recording events with user properties and with flushing events
498+
* Validating that if a user property save called, it flushes EQ before saving user properties
499+
*/
500+
@Test
501+
public void eventsUserProps_propsSave() {
502+
init(TestUtils.getConfigEvents(4));
503+
504+
Countly.instance().events().recordEvent(eKeys[0]);
505+
506+
Map<String, String>[] RQ = TestUtils.getCurrentRQ();
507+
Assert.assertEquals(0, RQ.length);
508+
TestUtils.validateEventInEQ(eKeys[0], null, 1, null, null, 0, 1, "_CLY_", null, "", null);
509+
510+
Countly.instance().userProfile().setProperty("after_event", "value2");
511+
Countly.instance().userProfile().save();
512+
513+
RQ = TestUtils.getCurrentRQ();
514+
Assert.assertEquals(2, RQ.length);
515+
Assert.assertTrue(RQ[0].containsKey("events"));
516+
Assert.assertEquals(TestUtils.json("custom", TestUtils.map("after_event", "value2")), RQ[1].get("user_details"));
517+
}
518+
519+
/**
520+
* Recording events with user properties and with flushing events
521+
* Validating that if a user property save called, it does not flush EQ before saving user properties
522+
*/
523+
@Test
524+
public void eventsUserProps_propsSave_reversed() {
525+
init(TestUtils.getConfigEvents(4).disableAutoSendUserProperties());
526+
527+
Countly.instance().events().recordEvent(eKeys[0]);
528+
529+
Map<String, String>[] RQ = TestUtils.getCurrentRQ();
530+
Assert.assertEquals(0, RQ.length);
531+
TestUtils.validateEventInEQ(eKeys[0], null, 1, null, null, 0, 1, "_CLY_", null, "", null);
532+
533+
Countly.instance().userProfile().setProperty("after_event", "value2");
534+
Countly.instance().userProfile().save();
535+
536+
RQ = TestUtils.getCurrentRQ();
537+
Assert.assertEquals(1, RQ.length);
538+
Assert.assertEquals(TestUtils.json("custom", TestUtils.map("after_event", "value2")), RQ[0].get("user_details"));
539+
}
540+
541+
/**
542+
* Validate that user properties are sent with timer tick if no events are recorded
543+
*/
544+
@Test
545+
public void eventsUserProps_timer() throws InterruptedException {
546+
init(TestUtils.getConfigEvents(4).setUpdateSessionTimerDelay(2));
547+
548+
Countly.instance().userProfile().setProperty("before_timer", "value1");
549+
550+
Map<String, String>[] RQ = TestUtils.getCurrentRQ();
551+
Assert.assertEquals(0, RQ.length);
552+
553+
Thread.sleep(2500); // wait for the tick
554+
RQ = TestUtils.getCurrentRQ();
555+
Assert.assertEquals(1, RQ.length);
556+
Assert.assertEquals(TestUtils.json("custom", TestUtils.map("before_timer", "value1")), RQ[0].get("user_details"));
557+
}
558+
559+
/**
560+
* Validate that user properties does not send with timer tick if no events are recorded
561+
*/
562+
@Test
563+
public void eventsUserProps_timer_reversed() throws InterruptedException {
564+
init(TestUtils.getConfigEvents(4).setUpdateSessionTimerDelay(2).disableAutoSendUserProperties());
565+
566+
Countly.instance().userProfile().setProperty("before_timer", "value1");
567+
568+
Map<String, String>[] RQ = TestUtils.getCurrentRQ();
569+
Assert.assertEquals(0, RQ.length);
570+
571+
Thread.sleep(2500); // wait for the tick
572+
RQ = TestUtils.getCurrentRQ();
573+
Assert.assertEquals(0, RQ.length);
574+
}
575+
445576
private void validateTimedEventSize(int expectedQueueSize, int expectedTimedEventSize) {
446577
TestUtils.validateEQSize(expectedQueueSize, TestUtils.getCurrentEQ(), moduleEvents.eventQueue);
447578
Assert.assertEquals(expectedTimedEventSize, moduleEvents.timedEvents.size());

sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ public void userPropsOnSessions() throws InterruptedException {
680680
*/
681681
@Test
682682
public void userPropsOnSessions_reversed() throws InterruptedException {
683-
Countly.instance().init(TestUtils.getConfigSessions(Config.Feature.UserProfiles).disableAutoSendUserPropertiesOnSessions());
683+
Countly.instance().init(TestUtils.getConfigSessions(Config.Feature.UserProfiles).disableAutoSendUserProperties());
684684
Countly.instance().userProfile().setProperty("name", "John Doe");
685685
Countly.instance().userProfile().setProperty("custom_key", "custom_value");
686686

0 commit comments

Comments
 (0)