diff --git a/CHANGELOG.md b/CHANGELOG.md index b7944c7d..fb570b15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 24.1.4 +* Updated user properties caching mechanism according to sessions. * Cleaned up unused gradle dependencies from root build.gradle. ## 24.1.3 diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Config.java b/sdk-java/src/main/java/ly/count/sdk/java/Config.java index c4f2372b..953521ff 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/Config.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/Config.java @@ -243,6 +243,7 @@ public class Config { protected String city = null; protected String country = null; protected boolean locationEnabled = true; + protected boolean autoSendUserPropertiesOnSessions = true; // TODO: storage limits & configuration // protected int maxRequestsStored = 0; @@ -1480,4 +1481,14 @@ public String toString() { return "DID " + id + " ( " + strategy + ")"; } } + + /** + * Disable automatic sending of user properties on session begin, update and end + * + * @return {@code this} instance for method chaining + */ + public Config disableAutoSendUserPropertiesOnSessions() { + this.autoSendUserPropertiesOnSessions = false; + return this; + } } diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java index 5c51185b..9aa60e9e 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java @@ -32,6 +32,7 @@ public class InternalConfig extends Config { protected IdGenerator viewIdGenerator; protected IdGenerator eventIdGenerator; protected ViewIdProvider viewIdProvider; + /** * Shouldn't be used! */ @@ -211,4 +212,8 @@ String[] getLocationParams() { boolean isLocationDisabled() { return !locationEnabled; } + + boolean isAutoSendUserPropertiesOnSessions() { + return autoSendUserPropertiesOnSessions; + } } diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java index 22a59e8d..f966034f 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java @@ -118,6 +118,9 @@ Future begin(Long now) { } this.consents = SDKCore.instance.consents; + if (config.isAutoSendUserPropertiesOnSessions() && config.sdk.userProfile() != null) { + config.sdk.module(ModuleUserProfile.class).saveInternal(); + } if (pushOnChange) { Storage.pushAsync(config, this); @@ -157,6 +160,9 @@ Future update(Long now) { } this.consents = SDKCore.instance.consents; + if (config.isAutoSendUserPropertiesOnSessions() && config.sdk.userProfile() != null) { + config.sdk.module(ModuleUserProfile.class).saveInternal(); + } Long duration = updateDuration(now); @@ -192,6 +198,9 @@ Future end(Long now, final Tasks.Callback callback, String did ended = now == null ? System.nanoTime() : now; this.consents = SDKCore.instance.consents; + if (config.isAutoSendUserPropertiesOnSessions() && config.sdk.userProfile() != null) { + config.sdk.module(ModuleUserProfile.class).saveInternal(); + } if (currentView != null) { currentView.stop(true); diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java index 859d50c3..52e16e10 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java @@ -629,6 +629,81 @@ public void view_stopStartedAndNext() { ModuleViewsTests.validateView("next", 0.0, 2, 3, false, true, null, TestUtils.keysValues[1], TestUtils.keysValues[0]); } + /** + * Validates that when session calls are made, if any user properties are set, + * they are sent before sending that session call + * Validated with all session calls: begin, update, end + * + * @throws InterruptedException if thread is interrupted + */ + @Test + public void userPropsOnSessions() throws InterruptedException { + Countly.instance().init(TestUtils.getConfigSessions(Config.Feature.UserProfiles)); + Countly.instance().userProfile().setProperty("name", "John Doe"); + Countly.instance().userProfile().setProperty("custom_key", "custom_value"); + + Countly.session().begin(); + Map[] RQ = TestUtils.getCurrentRQ(); + UserEditorTests.validateUserDetailsRequestInRQ(TestUtils.map("user_details", TestUtils.json("name", "John Doe", "custom", TestUtils.map("custom_key", "custom_value"))), 0, 2); + Assert.assertEquals("1", RQ[1].get("begin_session")); + + Thread.sleep(2000); // wait for session to update + Countly.instance().userProfile().save(); + RQ = TestUtils.getCurrentRQ(); + Assert.assertEquals(2, RQ.length); // Validate that user properties are flushed + + Countly.instance().userProfile().setProperty("email", "john@doe.com"); + Countly.session().update(); + + RQ = TestUtils.getCurrentRQ(); + Assert.assertEquals(TestUtils.json("email", "john@doe.com"), RQ[2].get("user_details")); + Assert.assertEquals("2", RQ[3].get("session_duration")); + + Thread.sleep(2000); // wait for session to update + Countly.instance().userProfile().save(); + RQ = TestUtils.getCurrentRQ(); + Assert.assertEquals(4, RQ.length); // Validate that user properties are flushed with update call + + Countly.instance().userProfile().setProperty("done", "yes"); + Countly.session().end(); + + RQ = TestUtils.getCurrentRQ(); + Assert.assertEquals(TestUtils.json("custom", TestUtils.map("done", "yes")), RQ[4].get("user_details")); + Assert.assertEquals("1", RQ[5].get("end_session")); + } + + /** + * Validates that when session calls are made, if any user properties are set, + * they are not packed because auto-send is disabled + * + * @throws InterruptedException if thread is interrupted + */ + @Test + public void userPropsOnSessions_reversed() throws InterruptedException { + Countly.instance().init(TestUtils.getConfigSessions(Config.Feature.UserProfiles).disableAutoSendUserPropertiesOnSessions()); + Countly.instance().userProfile().setProperty("name", "John Doe"); + Countly.instance().userProfile().setProperty("custom_key", "custom_value"); + + Countly.session().begin(); + Map[] RQ = TestUtils.getCurrentRQ(); + Assert.assertEquals(1, RQ.length); + Assert.assertEquals("1", RQ[0].get("begin_session")); + + Thread.sleep(2000); // wait for session to update + Countly.session().update(); + RQ = TestUtils.getCurrentRQ(); + + Assert.assertEquals(2, RQ.length); + Assert.assertEquals("2", RQ[1].get("session_duration")); + + Thread.sleep(2000); + Countly.session().end(); + RQ = TestUtils.getCurrentRQ(); + + Assert.assertEquals(3, RQ.length); + Assert.assertEquals("1", RQ[2].get("end_session")); + } + private void validateNotEquals(int idOffset, BiFunction> setter) { Countly.instance().init(TestUtils.getConfigSessions()); long ts = TimeUtils.timestampMs();