Skip to content

Commit 308b80b

Browse files
authored
Merge pull request #2487 from DataDog/maciey/RUM-7797/anonymous-id
RUM-7797 Anonymous RUM Identifier
2 parents fc254cf + d91b764 commit 308b80b

38 files changed

+403
-14
lines changed

dd-sdk-android-core/api/apiSurface

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ data class com.datadog.android.api.context.ProcessInfo
8989
data class com.datadog.android.api.context.TimeInfo
9090
constructor(Long, Long, Long, Long)
9191
data class com.datadog.android.api.context.UserInfo
92-
constructor(kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, Map<kotlin.String, kotlin.Any?> = emptyMap())
92+
constructor(kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, kotlin.String? = null, Map<kotlin.String, kotlin.Any?> = emptyMap())
9393
interface com.datadog.android.api.feature.Feature
9494
val name: String
9595
fun onInitialize(android.content.Context)
@@ -123,6 +123,7 @@ interface com.datadog.android.api.feature.FeatureSdkCore : com.datadog.android.a
123123
fun removeEventReceiver(String)
124124
fun createSingleThreadExecutorService(String): java.util.concurrent.ExecutorService
125125
fun createScheduledExecutorService(String): java.util.concurrent.ScheduledExecutorService
126+
fun setAnonymousId(java.util.UUID?)
126127
interface com.datadog.android.api.feature.StorageBackedFeature : Feature
127128
val requestFactory: com.datadog.android.api.net.RequestFactory
128129
val storageConfiguration: com.datadog.android.api.storage.FeatureStorageConfiguration

