Skip to content

Commit

Permalink
Adding owner to config keys and analytics (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
jraska committed May 3, 2020
1 parent a65515c commit 07fdc23
Show file tree
Hide file tree
Showing 18 changed files with 144 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ import org.junit.Test
class FirebaseEventConverterTest {
@Test
fun convertsProperly() {
val event = AnalyticsEvent.builder("whatever")
.addProperty("boolean", true)
.addProperty("int", 8)
.addProperty("double", 3.4)
.addProperty("string", "Hello")
.addProperty("long", 123L)
.build()
val event = AnalyticsEvent.builder(
AnalyticsEvent.Key("whatever", Owner.CORE_TEAM))
.addProperty("boolean", true)
.addProperty("int", 8)
.addProperty("double", 3.4)
.addProperty("string", "Hello")
.addProperty("long", 123L)
.build()

val firebaseBundle = FirebaseEventConverter.firebaseBundle(event.properties)!!
val firebaseBundle = FirebaseEventConverter . firebaseBundle (event.properties)!!

assertThat(firebaseBundle.getInt("boolean")).isEqualTo(1)
assertThat(firebaseBundle.getDouble("double")).isEqualTo(3.4)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ internal class FirebaseConfigProxy(private val config: FirebaseRemoteConfig) : C
Date(config.info.fetchTimeMillis))
}

override fun getBoolean(key: String): Boolean {
return config.getBoolean(key)
override fun getBoolean(key: Config.Key): Boolean {
return config.getBoolean(key.name)
}

override fun getLong(key: String): Long {
return config.getLong(key)
override fun getLong(key: Config.Key): Long {
return config.getLong(key.name)
}

override fun getString(key: String): String {
return config.getString(key)
override fun getString(key: Config.Key): String {
return config.getString(key.name)
}

override fun triggerRefresh() {
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/java/com/jraska/github/client/FirebaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import timber.log.Timber
import javax.inject.Singleton

@Module
class FirebaseModule {
object FirebaseModule {

val ANALYTICS_DISABLED_KEY = Config.Key("analytics_disabled", Owner.CORE_TEAM)

@Provides
@Singleton internal fun firebaseAnalytics(config: Config): FirebaseEventAnalytics {
val firebaseAnalytics = FirebaseAnalytics.getInstance(FirebaseApp.getInstance().applicationContext)

if (config.getBoolean("analytics_disabled")) {
if (config.getBoolean(ANALYTICS_DISABLED_KEY)) {
firebaseAnalytics.setAnalyticsCollectionEnabled(false)
Timber.d("Analytics disabled")
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.jraska.github.client.DeepLinkLauncher
import com.jraska.github.client.Owner
import com.jraska.github.client.analytics.AnalyticsEvent
import com.jraska.github.client.analytics.EventAnalytics
import com.jraska.github.client.core.android.logging.SetupLogging
Expand Down Expand Up @@ -94,7 +95,7 @@ object CoreAndroidModule {
fun reportAppCreateEvent(eventAnalytics: EventAnalytics): OnAppCreate {
return object : OnAppCreate {
override fun onCreate(app: Application) {
val createEvent = AnalyticsEvent.create("app_create")
val createEvent = AnalyticsEvent.create(AnalyticsEvent.Key("app_create", Owner.CORE_TEAM))
eventAnalytics.report(createEvent)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.jraska.github.client.core.android

import androidx.lifecycle.ViewModel
import com.jraska.github.client.DeepLinkHandler
import com.jraska.github.client.Owner
import com.jraska.github.client.analytics.AnalyticsEvent
import com.jraska.github.client.analytics.EventAnalytics
import com.jraska.github.client.analytics.toAnalyticsString
Expand All @@ -16,10 +17,14 @@ class UriHandlerViewModel @Inject constructor(
fun handleDeepLink(deepLink: HttpUrl) {
val success = deepLinkHandler.handleDeepLink(deepLink)

val event = AnalyticsEvent.builder("deep_link_received")
val event = AnalyticsEvent.builder(ANALYTICS_DEEP_LINK_KEY)
.addProperty("deep_link", deepLink.toAnalyticsString())
.addProperty("success", success.toBoolean())
.build()
eventAnalytics.report(event)
}

companion object {
val ANALYTICS_DEEP_LINK_KEY = AnalyticsEvent.Key("deep_link_received", Owner.CORE_TEAM)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.jraska.github.client.core.android.logging

import android.util.Log
import com.jraska.github.client.Config
import com.jraska.github.client.Owner
import com.jraska.github.client.analytics.AnalyticsEvent
import com.jraska.github.client.analytics.EventAnalytics
import com.jraska.github.client.time.DateTimeProvider
Expand All @@ -14,14 +15,25 @@ class AnalyticsLoggingTree @Inject constructor(
private val dateTimeProvider: DateTimeProvider
) : Timber.DebugTree() {

private val analyticsKeyMap by lazy {
mapOf(
Log.VERBOSE to AnalyticsEvent.Key("verbose", Owner.CORE_TEAM),
Log.DEBUG to AnalyticsEvent.Key("debug", Owner.CORE_TEAM),
Log.INFO to AnalyticsEvent.Key("info", Owner.CORE_TEAM),
Log.WARN to AnalyticsEvent.Key("warning", Owner.CORE_TEAM),
Log.ERROR to AnalyticsEvent.Key("error", Owner.CORE_TEAM),
Log.ASSERT to AnalyticsEvent.Key("WTF", Owner.CORE_TEAM)
).withDefault { AnalyticsEvent.Key("unknown", Owner.CORE_TEAM) }
}

override fun isLoggable(tag: String?, priority: Int): Boolean {
val priorityToLog = config.getLong("logging_analytics_priority")
val priorityToLog = config.getLong(LOGGING_PRIORITY)
return (priority >= priorityToLog && priorityToLog != 0L)
}

override fun log(priority: Int, tag: String?, message: String, error: Throwable?) {
val analyticsName = analyticsName(priority)
val eventBuilder = AnalyticsEvent.builder(analyticsName)
val analyticsKey = analyticsKeyMap.getValue(priority)
val eventBuilder = AnalyticsEvent.builder(analyticsKey)
.addProperty("tag", maxString(tag))
.addProperty("message", maxString(message))
.addProperty("time", dateTimeProvider.now().toString())
Expand All @@ -47,20 +59,8 @@ class AnalyticsLoggingTree @Inject constructor(
}
}

private fun analyticsName(priority: Int): String {
return when (priority) {
Log.VERBOSE -> "verbose"
Log.DEBUG -> "debug"
Log.INFO -> "info"
Log.WARN -> "warning"
Log.ERROR -> "error"
Log.ASSERT -> "WTF"

else -> "unknown"
}
}

companion object {
const val MAX_LENGTH: Int = 100
val LOGGING_PRIORITY = Config.Key("logging_analytics_priority", Owner.CORE_TEAM)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ class FakeConfig private constructor(private val values: MutableMap<String, Any>
// do nothing
}

override fun getBoolean(key: String): Boolean {
return (values[key] ?: false) as Boolean
override fun getBoolean(key: Config.Key): Boolean {
return (values[key.name] ?: false) as Boolean
}

override fun getString(key: String): String {
return (values[key] ?: "") as String
override fun getString(key: Config.Key): String {
return (values[key.name] ?: "") as String
}

override fun getLong(key: String): Long {
return (values[key] ?: 0L) as Long
override fun getLong(key: Config.Key): Long {
return (values[key.name] ?: 0L) as Long
}

fun set(key: String, value: Any): RevertSet {
Expand Down
8 changes: 5 additions & 3 deletions core/src/main/java/com/jraska/github/client/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package com.jraska.github.client
interface Config {
fun triggerRefresh()

fun getBoolean(key: String): Boolean
fun getBoolean(key: Key): Boolean

fun getLong(key: String): Long
fun getLong(key: Key): Long

fun getString(key: String): String
fun getString(key: Key): String

class Key(val name: String, val owner: Owner)
}
20 changes: 20 additions & 0 deletions core/src/main/java/com/jraska/github/client/Owner.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.jraska.github.client

/**
* This enum is a demonstration how to scale maintenance of keys like analytics and remote configuration over the long term.
*
* - All keys, which are interacting with external system have to have an owner.
* - The reason for that is the input for these is dynamic, by time it is not clear if they are still used or consumed and tracing them is difficult.
*
* - Each type of a resource coming from outside has also a stale state after some time of inactivity during keys audit.
* - In case of some key being stale - it can be deleted together with related code.
* - At the moment some key has an Owner#Unknown - it becomes stale.
*
* @see AnalyticsEvent.Key
* @see Config.Key for remote configuration
*/
enum class Owner {
CORE_TEAM,
USERS_TEAM,
UNKNOWN // Stale! Can be deleted at any moment during resource key audit.
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package com.jraska.github.client.analytics

import com.jraska.github.client.Owner
import java.util.Collections

class AnalyticsEvent private constructor(
val name: String,
val key: Key,
val properties: Map<String, Any>
) {

class Builder internal constructor(private val name: String) {
val name get() = key.name

class Key(val name: String, val owner: Owner)

class Builder internal constructor(private val key: Key) {
private val properties = HashMap<String, Any>()

private fun addAny(name: String, value: Any): Builder {
Expand Down Expand Up @@ -36,17 +41,17 @@ class AnalyticsEvent private constructor(
}

fun build(): AnalyticsEvent {
return AnalyticsEvent(name, Collections.unmodifiableMap(properties))
return AnalyticsEvent(key, Collections.unmodifiableMap(properties))
}
}

companion object {
fun create(name: String): AnalyticsEvent {
return AnalyticsEvent(name, emptyMap())
fun create(key: Key): AnalyticsEvent {
return AnalyticsEvent(key, emptyMap())
}

fun builder(name: String): Builder {
return Builder(name)
fun builder(key: Key): Builder {
return Builder(key)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jraska.github.client.about

import androidx.lifecycle.ViewModel
import com.jraska.github.client.Owner
import com.jraska.github.client.analytics.AnalyticsEvent
import com.jraska.github.client.analytics.EventAnalytics
import com.jraska.github.client.analytics.toAnalyticsString
Expand Down Expand Up @@ -37,7 +38,7 @@ internal class AboutViewModel @Inject constructor(

private fun openUrl(urlText: String) {
val url = urlText.toHttpUrl()
val event = AnalyticsEvent.builder("about_clicked")
val event = AnalyticsEvent.builder(ANALYTICS_ABOUT_CLICKED)
.addProperty("url", url.toAnalyticsString())
.addProperty("user_id", identityProvider.session().userId.toString())
.build()
Expand All @@ -46,4 +47,8 @@ internal class AboutViewModel @Inject constructor(

navigator.launchOnWeb(url)
}

companion object {
val ANALYTICS_ABOUT_CLICKED = AnalyticsEvent.Key("about_clicked", Owner.USERS_TEAM)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jraska.github.client.push

import com.jraska.github.client.Config
import com.jraska.github.client.Owner
import com.jraska.github.client.analytics.AnalyticsProperty
import com.jraska.github.client.common.BooleanResult
import com.jraska.github.client.common.BooleanResult.FAILURE
Expand All @@ -13,7 +14,7 @@ internal class ConfigAsPropertyCommand @Inject constructor(
override fun execute(action: PushAction): BooleanResult {
val key = action.parameters["config_key"] ?: return FAILURE

val value = config.getString(key)
val value = config.getString(Config.Key(key, Owner.UNKNOWN))
analyticsProperty.setUserProperty(key, value)
return BooleanResult.SUCCESS
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jraska.github.client.push

import com.jraska.github.client.Owner
import com.jraska.github.client.analytics.AnalyticsEvent
import com.jraska.github.client.analytics.EventAnalytics
import com.jraska.github.client.common.BooleanResult
Expand All @@ -11,23 +12,29 @@ internal class PushAnalytics @Inject constructor(
private val eventAnalytics: EventAnalytics
) {
fun onTokenRefresh() {
val tokenEvent = AnalyticsEvent.builder("push_token_refresh")
val tokenEvent = AnalyticsEvent.builder(ANALYTICS_PUSH_TOKEN_REFRESH)
.addProperty("session_id", identityProvider.session().id.toString())
.build()
eventAnalytics.report(tokenEvent)
}

fun onPushHandled(action: PushAction, result: BooleanResult) {
if (result == BooleanResult.SUCCESS) {
val pushHandled = AnalyticsEvent.builder("push_handled")
val pushHandled = AnalyticsEvent.builder(ANALYTICS_PUSH_HANDLED)
.addProperty("push_action", action.name)
.build()
eventAnalytics.report(pushHandled)
} else {
val pushHandled = AnalyticsEvent.builder("push_not_handled")
val pushHandled = AnalyticsEvent.builder(ANALYTICS_PUSH_NOT_HANDLED)
.addProperty("push_action", action.name)
.build()
eventAnalytics.report(pushHandled)
}
}

companion object {
val ANALYTICS_PUSH_TOKEN_REFRESH = AnalyticsEvent.Key("push_token_refresh", Owner.CORE_TEAM)
val ANALYTICS_PUSH_HANDLED = AnalyticsEvent.Key("push_handled", Owner.CORE_TEAM)
val ANALYTICS_PUSH_NOT_HANDLED = AnalyticsEvent.Key("push_not_handled", Owner.CORE_TEAM)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jraska.github.client.settings

import androidx.lifecycle.ViewModel
import com.jraska.github.client.Owner
import com.jraska.github.client.analytics.AnalyticsEvent
import com.jraska.github.client.analytics.EventAnalytics
import javax.inject.Inject
Expand All @@ -12,11 +13,15 @@ constructor(
fun onPurchaseSubmitted(value: String) {
val money = value.toDoubleOrNull() ?: return

val event = AnalyticsEvent.builder("ecommerce_purchase")
val event = AnalyticsEvent.builder(ANALYTICS_ECOMMERCE_PURCHASE)
.addProperty("value", money)
.addProperty("currency", "USD")
.build()

eventAnalytics.report(event)
}

companion object {
val ANALYTICS_ECOMMERCE_PURCHASE = AnalyticsEvent.Key("ecommerce_purchase", Owner.USERS_TEAM)
}
}
Loading

0 comments on commit 07fdc23

Please sign in to comment.