Skip to content

Commit

Permalink
feat: use exposure-v2 in connector analytics provider (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
bgiori authored Feb 11, 2022
1 parent da7a11c commit afe9cfc
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,32 @@ internal class ConnectorAnalyticsProvider(
): ExperimentAnalyticsProvider {

override fun track(event: ExperimentAnalyticsEvent) {
val eventProperties: Map<String, String> = event.properties
.filterValues { it != null }
.mapValues { it.value!! }
eventBridge.logEvent(
AnalyticsEvent(
eventType = event.name,
eventProperties = eventProperties
eventType = "\$exposure",
eventProperties = mapOf(
"flag_key" to event.key,
"variant" to event.variant.value
).filterNull()
)
)
}

override fun setUserProperty(event: ExperimentAnalyticsEvent) {
val variant = event.variant.value ?: return
eventBridge.logEvent(
AnalyticsEvent(
"\$identify",
null,
mapOf(
"\$set" to mapOf(
event.userProperty to variant
)
)
)
)
}

override fun unsetUserProperty(event: ExperimentAnalyticsEvent) {
eventBridge.logEvent(
AnalyticsEvent(
"\$identify",
null,
mapOf(
"\$unset" to mapOf(
event.userProperty to "-"
)
)
eventType = "\$exposure",
eventProperties = mapOf(
"flag_key" to event.key,
).filterNull()
)
)
}
}

private fun Map<String, String?>.filterNull(): Map<String, String> {
return filterValues { it != null }.mapValues { it.value!! }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,41 @@ internal class SessionAnalyticsProvider(
private val analyticsProvider: ExperimentAnalyticsProvider,
): ExperimentAnalyticsProvider {

private val lock = Any()
private val setProperties = mutableMapOf<String, String>()
private val unsetProperties = mutableSetOf<String>()

override fun track(event: ExperimentAnalyticsEvent) {
val variant = event.variant.value ?: return
if (setProperties[event.key] == variant) {
return
} else {
setProperties[event.key] = variant
unsetProperties.remove(event.key)
synchronized(lock) {
if (setProperties[event.key] == variant) {
return
} else {
setProperties[event.key] = variant
unsetProperties.remove(event.key)
}
}
analyticsProvider.track(event)
}

override fun setUserProperty(event: ExperimentAnalyticsEvent) {
val variant = event.variant.value ?: return
if (setProperties[event.key] == variant) {
return
synchronized(lock) {
if (setProperties[event.key] == variant) {
return@setUserProperty
}
}
analyticsProvider.setUserProperty(event)
}

override fun unsetUserProperty(event: ExperimentAnalyticsEvent) {
if (unsetProperties.contains(event.key)) {
return
} else {
unsetProperties.add(event.key)
setProperties.remove(event.key)
synchronized(lock) {
if (unsetProperties.contains(event.key)) {
return@unsetUserProperty
} else {
unsetProperties.add(event.key)
setProperties.remove(event.key)
}
}
analyticsProvider.unsetUserProperty(event)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class TestEventBridge : EventBridge {
override fun setEventReceiver(receiver: AnalyticsEventReceiver?) {}
}

class AnalyticsProviderTest {
class ConnectorAnalyticsProviderTest {

private val exposureEvent1 = ExposureEvent(
user = ExperimentUser(),
Expand All @@ -33,78 +33,80 @@ class AnalyticsProviderTest {
source = VariantSource.LOCAL_STORAGE,
)
private val expectedSet1 = AnalyticsEvent(
eventType = "\$identify",
eventProperties = null,
userProperties = mapOf(
"\$set" to mapOf("[Experiment] test-key" to "test")
)
eventType = "\$exposure",
eventProperties = mapOf(
"flag_key" to "test-key",
"variant" to "test",
),
)
private val expectedSet2 = AnalyticsEvent(
eventType = "\$identify",
eventProperties = null,
userProperties = mapOf(
"\$set" to mapOf("[Experiment] test-key" to "test2")
)
eventType = "\$exposure",
eventProperties = mapOf(
"flag_key" to "test-key",
"variant" to "test2",
),
)
private val expectedTrack1 = AnalyticsEvent(
eventType = "[Experiment] Exposure",
eventProperties = exposureEvent1.properties
.filterValues { it != null }
.mapValues { it.value!! },
userProperties = null,
eventType = "\$exposure",
eventProperties = mapOf(
"flag_key" to "test-key",
"variant" to "test",
),
)

private val expectedTrack2 = AnalyticsEvent(
eventType = "[Experiment] Exposure",
eventProperties = exposureEvent2.properties
.filterValues { it != null }
.mapValues { it.value!! },
userProperties = null,
eventType = "\$exposure",
eventProperties = mapOf(
"flag_key" to "test-key",
"variant" to "test2",
),
)
private val expectedUnset = AnalyticsEvent(
eventType = "\$identify",
eventProperties = null,
userProperties = mapOf(
"\$unset" to mapOf("[Experiment] test-key" to "-")
)
eventType = "\$exposure",
eventProperties = mapOf(
"flag_key" to "test-key",
),
)


@Test
fun `set and track called once each per variant`() {
val eventBridge = TestEventBridge()
val coreAnalyticsProvider = SessionAnalyticsProvider(ConnectorAnalyticsProvider(eventBridge))

coreAnalyticsProvider.setUserProperty(exposureEvent1)
Assert.assertEquals(expectedSet1, eventBridge.recentEvent)
Assert.assertEquals(null, eventBridge.recentEvent)
coreAnalyticsProvider.track(exposureEvent1)
Assert.assertEquals(expectedTrack1, eventBridge.recentEvent)
Assert.assertEquals(eventBridge.logEventCount, 1)
repeat(10) {
eventBridge.recentEvent = null
coreAnalyticsProvider.setUserProperty(exposureEvent1)
Assert.assertEquals(expectedTrack1, eventBridge.recentEvent)
Assert.assertEquals(null, eventBridge.recentEvent)
coreAnalyticsProvider.track(exposureEvent1)
Assert.assertEquals(expectedTrack1, eventBridge.recentEvent)
Assert.assertEquals(null, eventBridge.recentEvent)
}
Assert.assertEquals(eventBridge.logEventCount, 2)
Assert.assertEquals(eventBridge.logEventCount, 1)

eventBridge.recentEvent = null
coreAnalyticsProvider.setUserProperty(exposureEvent2)
Assert.assertEquals(expectedSet2, eventBridge.recentEvent)
Assert.assertEquals(null, eventBridge.recentEvent)
coreAnalyticsProvider.track(exposureEvent2)
Assert.assertEquals(expectedTrack2, eventBridge.recentEvent)
repeat(10) {
eventBridge.recentEvent = null
coreAnalyticsProvider.setUserProperty(exposureEvent2)
Assert.assertEquals(expectedTrack2, eventBridge.recentEvent)
Assert.assertEquals(null, eventBridge.recentEvent)
coreAnalyticsProvider.track(exposureEvent2)
Assert.assertEquals(expectedTrack2, eventBridge.recentEvent)
Assert.assertEquals(null, eventBridge.recentEvent)
}
Assert.assertEquals(eventBridge.logEventCount, 4)
Assert.assertEquals(eventBridge.logEventCount, 2)
}

@Test
fun `unset called once per key`() {
val eventBridge = TestEventBridge()
val coreAnalyticsProvider = SessionAnalyticsProvider(ConnectorAnalyticsProvider(eventBridge))
val exposureEvent1 = ExposureEvent(ExperimentUser(), "test-key", Variant("test"), VariantSource.LOCAL_STORAGE)
val exposureEvent1 = ExposureEvent(ExperimentUser(), "test-key", Variant("off"), VariantSource.FALLBACK_INLINE)
repeat(10) {
coreAnalyticsProvider.unsetUserProperty(exposureEvent1)
Assert.assertEquals(expectedUnset, eventBridge.recentEvent)
Expand All @@ -116,17 +118,19 @@ class AnalyticsProviderTest {
fun `test property set tracked, unset, set tracked`() {
val eventBridge = TestEventBridge()
val coreAnalyticsProvider = ConnectorAnalyticsProvider(eventBridge)
val unsetEvent = ExposureEvent(ExperimentUser(), "test-key", Variant(), VariantSource.FALLBACK_CONFIG)
repeat(10) {
// set
eventBridge.recentEvent = null
// set (not called in ConnectorUserProvider)
coreAnalyticsProvider.setUserProperty(exposureEvent1)
Assert.assertEquals(expectedSet1, eventBridge.recentEvent)
Assert.assertEquals(null, eventBridge.recentEvent)
// track
coreAnalyticsProvider.track(exposureEvent1)
Assert.assertEquals(expectedTrack1, eventBridge.recentEvent)
// unset
coreAnalyticsProvider.unsetUserProperty(exposureEvent1)
coreAnalyticsProvider.unsetUserProperty(unsetEvent)
Assert.assertEquals(expectedUnset, eventBridge.recentEvent)
}
Assert.assertEquals(eventBridge.logEventCount, 30)
Assert.assertEquals(eventBridge.logEventCount, 20)
}
}

0 comments on commit afe9cfc

Please sign in to comment.