dd-sdk-android-core/api/dd-sdk-android-core.api

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,18 +281,20 @@ public final class com/datadog/android/api/context/TimeInfo {
281281

282282
public final class com/datadog/android/api/context/UserInfo {
283283
public fun <init> ()V
284-
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V
285-
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
284+
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V
285+
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
286286
public final fun component1 ()Ljava/lang/String;
287287
public final fun component2 ()Ljava/lang/String;
288288
public final fun component3 ()Ljava/lang/String;
289-
public final fun component4 ()Ljava/util/Map;
290-
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/api/context/UserInfo;
291-
public static synthetic fun copy$default (Lcom/datadog/android/api/context/UserInfo;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/api/context/UserInfo;
289+
public final fun component4 ()Ljava/lang/String;
290+
public final fun component5 ()Ljava/util/Map;
291+
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/api/context/UserInfo;
292+
public static synthetic fun copy$default (Lcom/datadog/android/api/context/UserInfo;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/api/context/UserInfo;
292293
public fun equals (Ljava/lang/Object;)Z
293294
public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/api/context/UserInfo;
294295
public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/api/context/UserInfo;
295296
public final fun getAdditionalProperties ()Ljava/util/Map;
297+
public final fun getAnonymousId ()Ljava/lang/String;
296298
public final fun getEmail ()Ljava/lang/String;
297299
public final fun getId ()Ljava/lang/String;
298300
public final fun getName ()Ljava/lang/String;
@@ -355,6 +357,7 @@ public abstract interface class com/datadog/android/api/feature/FeatureSdkCore :
355357
public abstract fun registerFeature (Lcom/datadog/android/api/feature/Feature;)V
356358
public abstract fun removeContextUpdateReceiver (Ljava/lang/String;Lcom/datadog/android/api/feature/FeatureContextUpdateReceiver;)V
357359
public abstract fun removeEventReceiver (Ljava/lang/String;)V
360+
public abstract fun setAnonymousId (Ljava/util/UUID;)V
358361
public abstract fun setContextUpdateReceiver (Ljava/lang/String;Lcom/datadog/android/api/feature/FeatureContextUpdateReceiver;)V
359362
public abstract fun setEventReceiver (Ljava/lang/String;Lcom/datadog/android/api/feature/FeatureEventReceiver;)V
360363
public abstract fun updateFeatureContext (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V

dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/context/UserInfo.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ import kotlin.jvm.Throws
2222

2323
/**
2424
* Holds information about the current User.
25+
* @property anonymousId a unique anonymous identifier for the device, or null
2526
* @property id a unique identifier for the user, or null
2627
* @property name the name of the user, or null
2728
* @property email the email address of the user, or null
2829
* @property additionalProperties a dictionary of custom properties attached to the current user
2930
*/
3031
data class UserInfo(
32+
val anonymousId: String? = null,
3133
val id: String? = null,
3234
val name: String? = null,
3335
val email: String? = null,
@@ -37,6 +39,9 @@ data class UserInfo(
3739
@Suppress("StringLiteralDuplication")
3840
internal fun toJson(): JsonElement {
3941
val json = JsonObject()
42+
anonymousId?.let { idNonNull ->
43+
json.addProperty("anonymous_id", idNonNull)
44+
}
4045
id?.let { idNonNull ->
4146
json.addProperty("id", idNonNull)
4247
}
@@ -79,6 +84,7 @@ data class UserInfo(
7984
@Suppress("StringLiteralDuplication", "ThrowsCount")
8085
fun fromJsonObject(jsonObject: JsonObject): UserInfo {
8186
try {
87+
val anonymousId = jsonObject.get("anonymous_id")?.asString
8288
val id = jsonObject.get("id")?.asString
8389
val name = jsonObject.get("name")?.asString
8490
val email = jsonObject.get("email")?.asString
@@ -88,7 +94,7 @@ data class UserInfo(
8894
additionalProperties[entry.key] = entry.value
8995
}
9096
}
91-
return UserInfo(id, name, email, additionalProperties)
97+
return UserInfo(anonymousId, id, name, email, additionalProperties)
9298
} catch (e: IllegalStateException) {
9399
throw JsonParseException(
94100
"Unable to parse json into type UserInfo",

dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/feature/FeatureSdkCore.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package com.datadog.android.api.feature
88

99
import com.datadog.android.api.InternalLogger
1010
import com.datadog.android.api.SdkCore
11+
import java.util.UUID
1112
import java.util.concurrent.ExecutorService
1213
import java.util.concurrent.ScheduledExecutorService
1314

@@ -16,6 +17,7 @@ import java.util.concurrent.ScheduledExecutorService
1617
*
1718
* SDK core is always guaranteed to implement this interface.
1819
*/
20+
@Suppress("TooManyFunctions")
1921
interface FeatureSdkCore : SdkCore {
2022

2123
/**
@@ -104,4 +106,11 @@ interface FeatureSdkCore : SdkCore {
104106
* @param executorContext Context to be used for logging and naming threads running on this executor.
105107
*/
106108
fun createScheduledExecutorService(executorContext: String): ScheduledExecutorService
109+
110+
/**
111+
* Allows the given feature to set the anonymous ID for the SDK.
112+
*
113+
* @param anonymousId Anonymous ID to set. Can be null if feature is disabled.
114+
*/
115+
fun setAnonymousId(anonymousId: UUID?)
107116
}

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import com.datadog.android.privacy.TrackingConsent
4444
import com.google.gson.JsonObject
4545
import java.io.File
4646
import java.util.Locale
47+
import java.util.UUID
4748
import java.util.concurrent.ConcurrentHashMap
4849
import java.util.concurrent.ExecutorService
4950
import java.util.concurrent.ScheduledExecutorService
@@ -268,6 +269,10 @@ internal class DatadogCore(
268269
return coreFeature.createScheduledExecutorService(executorContext)
269270
}
270271

272+
override fun setAnonymousId(anonymousId: UUID?) {
273+
coreFeature.userInfoProvider.setAnonymousId(anonymousId?.toString())
274+
}
275+
271276
override fun isCoreActive(): Boolean = isActive
272277

273278
// endregion

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpContextProvider.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ internal class NoOpContextProvider : ContextProvider {
4545
cellularTechnology = null
4646
),
4747
deviceInfo = DeviceInfo("", "", "", DeviceType.OTHER, "", "", "", "", ""),
48-
userInfo = UserInfo(null, null, null, emptyMap()),
48+
userInfo = UserInfo(null, null, null, null, emptyMap()),
4949
trackingConsent = TrackingConsent.NOT_GRANTED,
5050
appBuildId = null,
5151
featuresContext = emptyMap()

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/NoOpInternalSdkCore.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
2222
import com.datadog.android.privacy.TrackingConsent
2323
import com.google.gson.JsonObject
2424
import java.io.File
25+
import java.util.UUID
2526
import java.util.concurrent.Callable
2627
import java.util.concurrent.Delayed
2728
import java.util.concurrent.ExecutionException
@@ -128,6 +129,8 @@ internal object NoOpInternalSdkCore : InternalSdkCore {
128129
return NoOpScheduledExecutorService()
129130
}
130131

132+
override fun setAnonymousId(anonymousId: UUID?) = Unit
133+
131134
// endregion
132135

133136
// region InternalSdkCore

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/user/DatadogUserInfoProvider.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ internal class DatadogUserInfoProvider(
2929
)
3030
}
3131

32+
override fun setAnonymousId(id: String?) {
33+
internalUserInfo = internalUserInfo.copy(
34+
anonymousId = id
35+
)
36+
}
37+
3238
override fun addUserProperties(properties: Map<String, Any?>) {
3339
internalUserInfo = internalUserInfo.copy(
3440
additionalProperties = internalUserInfo.additionalProperties + properties

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/user/MutableUserInfoProvider.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,7 @@ internal interface MutableUserInfoProvider : UserInfoProvider {
1818
extraInfo: Map<String, Any?>
1919
)
2020

21+
fun setAnonymousId(id: String?)
22+
2123
fun addUserProperties(properties: Map<String, Any?>)
2224
}

dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import org.mockito.kotlin.whenever
7777
import org.mockito.quality.Strictness
7878
import java.util.Collections
7979
import java.util.Locale
80+
import java.util.UUID
8081
import java.util.concurrent.CountDownLatch
8182
import java.util.concurrent.Future
8283
import java.util.concurrent.TimeUnit
@@ -228,6 +229,35 @@ internal class DatadogCoreTest {
228229
verify(mockUserInfoProvider).setUserInfo(id, name, email, fakeUserProperties)
229230
}
230231

232+
@Test
233+
fun `M update anonymousId W setAnonymousId()`(
234+
forge: Forge
235+
) {
236+
// Given
237+
val uuid = forge.getForgery<UUID>()
238+
val mockUserInfoProvider = mock<MutableUserInfoProvider>()
239+
testedCore.coreFeature.userInfoProvider = mockUserInfoProvider
240+
241+
// When
242+
testedCore.setAnonymousId(uuid)
243+
244+
// Then
245+
verify(mockUserInfoProvider).setAnonymousId(uuid.toString())
246+
}
247+
248+
@Test
249+
fun `M clears anonymousId W setAnonymousId(null)`() {
250+
// Given
251+
val mockUserInfoProvider = mock<MutableUserInfoProvider>()
252+
testedCore.coreFeature.userInfoProvider = mockUserInfoProvider
253+
254+
// When
255+
testedCore.setAnonymousId(null)
256+
257+
// Then
258+
verify(mockUserInfoProvider).setAnonymousId(null)
259+
}
260+
231261
@Test
232262
fun `M set additional user info W addUserProperties() is called`(
233263
@StringForgery(type = StringForgeryType.HEXADECIMAL) id: String,

0 commit comments

Comments
 (0)