Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding owner to config keys and analytics #230

Merged
merged 1 commit into from
May 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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